1 /**
2  *          __                        __                  __     __
3  *         / /                       /_/ _____            \ \   /_/
4  *   _____/ / _____     __   ___    __  / ___ \  _____    / /  __  __  ___  _____
5  *  / ___  / / ___ \    \ \_/__/   / / / _____/ / ___ \  / /  / / / /_/  / / ___ \
6  * / /__/ / / /__/  \   / _/      / / / /__/ / / _____/ / /  / / / _/ / / / /__/ /
7  * \_____/  \_____/\_\ /_/  __   / /  \_____/ / /__/ /  \_\ /_/ /_/  /_/ _\___  /
8  *                         / /__/ /           \_____/                   / /__/ /
9  *                         \_____/                                      \_____/
10  *
11  * A module for values with two possiblities
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.either;
18 
19 import std.format : format;
20 import std.traits : fullyQualifiedName;
21 import darjeeling.maybe : Maybe;
22 import darjeeling.assertion;
23 
24 /**
25  * A interface to represent a two possible value.
26  *
27  * Example:
28  * -----
29  * auto either = Either!(string, int).right(33-4);
30  * if (either.isRight)
31  * {
32  *     writeln(either.right());
33  * }
34  * -----
35  * Output:
36  * -----
37  * 29
38  * -----
39  */
40 interface Either(TLeft, TRight)
41 {
42     /**
43      * Return true if $(D this) is the Right!(TLeft, TRight) instance, otherwise false.
44      */
45     @property bool isRight() nothrow pure @safe;
46 
47     /**
48      * Return true if $(D this) is the Left!(TLeft, TRight) instance, otherwise false.
49      */
50     @property bool isLeft() nothrow pure @safe;
51 
52     /**
53      * Return TLeft value if $(D this) is the Left!(TLeft, TRight) instance, otherwise throw Exception.
54      */
55     TLeft left() pure @safe;
56 
57     /**
58      * Return TRight value if $(D this) is the Right!(TLeft, TRight) instance, otherwise throw Exception.
59      */
60     TRight right() pure @safe;
61 
62     /**
63      * Return Just!TLeft value if $(D this) is the Left!(TLeft, TRight) instance, otherwise Nothing!TLeft value.
64      */
65     Maybe!TLeft tryLeft() nothrow pure @safe;
66 
67     /**
68      * Return Just!TRight value if $(D this) is the Right!(TLeft, TRight) instance, otherwise Nothing!TRight value.
69      */
70     Maybe!TRight tryRight() nothrow pure @safe;
71 
72     /**
73      * A factory method to create Right(TLeft, TRight) instance.
74      *
75      * Params:
76      *  value = a TRight value to contain
77      *
78      * Returns:
79      *  A Right!(TLeft, TRight) instance which has $(D_PARAM value).
80      */
81     static Either!(TLeft, TRight) right(TRight value) nothrow pure @safe
82     {
83         return new Right!(TLeft, TRight)(value);
84     }
85 
86     /**
87      * A factory method to create Left(TLeft, TRight) instance.
88      *
89      * Params:
90      *  value = a TLeft value to contain
91      *
92      * Returns:
93      *  A Left!(TLeft, TRight) instance which has $(D_PARAM value).
94      */
95     static Either!(TLeft, TRight) left(TLeft value) nothrow pure @safe
96     {
97         return new Left!(TLeft, TRight)(value);
98     }
99 }
100 
101 final class Left(TLeft, TRight) : Either!(TLeft, TRight)
102 {
103     TLeft value;
104 
105     package this(TLeft value) nothrow pure @safe
106     {
107         this.value = value;
108     }
109 
110     @property bool isRight() nothrow pure @safe
111     {
112         return !this.isLeft;
113     }
114 
115     unittest
116     {
117         auto left = new Left!(string, int)("hoge");
118         assert(!left.isRight);
119     }
120 
121     @property bool isLeft() nothrow pure @safe
122     {
123         return true;
124     }
125 
126     unittest
127     {
128         auto left = new Left!(Exception, string)(new Exception("fuga"));
129         assert(left.isLeft);
130     }
131 
132     TLeft left() pure @safe
133     {
134         return this.value;
135     }
136 
137     unittest
138     {
139         auto expected = "hoge";
140         auto left = new Left!(string, int)(expected);
141         auto actual = left.left();
142         assertEquals(expected, actual);
143     }
144 
145     TRight right() pure @safe
146     {
147         throw new Exception("Invalid operation: Left(TLeft, TRight) cannot return its right value.");
148     }
149 
150     unittest
151     {
152         auto left = new Left!(Exception, string)(new Exception("bar"));
153         auto ex = trap!(Left!(Exception, string), string)(left, function(Left!(Exception, string) l) => l.right());
154         auto expected = "Invalid operation: Left(TLeft, TRight) cannot return its right value.";
155         assertEquals(expected, ex.msg);
156     }
157 
158     Maybe!TLeft tryLeft() nothrow pure @safe
159     {
160         return Maybe!TLeft.just(this.value);
161     }
162 
163     unittest
164     {
165         auto expected = "left";
166         auto left = new Left!(string, int)(expected);
167         auto actual = left.tryLeft();
168         assert(actual.isJust);
169         assertEquals(expected, actual.fromJust());
170     }
171 
172     Maybe!TRight tryRight() nothrow pure @safe
173     {
174         return Maybe!TRight.nothing();
175     }
176 
177     unittest
178     {
179         auto left = new Left!(Exception, string)(new Exception("LeFT"));
180         auto actual = left.tryRight();
181         assert(!actual.isJust);
182     }
183 
184     override string toString()
185     {
186         return format("Left!(%s, %s)(%s)", fullyQualifiedName!TLeft, fullyQualifiedName!TRight, this.value);
187     }
188 
189     override bool opEquals(Object o)
190     {
191         if (typeid(o) == typeid(this))
192         {
193             auto other = cast(Left!(TLeft, TRight))(o);
194             return this.value == other.value;
195         }
196         else
197         {
198             return false;
199         }
200     }
201 }
202 
203 final class Right(TLeft, TRight) : Either!(TLeft, TRight)
204 {
205     TRight value;
206 
207     package this(TRight value) nothrow pure @safe
208     {
209         this.value = value;
210     }
211 
212     @property bool isRight() nothrow pure @safe
213     {
214         return true;
215     }
216 
217     unittest
218     {
219         auto right = new Right!(string, int)(334);
220         assert(right.isRight);
221     }
222 
223     @property bool isLeft() nothrow pure @safe
224     {
225         return !this.isRight;
226     }
227 
228     unittest
229     {
230         auto right = new Right!(Exception, string)("right");
231         assert(!right.isLeft);
232     }
233 
234     TLeft left() pure @safe
235     {
236         throw new Exception("Invalid operation: Right(TLeft, TRight) cannot return its left value.");
237     }
238 
239     unittest
240     {
241         auto right = new Right!(string, int)(42);
242         auto ex = trap!(Right!(string, int), string)(right, function(r) => r.left());
243         auto expected = "Invalid operation: Right(TLeft, TRight) cannot return its left value.";
244         assertEquals(expected, ex.msg);
245     }
246 
247     TRight right() pure @safe
248     {
249         return this.value;
250     }
251 
252     unittest
253     {
254         auto expected = "That's right";
255         auto right = new Right!(Exception, string)(expected);
256         auto actual = right.right();
257         assertEquals(expected, actual);
258     }
259 
260     Maybe!TLeft tryLeft() nothrow pure @safe
261     {
262         return Maybe!TLeft.nothing();
263     }
264 
265     unittest
266     {
267         auto right = new Right!(string, int)(-10);
268         auto actual = right.tryLeft();
269         assert(!actual.isJust);
270     }
271 
272     Maybe!TRight tryRight() nothrow pure @safe
273     {
274         return Maybe!TRight.just(this.value);
275     }
276 
277     unittest
278     {
279         auto expected = "right";
280         auto right = new Right!(Exception, string)(expected);
281         auto actual = right.tryRight();
282         assert(actual.isJust);
283         assertEquals(expected, actual.fromJust());
284     }
285 
286     override string toString()
287     {
288         return format("Right!(%s, %s)(%s)", fullyQualifiedName!TLeft, fullyQualifiedName!TRight, this.value);
289     }
290 
291     override bool opEquals(Object o)
292     {
293         if (typeid(o) == typeid(this))
294         {
295             auto other = cast(Right!(TLeft, TRight))(o);
296             return this.value == other.value;
297         }
298         else
299         {
300             return false;
301         }
302     }
303 }
304 
305 unittest
306 {
307     // Left(TLeft, TRight)
308     {
309         auto expected = "hoge";
310         Either!(string, int) either = Either!(string, int).left(expected);
311         assert(either.isLeft);
312         assert(!either.isRight);
313         auto left = either.tryLeft();
314         assert(left.isJust);
315         assertEquals(expected, left.fromJust());
316     }
317     // Right(TLeft, TRight)
318     {
319         auto expected = 334;
320         Either!(string, int) either = Either!(string, int).right(expected);
321         assert(!either.isLeft);
322         assert(either.isRight);
323         auto right = either.tryRight();
324         assert(right.isJust);
325         assertEquals(expected, right.fromJust());
326     }
327     // Equality
328     {
329         {
330             auto either1 = Either!(string, int).left("hoge");
331             auto either2 = Either!(string, int).left("hoge");
332             assertEquals(either1, either2);
333         }
334         {
335             auto either1 = Either!(string, int).left("Left");
336             auto either2 = Either!(string, int).left("left");
337             assertNotEquals(either1, either2);
338         }
339         {
340             auto either1 = Either!(string, int).left("fuga");
341             auto either2 = Either!(Exception, string).left(new Exception("fuga"));
342             assertNotObjEquals(either1, either2);
343         }
344         {
345             auto either1 = Either!(string, int).right(42);
346             auto either2 = Either!(string, int).right(42);
347             assertEquals(either1, either2);
348         }
349         {
350             auto either1 = Either!(string, int).right(33);
351             auto either2 = Either!(string, int).right(4);
352             assertNotEquals(either1, either2);
353         }
354         {
355             auto either1 = Either!(string, int).right(10);
356             auto either2 = Either!(Exception, int).right(10);
357             assertNotObjEquals(either1, either2);
358         }
359         {
360             auto either1 = Either!(string, int).right(10);
361             auto either2 = Either!(string, int).left("ten");
362             assertNotObjEquals(either1, either2);
363         }
364         {
365             auto either1 = Either!(string, int).left("test");
366             auto either2 = Either!(Exception, string).right("test");
367             assertNotObjEquals(either1, either2);
368         }
369     }
370 }