1 module either;
2 
3 import std.traits;
4 import std.conv;
5 
6 version(unittest) {
7   import fluent.asserts;
8 }
9 
10 ///
11 enum EitherSide {
12   Left, Right
13 }
14 
15 ///
16 bool isCallableWith(func, T)() {
17   static if(!isCallable!func) {
18     return false;
19   } else static if(Parameters!func.length != 1) {
20     return false;
21   } else static if(!is(Parameters!func[0] == T)) {
22     return false;
23   } else {
24     return true;
25   }
26 }
27 
28 ///
29 bool canCheck(alias Matcher, ParameterType)() {
30   static if(!isCallable!Matcher) {
31     return false;
32   } else static if(Parameters!Matcher.length != 1) {
33     return false;
34   } else static if(!is(Parameters!Matcher[0] == ParameterType)) {
35     return false;
36   } else static if(!is(ReturnType!Matcher == bool)) {
37     return false;
38   } else {
39     return true;
40   }
41 }
42 
43 ///
44 auto checkEither(alias check, This: Either!(Left, Right), Left, Right)(This either) {
45   static if(canCheck!(check, Left)) {
46     if(either.isLeft && check(either.left)) {
47       return true;
48     }
49   }
50 
51   static if(canCheck!(check, Right)) {
52     if(either.isRight && check(either.right)) {
53       return true;
54     }
55   }
56 
57   return false;
58 }
59 
60 ///
61 auto callWith(MapFunction, This: Either!(Left, Right), Left, Right)(This either, MapFunction mapFunction) if(isCallable!MapFunction) {
62   static if(Parameters!MapFunction.length == 0) {
63     return mapFunction();
64   }
65 
66   static if(Parameters!MapFunction.length == 1 && isCallableWith!(MapFunction, Left)) {
67     if(either.isLeft) {
68       return mapFunction(either.left);
69     } else {
70       assert(0, "Got a right value. The mapFunction can't be called.");
71     }
72   }
73 
74   static if(Parameters!MapFunction.length == 1 && isCallableWith!(MapFunction, Right)) {
75     if(either.isRight) {
76       return mapFunction(either.right);
77     } else {
78       assert(0, "Got a left value. The mapFunction can't be called.");
79     }
80   }
81 
82   static if(Parameters!MapFunction.length > 1) {
83     static assert(false, "The map function must get none or 1 argument.");
84   }
85 }
86 
87 ///
88 auto callIfCan(MapFunction, This: Either!(Left, Right), Left, Right)(This either, MapFunction mapFunction) if(isCallable!MapFunction) {
89   This result = either;
90 
91   static if(Parameters!MapFunction.length == 0) {
92     static if(hasVoidReturn!MapFunction) {
93       mapFunction();
94     } else {
95       result = mapFunction().bind!This;
96     }
97   }
98 
99   static if(Parameters!MapFunction.length == 1 && isCallableWith!(MapFunction, Left)) {
100     if(either.isLeft) {
101       static if(hasVoidReturn!MapFunction) {
102         mapFunction(either.left);
103       } else {
104         result = mapFunction(either.left).bind!This;
105       }
106     }
107   }
108 
109   static if(Parameters!MapFunction.length == 1 && isCallableWith!(MapFunction, Right)) {
110     if(either.isRight) {
111       static if(hasVoidReturn!MapFunction) {
112         mapFunction(either.right);
113       } else {
114         result = mapFunction(either.right).bind!This;
115       }
116     }
117   }
118 
119   static if(Parameters!MapFunction.length > 1) {
120     static assert(false, "The map function must get none or 1 argument.");
121   }
122 
123   return result;
124 }
125 
126 /// Returns true if the given struct can be use as an Either struct
127 template isEitherStruct(T) if(isAggregateType!T && hasMember!(T, "left") && hasMember!(T, "right")) {
128   enum isEitherStruct = true;
129 }
130 
131 /// ditto
132 template isEitherStruct(T) if(isBuiltinType!T || !isAggregateType!(T) || !hasMember!(T, "left") || !hasMember!(T, "right")) {
133   enum isEitherStruct = false;
134 }
135 
136 /// Returns true if the function returns void
137 template hasVoidReturn(Func) {
138   static if (is(ReturnType!Func == void)) {
139     enum hasVoidReturn = true;
140   } else {
141     enum hasVoidReturn = false;
142   }
143 }
144 
145 /// Check if the matcher returns any of the provided return types
146 bool returnsAnyOf(Matcher, ReturnTypes...)() {
147   static if(ReturnTypes.length == 0) {
148     return false;
149   } else static if(is(ReturnType!Matcher == ReturnTypes[0])) {
150     return true;
151   } else {
152     return returnsAnyOf!(Matcher, ReturnTypes[1..$]);
153   }
154 }
155 
156 ///
157 template EitherTypeOf(T, Left, Right) {
158   static if(isEitherStruct!T) {
159     alias EitherTypeOf = T;
160   } else static if(isCallable!T) {
161     alias EitherTypeOf = EitherTypeOf!(ReturnType!T, Left, Right);
162   } else static if(is(T == Left)) {
163     alias EitherTypeOf = Either!(T, Any);
164   } else {
165     alias EitherTypeOf = Either!(Any, T);
166   }
167 }
168 
169 template PickDefinedType(A, B) {
170   static if(is(A == B) || is(B == Any)) {
171     alias PickDefinedType = A;
172   } else static if(is(A == Any)) {
173     alias PickDefinedType = B;
174   } else {
175     static assert(false, A.stringof ~ " is not compatible with " ~ B.stringof);
176   }
177 }
178 
179 ///
180 template NewLeft(This: Either!(Left, Right), That, Left, Right) {
181   alias EitherThisLeft = Left;
182   alias EitherThatLeft = typeof(EitherTypeOf!(That, Left, Right).left);
183 
184   alias NewLeft = PickDefinedType!(EitherThisLeft, EitherThatLeft);
185 }
186 
187 ///
188 template NewRight(This: Either!(Left, Right), That, Left, Right) {
189   alias EitherThisRight = Right;
190   alias EitherThatRight = typeof(EitherTypeOf!(That, Left, Right).right);
191 
192   alias NewRight = PickDefinedType!(EitherThisRight, EitherThatRight);
193 }
194 
195 ///
196 private auto resolve(T)(T newValue) {
197   static if(isCallable!newValue) {
198     return newValue();
199   } else {
200     return newValue;
201   }
202 }
203 
204 ///
205 struct Any { }
206 
207 /// The Either type represents values with two possibilities:
208 ///      a value of type Either a b is either Left a or Right b.
209 ///
210 /// The Either type is sometimes used to represent a value which is either correct or an error;
211 /// by convention, the Left constructor is used to hold an error value and the Right constructor
212 /// is used to hold a correct value (mnemonic: "right" also means "correct").
213 struct Either(Left, Right) if(!is(Left == Right)) {
214   /// The type of this Either struct
215   alias This = Either!(Left, Right);
216 
217   private {
218     Left left;
219     Right right;
220     EitherSide side;
221   }
222 
223   /// Initialise the struct with the Left type
224   this(Left value) {
225     left = value;
226     side = EitherSide.Left;
227   }
228 
229   /// Initialise the struct with the Right type
230   this(Right value) {
231     right = value;
232     side = EitherSide.Right;
233   }
234 }
235 
236 /// returns true when the Left type is stored
237 bool isLeft(T : Either!(Left, Right), Left, Right)(T either) {
238   return either.side == EitherSide.Left;
239 }
240 
241 /// isLeft is true when the struct is setup with the left type
242 unittest {
243   auto either = Either!(int, bool)(1);
244 
245   either.isLeft.should.equal(true);
246 }
247 
248 /// isLeft is false when the struct is setup with the right type
249 unittest {
250   auto either = Either!(int, bool)(true);
251 
252   either.isLeft.should.equal(false);
253 }
254 
255 /// returns true when the Right type is stored
256 bool isRight(T : Either!(Left, Right), Left, Right)(T either) {
257   return either.side == EitherSide.Right;
258 }
259 
260 /// isRight is false when the struct is setup with the left type
261 unittest {
262   auto either = Either!(int, bool)(1);
263 
264   either.isRight.should.equal(false);
265 }
266 
267 /// isRight is true when the struct is setup with the right type
268 unittest {
269   auto either = Either!(int, bool)(true);
270 
271   either.isRight.should.equal(true);
272 }
273 
274 version(unittest) {
275   alias TestEither = Either!(int, Exception);
276 }
277 
278 version(unittest) {
279   bool alwaysTrue(bool) {
280     return true;
281   }
282 
283   bool alwaysTrueInt(int) {
284     return true;
285   }
286 
287   bool alwaysFalse(bool) {
288     return false;
289   }
290 
291   bool alwaysFalseInt(int) {
292     return false;
293   }
294 }
295 
296 /// Wraps a value as an Either monad
297 Either!(Any, T) bind(T)(T value) if(!isEitherStruct!T) {
298   return Either!(Any, T)(value);
299 }
300 
301 /// ditto
302 Either!(Left, Right) bind(Left, Right, T)(T value) if(!isEitherStruct!Left && !isEitherStruct!Right && !isEitherStruct!T) {
303   return Either!(Left, Right)(value);
304 }
305 
306 /// ditto
307 auto bind(E, T)(T value) if(isEitherStruct!E && !isEitherStruct!T) {
308   return E(value);
309 }
310 
311 /// ditto
312 auto bind(T)(T value) if(isEitherStruct!T) {
313   return value;
314 }
315 
316 /// ditto
317 void bind(T)() if(isEitherStruct!T) {
318   static assert(false, "You can't bind void.");
319 }
320 
321 /// ditto
322 Either!(PickDefinedType!(L, Left), PickDefinedType!(R, Right))
323 bind(Left, Right, This: Either!(L, R), L, R)(This value)
324 if(!isEitherStruct!Left && !isEitherStruct!Right && isEitherStruct!This) {
325   alias NewL = PickDefinedType!(L, Left);
326   alias NewR = PickDefinedType!(R, Right);
327 
328   alias LocalEither = Either!(NewL, NewR);
329 
330   static if(!is(NewL == Any) && !is(L == Any)) {
331     if(value.isLeft) {
332       return LocalEither(value.left);
333     }
334   }
335 
336   static if(!is(NewR == Any) && !is(R == Any)) {
337     if(value.isRight) {
338       return LocalEither(value.right);
339     }
340   }
341 
342   assert(false, "Can't bind value `" ~ value.to!string ~ "` of type `" ~ This.stringof ~ "` to `" ~ LocalEither.stringof ~ "` .");
343 }
344 
345 /// returns a right hand Either when the value is an int
346 unittest {
347   auto result = bind(5);
348 
349   result.isLeft.should.equal(false);
350   result.isRight.should.equal(true);
351 }
352 
353 // Type matchers
354 
355 /// Match Left or Right values using types
356 This when(Matcher, This: Either!(Left, Right), Left, Right)(This either, Matcher matcher) if(isCallable!Matcher) {
357   return either.callIfCan(matcher);
358 }
359 
360 /// 'when' is called with the left value function when the monad isLeft is true
361 unittest {
362   auto either = Either!(int, bool)(1);
363   string result = "none";
364 
365   either
366     .when((int value) {
367       result = "left";
368     })
369     .when((bool value) {
370       result = "right";
371     });
372 
373   result.should.equal("left");
374 }
375 
376 /// it calls the 'when' with right value function when the monad isRight is true
377 unittest {
378   auto either = Either!(int, bool)(true);
379   string result = "none";
380 
381   either
382     .when((int value) {
383       result = "left";
384     })
385     .when((bool value) {
386       result = "right";
387     });
388 
389   result.should.equal("right");
390 }
391 
392 /// it does not call the 'when' function when the types don't match
393 unittest {
394   auto either = Either!(int, bool)(true);
395   string result = "none";
396 
397   either
398     .when((double value) {
399       result = "double";
400     })
401     .when((string value) {
402       result = "string";
403     });
404 
405   result.should.equal("none");
406 }
407 
408 /// it returns the binded value when the 'when' function returns
409 unittest {
410   auto either = Either!(int, bool)(1);
411   bool message;
412 
413   auto result = either
414     .when((int value) {
415       return true;
416     })
417     .when((bool value) {
418       message = value;
419     });
420 
421   result.isRight.should.equal(true);
422   message.should.equal(true);
423 }
424 
425 
426 // Value matchers
427 
428 /// Match Left or Right values by value examples
429 auto when(alias value, T, This: Either!(Left, Right), Left, Right)(This either, T newValue) if(!isCallable!value) {
430   auto result = newValue.resolve;
431   alias ResultType = typeof(result);
432 
433   alias NewL = NewLeft!(This, ResultType);
434   alias NewR = NewRight!(This, ResultType);
435   alias ValueType = typeof(value);
436 
437   static if(is(ValueType == Right)) {
438     if(either.isRight && value == either.right) {
439       return result.bind!(NewL, NewR);
440     }
441 
442     return either.right.bind!(NewL, NewR);
443   }
444 
445   static if(is(ValueType == Left)) {
446     if(either.isLeft && value == either.left) {
447       return result.bind!(NewL, NewR);
448     }
449 
450     return either.left.bind!(NewL, NewR);
451   }
452 }
453 
454 /// it is called when the Either value matches the when!Left value
455 unittest {
456   auto either = Either!(int, bool)(1);
457   bool message;
458 
459   auto result = either
460     .when!1 ({
461       return true;
462     })
463     .when((bool value) {
464       message = value;
465     });
466 
467   result.isRight.should.equal(true);
468   message.should.equal(true);
469 }
470 
471 /// it is not called when the value matches the Left
472 unittest {
473   auto either = Either!(int, bool)(1);
474   bool message;
475 
476   auto result = either
477     .when!11 ({
478       return true;
479     })
480     .when((bool value) {
481       message = value;
482     });
483 
484   result.isLeft.should.equal(true);
485   message.should.equal(false);
486 }
487 
488 /// it is called when the value matches the Right
489 unittest {
490   auto either = Either!(int, bool)(true);
491   int message;
492 
493   auto result = either
494     .when!true ({
495       return 2;
496     })
497     .when((int value) {
498       message = value;
499     });
500 
501   result.isLeft.should.equal(true);
502   message.should.equal(2);
503 }
504 
505 /// it does not call the 'when' function when the value matches the Left
506 unittest {
507   auto either = Either!(int, bool)(true);
508   bool message;
509 
510   auto result = either
511     .when!false ({
512       return 3;
513     })
514     .when((bool value) {
515       message = value;
516     });
517 
518   result.isRight.should.equal(true);
519   message.should.equal(true);
520 }
521 
522 /// it returns the 'when' value when the value matches the Right
523 unittest {
524   auto either = Either!(int, bool)(true);
525   int message;
526 
527   auto result = either
528     .when!true (2)
529     .when((int value) {
530       message = value;
531     });
532 
533   result.isLeft.should.equal(true);
534   message.should.equal(2);
535 }
536 
537 /// it does not return the 'when' value when the value is not matched
538 unittest {
539   auto either = Either!(int, bool)(2);
540   bool message;
541 
542   auto result = either
543     .when!3 (true)
544     .when((bool value) {
545       message = value;
546     });
547 
548   result.isLeft.should.equal(true);
549   message.should.equal(false);
550 }
551 
552 /// it does not return the 'when' value when the value is not matched
553 unittest {
554   auto either = Either!(int, bool)(true);
555   bool message;
556 
557   auto result = either
558     .when!false (3)
559     .when((bool value) {
560       message = value;
561     });
562 
563   result.isRight.should.equal(true);
564   message.should.equal(true);
565 }
566 
567 /// it returns the 'when' value when the value matches the Left
568 unittest {
569   auto either = Either!(int, bool)(1);
570   bool message;
571 
572   auto result = either
573     .when!1 (true)
574     .when((bool value) {
575       message = value;
576     });
577 
578   result.isRight.should.equal(true);
579   message.should.equal(true);
580 }
581 
582 /// it returns the new Either type
583 unittest {
584   auto either = 5.bind;
585 
586   expect(typeof(either).stringof).to.equal("Either!(Any, int)");
587 
588   auto result = either.when!(5) ("The value is 5!".bind!(string, int));
589 
590   expect(typeof(result).stringof).to.equal("Either!(string, int)");
591   expect(result.left).to.equal("The value is 5!");
592 }
593 
594 /// returning a left value with any
595 unittest {
596   auto result = 0.bind.when!(0) ("got zero!".bindLeft);
597 
598   result.isLeft.should.equal(true);
599   result.left.should.equal("got zero!");
600 }
601 
602 /// Match Left or Right values using a check function
603 Either!(NewLeft!(This, T), NewRight!(This, T)) when(alias check, T, This: Either!(Left, Right), Left, Right)(This either, T result) if(isCallable!check) {
604   alias NewL = NewLeft!(This, T);
605   alias NewR = NewRight!(This, T);
606 
607   if(either.checkEither!(check)) {
608     static if(isCallable!result) {
609       return either.callWith(result).bind!(NewL, NewR);
610     } else {
611       return result.bind!(NewL, NewR);
612     }
613   }
614 
615   return either.bind!(NewL, NewR);
616 }
617 
618 /// it calls the 'when' function when the function check returns true for Right value
619 unittest {
620   auto either = Either!(int, bool)(true);
621   int message;
622 
623   auto result = either
624     .when!alwaysTrue ({
625       return 2;
626     })
627     .when((int value) {
628       message = value;
629     });
630 
631   result.isLeft.should.equal(true);
632   message.should.equal(2);
633 }
634 
635 /// it calls the 'when' with the value function when the function check returns true for Right value
636 unittest {
637   auto either = Either!(int, bool)(true);
638   int message;
639 
640   auto result = either
641     .when!alwaysTrue ((bool value) {
642       return 2;
643     })
644     .when((int value) {
645       message = value;
646     });
647 
648   result.isLeft.should.equal(true);
649   message.should.equal(2);
650 }
651 
652 /// it does not call the 'when' function when the function check returns false for Right value
653 unittest {
654   auto either = Either!(int, bool)(true);
655   bool message;
656 
657   auto result = either
658     .when!alwaysFalse ({
659       return 2;
660     })
661     .when((bool value) {
662       message = value;
663     });
664 
665   result.isRight.should.equal(true);
666   message.should.equal(true);
667 }
668 
669 /// it calls the 'when' function when the function check returns true for Left value
670 unittest {
671   auto either = Either!(int, bool)(8);
672   int message;
673 
674   auto result = either
675     .when!alwaysTrueInt ({
676       return 2;
677     })
678     .when((int value) {
679       message = value;
680     });
681 
682   result.isLeft.should.equal(true);
683   message.should.equal(2);
684 }
685 
686 ///
687 auto bindLeft(T)(T value) {
688   return Either!(T, Any)(value);
689 }