Result.cs (18865B)
1 using Std.Clone; 2 using Std.Cmp; 3 using static Std.Cmp.Ordering; 4 using Std.Convert; 5 using Std.Hashing; 6 using Std.Iter; 7 using Std.Maybe; 8 using static Std.Maybe.Maybe<Std.Cmp.Ordering>; 9 using Std.Ops; 10 using System; 11 using System.Runtime.CompilerServices; 12 using System.Runtime.InteropServices; 13 #region Namespaces 14 namespace Std.Result { 15 #region Types 16 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 0)] 17 public struct IntoIterator<T>: IDoubleEndedIterator<T>, IExactSizeIterator<T>, IFusedIterator<T>, IInto<IntoIterator<T>>, IIntoIterator<T, IntoIterator<T>> where T: notnull { 18 19 #region Type-level Constructors 20 #endregion 21 22 #region Instance Constructors 23 public IntoIterator() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 24 internal IntoIterator(Maybe<T> maybe) => _maybe = maybe; 25 #endregion 26 27 #region Type-level Fields 28 #endregion 29 30 #region Instance Fields 31 Maybe<T> _maybe; 32 #endregion 33 34 #region Type-level Properties 35 #endregion 36 37 #region Instance Properties 38 #endregion 39 40 #region Type-level Functions 41 #endregion 42 43 #region Instance Functions 44 public Result<Unit, ulong> AdvanceBackBy(ulong n) => IDoubleEndedIterator<T>.AdvanceBackByDefault(ref this, n); 45 public Result<Unit, ulong> AdvanceBy(ulong n) => IIterator<T>.AdvanceByDefault(ref this, n); 46 public readonly ulong Count() => _maybe.IsSome ? 1ul : ulong.MinValue; 47 public TInit Fold<TInit>(TInit init, Fn<TInit, T, TInit> f) where TInit: notnull => IIterator<T>.FoldDefault(ref this, init, f); 48 public override readonly bool Equals(object? _) => false; 49 public override readonly int GetHashCode() => 0; 50 public readonly IntoIterator<T> Into() => this; 51 public readonly IntoIterator<T> IntoIter() => this; 52 public Maybe<T> Last() => IIterator<T>.LastDefault(ref this); 53 public Maybe<T> Next() { 54 55 var val = _maybe; 56 _maybe = Maybe<T>.None(); 57 return val; 58 } 59 public Maybe<T> NextBack() => Next(); 60 public TInit RFold<TInit>(TInit init, Fn<TInit, T, TInit> f) where TInit: notnull => IDoubleEndedIterator<T>.RFoldDefault(ref this, init, f); 61 public readonly Prod<ulong, Maybe<ulong>> SizeHint() => _maybe.IsSome ? new(1ul, new(1ul)) : new(ulong.MinValue, new(ulong.MinValue)); 62 public override readonly string ToString() => $"IntoIterator<{typeof(T).Name}>"; 63 public Result<TInit, TErr> TryFold<TInit, TErr>(TInit init, Fn<TInit, T, Result<TInit, TErr>> f) where TInit: notnull where TErr: notnull => IIterator<T>.TryFoldDefault(ref this, init, f); 64 public Result<TInit, TErr> TryRFold<TInit, TErr>(TInit init, Fn<TInit, T, Result<TInit, TErr>> f) where TInit: notnull where TErr: notnull => IDoubleEndedIterator<T>.TryRFoldDefault(ref this, init, f); 65 readonly Result<IntoIterator<T>, Bottom> ITryInto<IntoIterator<T>, Bottom>.TryInto() => new(this); 66 #endregion 67 68 #region Operators 69 #endregion 70 71 #region Types 72 #endregion 73 } 74 // Even though TOK and TErr can be non-readonly structs, we can still make Result readonly without any performance hit so long 75 // as we never access any members of _ok or _err. 76 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 0)] 77 public readonly struct Result<TOK, TErr>: ISum<TOK, TErr>, IInto<Result<TOK, TErr>>, IIntoIterator<TOK, IntoIterator<TOK>> where TOK: notnull where TErr: notnull { 78 79 #region Type-level Constructors 80 #endregion 81 82 #region Instance Constructors 83 public Result() => throw new InvalidOperationException("Parameterless constructor is not allowed to be called!"); 84 [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 public Result(TErr err) => (Var, _ok, _err) = (Tag.Err, default!, err); 86 [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 public Result(TOK ok) => (Var, _ok, _err) = (Tag.OK, ok, default!); 88 #endregion 89 90 #region Type-level Fields 91 #endregion 92 93 #region Instance Fields 94 // This is the "standard library"; so it is important that code is quick. 95 // Due to the pervasive usage of Results, we decide to expose these fields to the rest of the shared library. 96 // This allows code to avoid overhead from calling the monadic API or even functions like Unwrap. 97 // It is imperative that code manually verify IsOK or IsErr is true first though. 98 // KEEP THIS ORDER! It is very common for TErr to be one byte in size. By having it located after _ok, 99 // we increase the probability of having a more compact Result. 100 internal readonly TOK _ok; 101 internal readonly TErr _err; 102 public readonly Tag Var; 103 #endregion 104 105 #region Type-level Properties 106 #endregion 107 108 #region Instance Properties 109 public readonly bool IsErr => Var == Tag.Err; 110 public readonly bool IsOK => Var == Tag.OK; 111 public readonly Var2 Variant => Var == Tag.OK ? Var2.V0 : Var2.V1; 112 public readonly TOK Variant0 => Var == Tag.OK ? _ok : throw new InvalidOperationException($"The Result variant, {Var.ToString()} is not OK!"); 113 public readonly TErr Variant1 => Var == Tag.Err ? _err : throw new InvalidOperationException($"The Result variant, {Var.ToString()} is not Err!"); 114 #endregion 115 116 #region Type-level Functions 117 [MethodImpl(MethodImplOptions.AggressiveInlining)] 118 public static Result<TOK, TErr> Err(TErr val) => new(val); 119 [MethodImpl(MethodImplOptions.AggressiveInlining)] 120 public static Result<TOK, TErr> OK(TOK val) => new(val); 121 #endregion 122 123 #region Instance Functions 124 [MethodImpl(MethodImplOptions.AggressiveInlining)] 125 public readonly Result<T, TErr> And<T>(Result<T, TErr> val) where T: notnull => IsOK ? val : new(_err); 126 [MethodImpl(MethodImplOptions.AggressiveInlining)] 127 public readonly Result<T, TErr> AndThen<T>(Fn<TOK, Result<T, TErr>> f) where T: notnull => IsOK ? f(_ok) : new(_err); 128 public override readonly bool Equals(object? _) => false; 129 public readonly Maybe<TErr> Err() => IsOK ? Maybe<TErr>.None() : new(_err); 130 [MethodImpl(MethodImplOptions.AggressiveInlining)] 131 public readonly TOK Expect(string msg) => IsOK ? _ok : throw new InvalidOperationException($"{msg}: {_err.ToString()}"); 132 [MethodImpl(MethodImplOptions.AggressiveInlining)] 133 public readonly TErr ExpectErr(string msg) => !IsOK ? _err : throw new InvalidOperationException($"{msg}: {_ok.ToString()}"); 134 public override readonly int GetHashCode() => 0; 135 public readonly Result<TOK, TErr> Into() => this; 136 public readonly IntoIterator<TOK> IntoIter() => new(OK()); 137 [MethodImpl(MethodImplOptions.AggressiveInlining)] 138 public readonly Result<T, TErr> Map<T>(Fn<TOK, T> f) where T: notnull => IsOK ? new(f(_ok)) : new(_err); 139 [MethodImpl(MethodImplOptions.AggressiveInlining)] 140 public readonly Result<TOK, T> MapErr<T>(Fn<TErr, T> f) where T: notnull => IsOK ? new(_ok) : new(f(_err)); 141 [MethodImpl(MethodImplOptions.AggressiveInlining)] 142 public readonly T MapOr<T>(T Default, Fn<TOK, T> f) where T: notnull => IsOK ? f(_ok) : Default; 143 [MethodImpl(MethodImplOptions.AggressiveInlining)] 144 public readonly T MapOrElse<T>(Fn<TErr, T> Default, Fn<TOK, T> f) where T: notnull => IsOK ? f(_ok) : Default(_err); 145 public readonly Maybe<TOK> OK() => IsOK ? new(_ok) : Maybe<TOK>.None(); 146 [MethodImpl(MethodImplOptions.AggressiveInlining)] 147 public readonly Result<TOK, T> Or<T>(Result<TOK, T> val) where T: notnull => IsOK ? new(_ok) : val; 148 [MethodImpl(MethodImplOptions.AggressiveInlining)] 149 public readonly Result<TOK, T> OrElse<T>(Fn<TErr, Result<TOK, T>> f) where T: notnull => IsOK ? new(_ok) : f(_err); 150 public override readonly string ToString() => IsOK ? $"OK({_ok.ToString()})" : $"Err({_err.ToString()})"; 151 readonly Result<Result<TOK, TErr>, Bottom> ITryInto<Result<TOK, TErr>, Bottom>.TryInto() => new(this); 152 [MethodImpl(MethodImplOptions.AggressiveInlining)] 153 public readonly TOK Unwrap() => IsOK ? _ok : throw new InvalidOperationException($"Err({_err.ToString()})"); 154 [MethodImpl(MethodImplOptions.AggressiveInlining)] 155 public readonly TErr UnwrapErr() => !IsOK ? _err : throw new InvalidOperationException($"OK({_ok.ToString()})"); 156 [MethodImpl(MethodImplOptions.AggressiveInlining)] 157 public readonly TOK UnwrapOr(TOK Default) => IsOK ? _ok : Default; 158 [MethodImpl(MethodImplOptions.AggressiveInlining)] 159 public readonly TOK UnwrapOrElse(Fn<TErr, TOK> f) => IsOK ? _ok : f(_err); 160 #endregion 161 162 #region Operators 163 #endregion 164 165 #region Types 166 public enum Tag: byte { 167 Err = byte.MinValue, 168 OK = byte.MaxValue, 169 } 170 #endregion 171 } 172 public static class Functions { 173 174 #region Type-level Constructors 175 #endregion 176 177 #region Instance Constructors 178 #endregion 179 180 #region Type-level Fields 181 #endregion 182 183 #region Instance Fields 184 #endregion 185 186 #region Type-level Properties 187 #endregion 188 189 #region Instance Properties 190 #endregion 191 192 #region Type-level Functions 193 public static Result<TOK, TErr> Clamp<TOK, TErr>(this Result<TOK, TErr> self, Result<TOK, TErr> min, Result<TOK, TErr> max) where TOK: notnull, IOrd<TOK> where TErr: notnull, IOrd<TErr> { 194 195 System.Diagnostics.Trace.Assert(min.Cmp(in max).Var != Ord.Greater); 196 return self.Cmp(in min).Var switch { 197 Ord.Less => min, 198 Ord.Equivalent => self, 199 _ => self.Cmp(in max).Var == Ord.Greater ? max : self, 200 }; 201 } 202 public static Result<TOK, TErr> Clone<TOK, TErr>(in this Result<TOK, TErr> self) where TOK: notnull, IClone<TOK> where TErr: notnull, IClone<TErr> => self.IsOK ? new(self._ok.Clone()) : new(self._err.Clone()); 203 public static Ordering Cmp<TOK, TErr>(in this Result<TOK, TErr> self, in Result<TOK, TErr> other) where TOK: notnull, IOrd<TOK> where TErr: notnull, IOrd<TErr> => (self.IsOK, other.IsOK) switch { 204 (true, true) => self._ok.Cmp(in other._ok), 205 (true, false) => Greater, 206 (false, true) => Less, 207 _ => self._err.Cmp(in other._err), 208 }; 209 public static int CompareToComparable<TOK, TErr>(in this Result<TOK, TErr> self, in Result<TOK, TErr> other) where TOK: notnull, IComparable<TOK> where TErr: notnull, IComparable<TErr> => (self.IsOK, other.IsOK) switch { 210 (true, true) => self._ok.CompareTo(other._ok), 211 (true, false) => 1, 212 (false, true) => -1, 213 _ => self._err.CompareTo(other._err), 214 }; 215 public static bool Contains<TOK, TErr, T>(in this Result<TOK, TErr> self, in T val) where TErr: notnull where TOK: notnull, IPartialEq<TOK, T> where T: notnull, IPartialEq<T, TOK> => self.IsOK && self._ok == val; 216 public static bool ContainsErr<TOK, TErr, T>(in this Result<TOK, TErr> self, in T val) where TOK: notnull where TErr: notnull, IPartialEq<TErr, T> where T: notnull, IPartialEq<T, TErr> => self.IsErr && self._err == val; 217 public static bool Eq<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialEq<TOK, TOK2> where TErr: notnull, IPartialEq<TErr, TErr2> where TOK2: notnull, IPartialEq<TOK2, TOK> where TErr2: notnull, IPartialEq<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 218 (true, true) => self._ok == other._ok, 219 (false, false) => self._err == other._err, 220 _ => false, 221 }; 222 public static bool EqualsEquality<TOK, TErr>(in this Result<TOK, TErr> self, in Result<TOK, TErr> other) where TOK: notnull, IEquality<TOK> where TErr: notnull, IEquality<TErr> => (self.IsOK, other.IsOK) switch { 223 (true, true) => self._ok.Equals(in other._ok), 224 (false, false) => self._err.Equals(in other._err), 225 _ => false, 226 }; 227 public static bool EqualsEquatable<TOK, TErr>(in this Result<TOK, TErr> self, in Result<TOK, TErr> other) where TOK: notnull, IEquatable<TOK> where TErr: notnull, IEquatable<TErr> => (self.IsOK, other.IsOK) switch { 228 (true, true) => self._ok.Equals(other._ok), 229 (false, false) => self._err.Equals(other._err), 230 _ => false, 231 }; 232 public static Result<TOK, TErr> Flatten<TOK, TErr>(this Result<Result<TOK, TErr>, TErr> self) where TOK: notnull where TErr: notnull => self.AndThen(Convert.Functions.Identity); 233 public static Result<TOK, TErr> FromIter<TOK, TErr, T, TIter>(TIter iter) where T: notnull where TIter: notnull, IIterator<Result<T, TErr>> where TErr: notnull where TOK: notnull, IFromIterator<TOK, T> { 234 235 var mbe = Maybe<TErr>.None(); 236 var v = TOK.FromIter(iter.Scan(new Unit(), (ref Unit _, ref Result<T, TErr> item) => { 237 238 if (item.IsErr) { 239 mbe = new(item.UnwrapErr()); 240 return Maybe<T>.None(); 241 } else { 242 return Maybe<T>.Some(item.Unwrap()); 243 } 244 })); 245 return mbe.IsSome ? new Result<TOK, TErr>(mbe.Unwrap()) : new Result<TOK, TErr>(v); 246 } 247 public static bool Ge<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialOrd<TOK, TOK2> where TErr: notnull, IPartialOrd<TErr, TErr2> where TOK2: notnull, IPartialOrd<TOK2, TOK> where TErr2: notnull, IPartialOrd<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 248 (true, true) => self._ok >= other._ok, 249 (true, false) => true, 250 (false, true) => false, 251 _ => self._err >= other._err, 252 }; 253 public static int GetHashCodeEquatable<TOK, TErr>(in this Result<TOK, TErr> self) where TOK: notnull, IEquatable<TOK> where TErr: notnull, IEquatable<TErr> => self.IsOK ? HashCode.Combine(0, self._ok) : HashCode.Combine(1, self._err); 254 public static bool Gt<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialOrd<TOK, TOK2> where TErr: notnull, IPartialOrd<TErr, TErr2> where TOK2: notnull, IPartialOrd<TOK2, TOK> where TErr2: notnull, IPartialOrd<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 255 (true, true) => self._ok > other._ok, 256 (true, false) => true, 257 (false, true) => false, 258 _ => self._err > other._err, 259 }; 260 public static Unit Hash<TOK, TErr, THasher>(in this Result<TOK, TErr> self, ref THasher hasher) where TOK: notnull, IHashable where TErr: notnull, IHashable where THasher: notnull, IHasher { 261 262 if (self.IsOK) { 263 _ = hasher.WriteByte(byte.MinValue); 264 return self._ok.Hash(ref hasher); 265 } else { 266 _ = hasher.WriteByte(byte.MaxValue); 267 return self._err.Hash(ref hasher); 268 } 269 } 270 public static string IntoString<TOK, TErr>(in this Result<TOK, TErr> self) where TOK: notnull, IInto<string> where TErr: notnull, IInto<string> => self.IsOK ? $"OK({self._ok.Into()})" : $"Err({self._err.Into()})"; 271 public static bool Le<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialOrd<TOK, TOK2> where TErr: notnull, IPartialOrd<TErr, TErr2> where TOK2: notnull, IPartialOrd<TOK2, TOK> where TErr2: notnull, IPartialOrd<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 272 (true, true) => self._ok <= other._ok, 273 (true, false) => false, 274 (false, true) => true, 275 _ => self._err <= other._err, 276 }; 277 public static bool Lt<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialOrd<TOK, TOK2> where TErr: notnull, IPartialOrd<TErr, TErr2> where TOK2: notnull, IPartialOrd<TOK2, TOK> where TErr2: notnull, IPartialOrd<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 278 (true, true) => self._ok < other._ok, 279 (true, false) => false, 280 (false, true) => true, 281 _ => self._err < other._err, 282 }; 283 public static bool Ne<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialEq<TOK, TOK2> where TErr: notnull, IPartialEq<TErr, TErr2> where TOK2: notnull, IPartialEq<TOK2, TOK> where TErr2: notnull, IPartialEq<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 284 (true, true) => self._ok != other._ok, 285 (false, false) => self._err != other._err, 286 _ => true, 287 }; 288 public static Maybe<Ordering> PartialCmp<TOK, TErr, TOK2, TErr2>(in this Result<TOK, TErr> self, in Result<TOK2, TErr2> other) where TOK: notnull, IPartialOrd<TOK, TOK2> where TErr: notnull, IPartialOrd<TErr, TErr2> where TOK2: notnull, IPartialOrd<TOK2, TOK> where TErr2: notnull, IPartialOrd<TErr2, TErr> => (self.IsOK, other.IsOK) switch { 289 (true, true) => self._ok.PartialCmp(in other._ok), 290 (true, false) => Some(Greater), 291 (false, true) => Some(Less), 292 _ => self._err.PartialCmp(in other._err), 293 }; 294 public static Maybe<Result<TSome, TErr>> Transpose<TSome, TErr>(this Result<Maybe<TSome>, TErr> self) where TSome: notnull where TErr: notnull => self.IsOK ? self._ok.IsSome ? new(new(self._ok._some)) : Maybe<Result<TSome, TErr>>.None() : new(new(self._err)); 295 public static Result<string, TErr2> TryIntoString<TOK, TErr, TErr2>(in this Result<TOK, TErr> self) where TOK: notnull, ITryInto<string, TErr2> where TErr: notnull, ITryInto<string, TErr2> where TErr2: notnull { 296 297 if (self.IsOK) { 298 var val = self._ok.TryInto(); 299 return val.IsErr ? val : new($"OK({val._ok})"); 300 } else { 301 var val = self._err.TryInto(); 302 return val.IsErr ? val : new($"Err({val._ok})"); 303 } 304 } 305 public static TOK UnwrapOrDefault<TOK, TErr>(this Result<TOK, TErr> self) where TOK: notnull, new() where TErr: notnull => self.Var == Result<TOK, TErr>.Tag.OK ? self._ok : new TOK(); 306 #endregion 307 308 #region Instance Functions 309 #endregion 310 311 #region Operators 312 #endregion 313 314 #region Types 315 #endregion 316 } 317 #endregion 318 319 #region Namespaces 320 #endregion 321 } 322 #endregion