1 /**
2  *          __                        __                  __     __
3  *         / /                       /_/ _____            \ \   /_/
4  *   _____/ / _____     __   ___    __  / ___ \  _____    / /  __  __  ___  _____
5  *  / ___  / / ___ \    \ \_/__/   / / / _____/ / ___ \  / /  / / / /_/  / / ___ \
6  * / /__/ / / /__/  \   / _/      / / / /__/ / / _____/ / /  / / / _/ / / / /__/ /
7  * \_____/  \_____/\_\ /_/  __   / /  \_____/ / /__/ /  \_\ /_/ /_/  /_/ _\___  /
8  *                         / /__/ /           \_____/                   / /__/ /
9  *                         \_____/                                      \_____/
10  *
11  * A module for optional values
12  *
13  * Copyright: (C) Kazuhiro Matsushima 2016.
14  * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
15  * Authors:   Kazuhiro Matsushima
16  */
17 module darjeeling.maybe;
18 
19 import std.format : format;
20 import std.traits : fullyQualifiedName;
21 import darjeeling.assertion;
22 
23 /**
24  * A interface to represent an optional value.
25  *
26  * Example:
27  * -----
28  * auto maybe = Maybe!int.just(42);
29  * if (maybe.isJust)
30  * {
31  *     writeln(maybe.fromJust());
32  * }
33  * -----
34  * Output:
35  * -----
36  * 42
37  * -----
38  */
39 interface Maybe(T)
40 {
41     /**
42      * Return true if $(D this) is the Just!T instance, otherwise false.
43      */
44     @property bool isJust() nothrow pure @safe;
45 
46     /**
47      * Return true if $(D this) is the Nothing!T instance, otherwise false.
48      */
49     @property bool isNothing() nothrow pure @safe;
50 
51     /**
52      * Return $(D value) if $(D this) is the Just!T instance, otherwise throw Exception.
53      */
54     T fromJust() pure @safe;
55 
56     /**
57      * A factory method to create Just(T) instance.
58      *
59      * Params:
60      *  value = a T value to contain
61      *
62      * Returns:
63      *  A Just!T instance which has $(D_PARAM value).
64      */
65     static Maybe!T just(T value) nothrow pure @safe
66     {
67         return new Just!T(value);
68     }
69 
70     /**
71      * A factory method to create Nothing(T) instance.
72      *
73      * Returns:
74      *  A Nothing!T instance.
75      */
76     static Maybe!T nothing() nothrow pure @safe
77     {
78         return new Nothing!T();
79     }
80 }
81 
82 final class Just(T) : Maybe!T
83 {
84     private T value;
85 
86     package this(T value) nothrow pure @safe
87     {
88         this.value = value;
89     }
90 
91     @property bool isJust() nothrow pure @safe
92     {
93         return true;
94     }
95 
96     unittest
97     {
98         auto just = new Just!int(2);
99         assert(just.isJust);
100     }
101 
102     @property bool isNothing() nothrow pure @safe
103     {
104         return !this.isJust;
105     }
106 
107     unittest
108     {
109         auto just = new Just!string("hoge");
110         assert(!just.isNothing);
111     }
112 
113     T fromJust() nothrow pure @safe
114     {
115         return this.value;
116     }
117 
118     unittest
119     {
120         auto expected = 3;
121         auto just = new Just!int(expected);
122         auto actual = just.fromJust();
123         assertEquals(expected, actual);
124     }
125 
126     override string toString()
127     {
128         return format("Just!%s(%s)", fullyQualifiedName!T, this.value);
129     }
130 
131     override bool opEquals(Object o)
132     {
133         if (typeid(o) == typeid(this))
134         {
135             auto other = cast(Just!T)(o);
136             return this.value == other.value;
137         }
138         else
139         {
140             return false;
141         }
142     }
143 }
144 
145 final class Nothing(T) : Maybe!T
146 {
147     package this() nothrow pure @safe {}
148 
149     @property bool isJust() nothrow pure @safe
150     {
151         return !this.isNothing();
152     }
153 
154     unittest
155     {
156         auto nothing = new Nothing!string();
157         assert(!nothing.isJust);
158     }
159 
160     @property bool isNothing() nothrow pure @safe
161     {
162         return true;
163     }
164 
165     unittest
166     {
167         auto nothing = new Nothing!int();
168         assert(nothing.isNothing);
169     }
170 
171     T fromJust() pure @safe
172     {
173         throw new Exception("Invalid operation: Nothing(T) cannot return its value.");
174     }
175 
176     unittest
177     {
178         auto nothing = new Nothing!string();
179         auto ex = trap!(Nothing!string, string)(nothing, function(Nothing!string n) => n.fromJust());
180         auto expected = "Invalid operation: Nothing(T) cannot return its value.";
181         assertEquals(expected, ex.msg);
182     }
183 
184     override string toString()
185     {
186         return format("Nothing!%s", fullyQualifiedName!T);
187     }
188 
189     override bool opEquals(Object o)
190     {
191         return (typeid(o) == typeid(this));
192     }
193 }
194 
195 unittest
196 {
197     // Just(T)
198     {
199         auto expected = 2;
200         Maybe!int just = Maybe!int.just(expected);
201         assert(just.isJust);
202         assert(!just.isNothing);
203         auto actual = just.fromJust();
204         assertEquals(expected, actual);
205     }
206     // Nothing(T)
207     {
208         Maybe!string nothing = Maybe!string.nothing();
209         assert(!nothing.isJust);
210         assert(nothing.isNothing);
211     }
212     // Equality
213     {
214         {
215             auto maybe1 = Maybe!int.just(1);
216             auto maybe2 = Maybe!int.just(1);
217             assertEquals(maybe1, maybe2);
218         }
219         {
220             auto maybe1 = Maybe!string.just("hoge");
221             auto maybe2 = Maybe!string.just("fuga");
222             assertNotEquals(maybe1, maybe2);
223         }
224         {
225             auto maybe1 = Maybe!short.just(1);
226             auto maybe2 = Maybe!long.just(1);
227             assertNotObjEquals(maybe1, maybe2);
228         }
229         {
230             auto maybe1 = Maybe!int.just(1);
231             auto maybe2 = Maybe!int.nothing();
232             assertNotEquals(maybe1, maybe2);
233         }
234         {
235             auto maybe1 = Maybe!string.nothing();
236             auto maybe2 = Maybe!int.nothing();
237             assertNotObjEquals(maybe1, maybe2);
238         }
239         {
240             auto maybe1 = Maybe!double.nothing();
241             auto maybe2 = Maybe!double.nothing();
242             assertEquals(maybe1, maybe2);
243         }
244     }
245 }