1 module universal.extras.maybe;
2 
3 import universal.meta;
4 import universal.core.coproduct;
5 import universal.core.product;
6 import universal.core.apply;
7 import std.traits;
8 import std.typetuple;
9 
10 /*
11 	a type which may or may not be inhabited
12 */
13 struct Maybe(A)
14 {
15   mixin UnionInstance!(
16     q{nothing},
17     q{just}, A,
18   );
19 }
20 alias Maybe(A : void) = Maybe!(Unit);
21 
22 Maybe!A just(A)(A a) { return Maybe!A().just(a); }
23 Maybe!A nothing(A)() { return Maybe!A().nothing; }
24 
25 auto isNothing(A)(Maybe!A ma) { return ma.isCase!q{nothing}; }
26 auto isJust   (A)(Maybe!A ma) { return ma.isCase!q{just};    }
27 
28 /*
29 	apply f to a Maybe if it is inhabited, otherwise return a default value.
30 	If f returns void, unit is returned regardless
31 */
32 template maybe(alias f) 
33 {
34   template maybe(A, B = typeof(A.init.apply!f))
35 	{
36 		B maybe(Maybe!A m, B b = B.init)
37 		{
38 			return m.visit!(
39 				q{just},    (a,_) => a.apply!f,
40 				q{nothing}, (b) => b,
41 			)(b);
42 		}
43 	}
44 }
45 
46 /*
47 	fmap
48 */
49 template maybeMap(alias f)
50 {
51 	template maybeMap(A)
52 	{
53 		alias B = typeof(A.init.apply!f);
54 
55 		Maybe!B maybeMap(Maybe!A m)
56 		{
57 			if(m.isJust)
58 				return just(f(m.just));
59 			else
60 				return nothing!B;
61 		}
62 	}
63 }
64 
65 /*
66 	like visit, but not all cases need to be accounted for. Requires named fields. If one of the supplied function matches the inhabited union, it returns wrapped in "just". If none match, it returns "nothing".
67 */
68 template maybeVisit(dtors...)
69 {
70   auto maybeVisit(U)(U u)
71   if(is(U.Union))
72   {
73     alias dtorNames = Filter!(isString, dtors);
74     alias dtorFuncs = Filter!(isParameterized, dtors);
75 
76     import std.algorithm.searching : countUntil;
77 
78     enum ctorIdx(string name) = [U.Union.ctorNames].countUntil!(n => n == name);
79     enum dtorIdx(string name) = [dtorNames].countUntil!(n => n == name);
80 
81     alias DtorCod(string name)
82     = typeof(dtorFuncs[dtorIdx!name](U.Union.Args!(ctorIdx!name).init));
83 
84     alias B = Universal!(CommonType!(staticMap!(DtorCod, dtorNames)));
85 
86     template doVisit(string name)
87     {
88       enum i = dtorIdx!name;
89       enum j = ctorIdx!name;
90 
91       static if(i > -1)
92         auto doVisit(U.Union.Args!j a)
93         { return a.apply!(dtorFuncs[i]).apply!(just!B); }
94       else
95         auto doVisit(U.Union.Args!j)   
96         { return nothing!B; }
97 
98     }
99 
100     return u.visit!(staticMap!(doVisit, U.Union.ctorNames));
101   }
102 }
103 
104 @("EXAMPLES") unittest
105 {
106 	Union!("a", int, "b", int) u;
107 	u.b = 3;
108 
109 	auto m = u.maybeVisit!(q{a}, a => a);
110 	assert(m.isNothing);
111 
112 	m = u.maybeVisit!(q{b}, b => b);
113 	assert(m == just(3));
114 }