fx-sharp  0.1
A collection of functional extensions for C#.
 All Classes Namespaces Files Functions Variables Enumerator
Either.cs
Go to the documentation of this file.
1 using System;
2 using JetBrains.Annotations;
3 
4 namespace FxSharp
5 {
6  public static class Either
7  {
8  internal const string DefaultCtorError = "Don't use the default constructor!";
9 
10  /// <summary>
11  /// Construct an Either instance from a success val.
12  /// </summary>
13  /// <typeparam name="TRight">The type of the success val.</typeparam>
14  /// <typeparam name="TLeft">The type of the error val.</typeparam>
15  /// <param name="value">The val to wrap.</param>
16  /// <returns>The val wrapped in an Either instance.</returns>
17  public static Either<TLeft, TRight> Right<TLeft, TRight>(TRight value)
18  {
19  return new Either<TLeft, TRight>(value);
20  }
21 
22  /// <summary>
23  /// Construct an Either instance from an error val.
24  /// </summary>
25  /// <typeparam name="TRight">The type of the success val.</typeparam>
26  /// <typeparam name="TLeft">The type of the error val.</typeparam>
27  /// <param name="error">The error to wrap.</param>
28  /// <returns>The error wrapped in an Either instance.</returns>
29  public static Either<TLeft, TRight> Left<TLeft, TRight>(TLeft error)
30  {
31  return new Either<TLeft, TRight>(error);
32  }
33  }
34 
35  /// <summary>
36  /// An Either wraps the result of a computation that can fail or the resulting error val.
37  /// The subtype 'Right' represents a successful result wrapping the val whereas the
38  /// 'Left' subtype represents a failed computation with the resulting error val.
39  /// </summary>
40  /// <typeparam name="TLeft">Type of the error val.</typeparam>
41  /// <typeparam name="TRight">Type of the successfully computed val.</typeparam>
42  public struct Either<TLeft, TRight>
43  {
44  /// <summary>
45  /// The possibly present error.
46  /// </summary>
47  private readonly TLeft _error;
48 
49  /// <summary>
50  /// The internal state. NoValue represents an invalid state.
51  /// </summary>
52  private readonly EitherState _state;
53 
54  /// <summary>
55  /// The possibly present val.
56  /// </summary>
57  private readonly TRight _val;
58 
59  /// <summary>
60  /// Right ctor.
61  /// </summary>
62  /// <param name="val">The success val.</param>
63  internal Either(TRight val)
64  {
65  _state = EitherState.IsRight;
66  _val = val;
67  _error = default(TLeft);
68  }
69 
70  /// <summary>
71  /// Left ctor.
72  /// </summary>
73  /// <param name="error">The error val.</param>
74  internal Either(TLeft error)
75  {
76  _state = EitherState.IsLeft;
77  _val = default(TRight);
78  _error = error;
79  }
80 
81  /// <summary>
82  /// Get the stored val or the given default instead.
83  /// </summary>
84  /// <param name="other">The default val to return in case no val is stored.</param>
85  /// <returns>Stored or given default val.</returns>
86  [Pure]
87  public TRight GetOrElse(TRight other)
88  {
89  switch (_state)
90  {
91  case EitherState.IsRight:
92  return _val;
93  case EitherState.IsLeft:
94  return other;
95  default:
96  throw new InvalidOperationException(Either.DefaultCtorError);
97  }
98  }
99 
100  /// <summary>
101  /// Apply the given function to the possibly present val and wrap it again.
102  /// Return the wrapped error val otherwise.
103  /// </summary>
104  /// <typeparam name="TResult">The result type of fn.</typeparam>
105  /// <param name="fn">The function to apply to the val.</param>
106  /// <returns>A new Either instance.</returns>
107  [Pure]
108  public Either<TLeft, TResult> Select<TResult>([NotNull] Func<TRight, TResult> fn)
109  {
110  switch (_state)
111  {
112  case EitherState.IsRight:
113  return new Either<TLeft, TResult>(fn(_val));
114  case EitherState.IsLeft:
115  return new Either<TLeft, TResult>(_error);
116  default:
117  throw new InvalidOperationException(Either.DefaultCtorError);
118  }
119  }
120 
121  /// <summary>
122  /// Apply the given function to the possibly present val and throw away the result.
123  /// Otherwise, don't do anything.
124  /// </summary>
125  /// <param name="fn">The function to apply to the val.</param>
126  /// <returns>This.</returns>
127  [Pure]
128  public Either<TLeft, TRight> Select_([NotNull] Action<TRight> fn)
129  {
130  switch (_state)
131  {
132  case EitherState.IsRight:
133  fn(_val);
134  return this;
135  case EitherState.IsLeft:
136  return this;
137  default:
138  throw new InvalidOperationException(Either.DefaultCtorError);
139  }
140  }
141 
142  [Pure]
143  public Either<TLeft, TResSuccess> SelectMany<TResSuccess>(
144  [NotNull] Func<TRight, Either<TLeft, TResSuccess>> fn)
145  {
146  switch (_state)
147  {
148  case EitherState.IsRight:
149  return fn(_val);
150  case EitherState.IsLeft:
151  return new Either<TLeft, TResSuccess>(_error);
152  default:
153  throw new InvalidOperationException(Either.DefaultCtorError);
154  }
155  }
156 
157  [Pure]
158  public Either<TLeft, TResSuccess> SelectMany<TInner, TResSuccess>(
159  [NotNull] Func<TRight, Either<TLeft, TInner>> firstFn,
160  [NotNull] Func<TRight, TInner, TResSuccess> secondFn)
161  {
162  return SelectMany(x => firstFn(x).SelectMany(y =>
163  new Either<TLeft, TResSuccess>(secondFn(x, y))));
164  }
165 
166  /// <summary>
167  /// Apply the function success to the val if present. Otherwise, apply the function
168  /// failure to the error val.
169  /// </summary>
170  /// <typeparam name="TResult">The result of success and failure.</typeparam>
171  /// <param name="right">The function to apply to error.</param>
172  /// <param name="left">The function to apply to val.</param>
173  /// <returns>The val returned by error failure or success.</returns>
174  [Pure]
175  public TResult Match<TResult>(
176  [NotNull] Func<TRight, TResult> right,
177  [NotNull] Func<TLeft, TResult> left)
178  {
179  switch (_state)
180  {
181  case EitherState.IsRight:
182  return right(_val);
183  case EitherState.IsLeft:
184  return left(_error);
185  default:
186  throw new InvalidOperationException(Either.DefaultCtorError);
187  }
188  }
189 
190  /// <summary>
191  /// Apply the function success to the val if present. Otherwise, apply the function
192  /// failure to the error val. Ignore the results of both.
193  /// </summary>
194  /// <param name="left">The function to apply to error.</param>
195  /// <param name="right">The function to apply to val.</param>
196  /// <returns>This.</returns>
197  [Pure]
199  [NotNull] Action<TRight> right,
200  [NotNull] Action<TLeft> left)
201  {
202  switch (_state)
203  {
204  case EitherState.IsRight:
205  right(_val);
206  return this;
207  case EitherState.IsLeft:
208  left(_error);
209  return this;
210  default:
211  throw new InvalidOperationException(Either.DefaultCtorError);
212  }
213  }
214 
215  [Pure]
216  public override string ToString()
217  {
218  switch (_state)
219  {
220  case EitherState.IsRight:
221  return string.Format("Right {0}", _val);
222  case EitherState.IsLeft:
223  return string.Format("Left {0}", _error);
224  default:
225  return "Invalid Either";
226  }
227  }
228 
229  /// <summary>
230  /// Convert either to Maybe.
231  /// </summary>
232  /// <returns>Maybe.Nothing(T) when Left.</returns>
233  [Pure]
234  public Maybe<TRight> ToMaybe()
235  {
236  return Match(
237  left: _ => Maybe.Nothing<TRight>(),
238  right: Maybe.Just);
239  }
240 
241  /// <summary>
242  /// The possible internal states.
243  /// </summary>
244  private enum EitherState
245  {
246  // Ugluk: "Get back, scum! The prisoners go to Saruman. Alive and unspoiled."
247  // Grishnakh: "Alive? Why alive? Do they give good sport?"
248  // Ugluk: "They have something. An Elvish weapon. The master wants it for the war."
249  // Ugluk: "We need this unused member as well. The master needs it for correct execution."
250  // (Ok, I made up the last line. We nevertheless need it for the switch-case-statements
251  // to fall into the default case as well as to detect the use of the default
252  // constructor which makes no sense in this context).
253  // ReSharper disable once UnusedMember.Local
254  IsUninitialized,
255  IsRight,
256  IsLeft
257  }
258  }
259 }
Either< TLeft, TRight > Select_([NotNull] Action< TRight > fn)
Apply the given function to the possibly present val and throw away the result. Otherwise, don't do anything.
Definition: Either.cs:128
Either< TLeft, TRight > Match_([NotNull] Action< TRight > right, [NotNull] Action< TLeft > left)
Apply the function success to the val if present. Otherwise, apply the function failure to the error ...
Definition: Either.cs:198
Property</c ></item > *< item >< c > value
override string ToString()
Definition: Either.cs:216
TRight GetOrElse(TRight other)
Get the stored val or the given default instead.
Definition: Either.cs:87
Maybe< TRight > ToMaybe()
Convert either to Maybe.
Definition: Either.cs:234
An Either wraps the result of a computation that can fail or the resulting error val. The subtype 'Right' represents a successful result wrapping the val whereas the 'Left' subtype represents a failed computation with the resulting error val.
Definition: Either.cs:42