1 /*
2  * The MIT License
3  *
4  * Copyright (c) 2014 Shunsuke Kirino
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 module option;
26 
27 import std.algorithm : find, reduce;
28 import std.functional : unaryFun;
29 import std.conv : to;
30 import std.traits : isCallable, isPointer, Unqual;
31 import std.range : isInputRange, ElementType, ElementEncodingType, empty, front;
32 import std.exception : enforce, assertThrown, assertNotThrown;
33 
34 template isOptionType(T) {
35   static if(is(T U == Option!U))
36     enum isOptionType = true;
37   else
38     enum isOptionType = false;
39 }
40 
41 private template isNullableType(T) {
42   enum isNullableType = is(T == class) || is(T == interface) || isPointer!T;
43 }
44 
45 struct Option(T)
46 {
47 private:
48   T _Option_value;// Avoid name conflict by prefixing
49 
50 public:
51   alias T OptionValueType;
52 
53   // constructor
54   static if(isNullableType!T) {
55   public:
56     this      (inout(T) t) inout { _Option_value = t; }
57     this(U: T)(inout(U) u) inout { _Option_value = u; }
58   } else {
59   private:
60     this      (inout(T) t, bool b) inout { _Option_value = t; _isDefined = b; }
61     this(U: T)(inout(U) u, bool b) inout { _Option_value = u; _isDefined = b; }
62   public:
63     this      (inout(T) u) inout { _Option_value = u; _isDefined = true; }
64     this(U: T)(inout(U) u) inout { _Option_value = u; _isDefined = true; }
65   }
66 
67   // isDefined
68   static if(isNullableType!T) {
69   public:
70     @property pure nothrow bool isDefined() const { return _Option_value !is null; }
71   } else {
72   private:
73     bool _isDefined;
74   public:
75     @property pure nothrow bool isDefined() const { return _isDefined; }
76   }
77   @property pure nothrow bool isEmpty() const { return !isDefined(); }
78 
79   @property pure ref inout(T) get() inout {
80     enforce(isDefined, "No such element: None!(" ~ T.stringof ~ ").get");
81     return _Option_value;
82   }
83 
84   pure nothrow const(T) getOrElse(U: T)(const(U) other) const {
85     return isDefined ? _Option_value : other;
86   }
87   pure nothrow T getOrElse(U: T)(U other) {
88     return isDefined ? _Option_value : other;
89   }
90   pure nothrow const(Option!T) orElse(U: T)(const(Option!U) other) const {
91     return isDefined ? this : other;
92   }
93   pure nothrow Option!T orElse(U: T)(Option!U other) {
94     return isDefined ? this : other;
95   }
96 
97   pure nothrow inout(T)[] array() inout {
98     return isDefined ? [_Option_value] : [];
99   }
100   static pure Option!T fromRange(R)(R r) if(isInputRange!R && is(ElementType!(R): T)) {
101     return r.empty ? None!T() : Some!T(r.front);
102   }
103 
104   string toString() const { // toString must be "const" in order to avoid strange linker error
105     if(isDefined) {
106       static if(__traits(compiles, to!string(_Option_value)))
107         return "Some!(" ~ T.stringof ~ ")(" ~ to!string(_Option_value) ~ ')';
108       else
109         return "Some!(" ~ T.stringof ~ ")(" ~ (cast(Unqual!T)_Option_value).toString ~ ')'; // Avoid error due to non-const toString()
110     } else {
111       return "None!(" ~ T.stringof ~ ")()";
112     }
113   }
114 
115   auto opDispatch(string fn, Args...)(lazy Args args) {
116     static if(args.length == 0) {
117       alias F = typeof(mixin("_Option_value." ~ fn));
118       static if(isCallable!F)
119         enum MethodCall = "_Option_value." ~ fn ~ "()";
120       else // property access
121         enum MethodCall = "_Option_value." ~ fn;
122     } else {
123       enum MethodCall = "_Option_value." ~ fn ~ "(args)";
124     }
125     alias R = typeof(mixin(MethodCall));
126     static if(is(R == void)) {
127       if(isDefined) mixin(MethodCall ~ ';');
128     } else {
129       return isDefined ? Some!R(mixin(MethodCall)) : None!R();
130     }
131   }
132 
133   bool opEquals(U: T)(const(Option!U) rhs) const {
134     if( isDefined &&  rhs.isDefined) return _Option_value == rhs._Option_value;
135     if(!isDefined && !rhs.isDefined) return true;
136     return false;
137   }
138 
139   // foreach support
140   int opApply(int delegate(ref const(T)) operation) const {
141     if(isDefined)
142       return operation(_Option_value);
143     else
144       return 0;
145   }
146 }
147 
148 pure Option!T Some(T)(T t)
149 {
150   return Some!(T, T)(t);
151 }
152 pure Option!T Some(T, U = T)(U t) if(is(T: U))
153 {
154   static if(isNullableType!T) {
155     enforce(t, "Value must not be null!");
156     return Option!T(t);
157   } else {
158     return Option!T(t, true);
159   }
160 }
161 pure nothrow Option!T None(T)()
162 {
163   static if(isNullableType!T)
164     return Option!T(null);
165   else
166     return Option!T(T.init, false);
167 }
168 
169 // `flatten` is defined as a top-level function in order to provide bettern type inference (to my best knowledge).
170 pure nothrow Option!T flatten(T)(Option!(Option!T) o)
171 {
172   return o.isDefined ? o._Option_value : None!T();
173 }
174 
175 // `filter`, `map` and `flatMap` is defined as a top-level function to avoid error:
176 // "cannot use local 'fun' as parameter to non-global template"
177 pure nothrow Option!T filter(alias pred, T)(Option!T o) if(is(typeof(unaryFun!pred(o.get)) : bool))
178 {
179   if(o.isDefined && unaryFun!pred(o._Option_value))
180     return o;
181   else
182     return None!T();
183 }
184 auto map(alias fun, T)(Option!T o) if(is(typeof(unaryFun!fun(o.get))))
185 {
186   alias R = typeof(unaryFun!fun(o.get));
187   static if(is(R == void)) {
188     if(o.isDefined) unaryFun!fun(o._Option_value);
189   } else {
190     return o.isDefined ? Some!R(unaryFun!fun(o._Option_value)) : None!R();
191   }
192 }
193 auto flatMap(alias fun, T)(Option!T o) if(isOptionType!(typeof(unaryFun!fun(o.get))))
194 {
195   return map!fun(o).flatten;
196 }
197 
198 // range helper
199 Option!(ElementEncodingType!R) detect(alias pred, R)(R range) if(isInputRange!R)
200 {
201   return Option!(ElementEncodingType!R).fromRange(find!(pred, R)(range));
202 }
203 pure ElementEncodingType!(R).OptionValueType[] flatten(R)(R range) if(isInputRange!R && isOptionType!(ElementEncodingType!R))
204 {
205   ElementEncodingType!(R).OptionValueType[] a0 = [];
206   return reduce!((a, b) => b.isDefined ? a ~ b.get : a)(a0, range);
207 }
208 
209 // associative array helper
210 //   Define each function overloads for mutable/const/immutable to workaround
211 //   an issue about inout (https://issues.dlang.org/show_bug.cgi?id=9983).
212 pure nothrow Option!V fetch(K, V)(V[K] aa, const K key)
213 {
214   auto ptr = key in aa;
215   return (ptr == null) ? None!V() : Some(*ptr);
216 }
217 pure nothrow Option!(const V) fetch(K, V)(const V[K] aa, const K key)
218 {
219   auto ptr = key in aa;
220   return (ptr == null) ? None!(const V)() : Some(*ptr);
221 }
222 pure nothrow Option!(immutable V) fetch(K, V)(immutable V[K] aa, const K key)
223 {
224   auto ptr = key in aa;
225   return (ptr == null) ? None!(immutable V)() : Some(*ptr);
226 }
227 
228 
229 unittest {
230   class C
231   {
232     int _i = 5;
233     void method1()             { _i = 10; }
234     int  method2(int x, int y) { return _i + x + y; }
235     override string toString() const { return "C#toString"; }
236   }
237   class D1: C {}
238   class D2: C {}
239 
240   // construction helpers
241   auto s0 = Some(3);
242   auto s1 = Some("hoge");
243   auto s2 = Some(new C);
244   auto n0 = None!int();
245   auto n1 = None!string();
246   auto n2 = None!C();
247   assert( s0.isDefined);
248   assert( s1.isDefined);
249   assert( s2.isDefined);
250   assert(!n0.isDefined);
251   assert(!n1.isDefined);
252   assert(!n2.isDefined);
253   assertThrown!Exception(Some!(int*)(null));
254   assertThrown!Exception(Some!C(null));
255 
256   {// Option constructor with value type, pointer, array/AA, class (Note that empty arrays are treated as null)
257     int integer = 0;
258     int[string] aaEmpty, aaNonEmpty;
259     aaNonEmpty["abc"] = 10;
260     assert( Option!(int        )(19                    ).isDefined);
261     assert(!Option!(int*       )(null                  ).isDefined);
262     assert( Option!(int*       )(&integer              ).isDefined);
263     assert( Option!(string     )(null                  ).isDefined);
264     assert( Option!(string     )(""                    ).isDefined);
265     assert( Option!(string     )("hoge"                ).isDefined);
266     assert( Option!(int[string])(cast(int[string]) null).isDefined);
267     assert( Option!(int[string])(aaEmpty               ).isDefined);
268     assert( Option!(int[string])(aaNonEmpty            ).isDefined);
269     assert( Option!(C          )(s2.get                ).isDefined);
270     assert(!Option!(C          )(null                  ).isDefined);
271     assert(Option!(string     )(                  null) == Some(""));
272     assert(Option!(int[string])(cast(int[string]) null) == Some(aaEmpty));
273 
274     assert(Option!(const     int)(0).isDefined);
275     assert(Option!(immutable int)(0).isDefined);
276     assert(Option!(const C)(              new C).isDefined);
277     assert(Option!(const C)(cast(const C) new C).isDefined);
278 
279     assert(Some!(const     int)(0).isDefined);
280     assert(Some!(immutable int)(0).isDefined);
281     assert(Some!(const C)(              new C).isDefined);
282     assert(Some!(const C)(cast(const C) new C).isDefined);
283 
284     assert(!None!(const     int)().isDefined);
285     assert(!None!(immutable int)().isDefined);
286     assert(!None!(const C)().isDefined);
287   }
288 
289   {// get, getOrElse, orElse
290     assert(s0.get    == 3);
291     assert(s1.get    == "hoge");
292     assert(s2.get._i == 5);
293     assertThrown!Exception(n0.get);
294     assertThrown!Exception(n1.get);
295     assertThrown!Exception(n2.get);
296 
297     assert(s0.getOrElse(7)        == s0.get);
298     assert(s0.getOrElse(true)     == s0.get);
299     assert(s1.getOrElse("fuga")   == s1.get);
300     assert(s2.getOrElse(new C)    is s2.get);
301     assert(n0.getOrElse(7)        == 7);
302     assert(n1.getOrElse("fuga")   == "fuga");
303     assert(n2.getOrElse(new C)._i == 5);
304 
305     assert(s0.orElse(None!int)      == s0);
306     assert(s1.orElse(Some("fuga"))  == s1);
307     assert(s2.orElse(None!C())      == s2);
308     assert(n0.orElse(Some(10))      == Some(10));
309     assert(n1.orElse(None!string()) == None!string());
310     assert(n2.orElse(s2)            == s2);
311 
312     assert(Some!(const     int)(1).get == 1);
313     assert(Some!(immutable int)(1).get == 1);
314     assert(Some!(const     int)(1).getOrElse(0) == 1);
315     assert(Some!(immutable int)(1).getOrElse(0) == 1);
316     assert(Some!(const     int)(1).orElse(Some!(const     int)(0)) == Some(1));
317     assert(Some!(immutable int)(1).orElse(Some!(immutable int)(0)) == Some(1));
318   }
319 
320   {// array conversion
321     assert(s0.array == [s0.get]);
322     assert(s1.array == [s1.get]);
323     assert(s2.array == [s2.get]);
324     assert(n0.array == []);
325     assert(n1.array == []);
326     assert(n2.array == []);
327     assert(s0 == Option!(int   ).fromRange(s0.array));
328     assert(s1 == Option!(string).fromRange(s1.array));
329     assert(s2 == Option!(C     ).fromRange(s2.array));
330     assert(n0 == Option!(int   ).fromRange(n0.array));
331     assert(n1 == Option!(string).fromRange(n1.array));
332     assert(n2 == Option!(C     ).fromRange(n2.array));
333 
334     assert(Option!(const     int).fromRange(Some!(const     int)(1).array) == Some(1));
335     assert(Option!(immutable int).fromRange(Some!(immutable int)(1).array) == Some(1));
336   }
337 
338   {// toString
339     assert(s0.toString == "Some!(int)(3)");
340     assert(s1.toString == "Some!(string)(hoge)");
341     assert(s2.toString == "Some!(C)(C#toString)");
342     assert(n0.toString == "None!(int)()");
343     assert(n1.toString == "None!(string)()");
344     assert(n2.toString == "None!(C)()");
345 
346     class X {}
347     static assert(__traits(compiles, Some(new X).toString));
348     struct Y {}
349     static assert(__traits(compiles, Some(Y()).toString));
350 
351     assert(Some!(const     int)(1).toString == "Some!(const(int))(1)");
352     assert(Some!(immutable int)(1).toString == "Some!(immutable(int))(1)");
353   }
354 
355   {// access T's members
356     assert(s1.length == Some!size_t(4));
357     s2.method1();
358     assert(s2.get._i        == 10);
359     assert(s2._i            == Some(10));
360     assert(s2.method2(1, 2) == Some(13));
361     assert(n1.length        == None!size_t());
362     assertNotThrown(n2.method1());
363     assert(n2.method2(1, 2) == None!int());
364 
365     assert(Some!(const     string)("hello").length == Some(5));
366     assert(Some!(immutable string)("hello").length == Some(5));
367   }
368 
369   {// flatten
370     assert(Some(Some(1))      .flatten == Some(1));
371     assert(Some(None!int())   .flatten == None!int());
372     assert(None!(Option!int)().flatten == None!int());
373 
374     assert(Some(Some!(const     int)(1)).flatten == Some(1));
375     assert(Some(Some!(immutable int)(1)).flatten == Some(1));
376   }
377 
378   {// filter
379     assert(Some(3)   .filter!(x => x % 2 == 1) == Some(3));
380     assert(Some(3)   .filter!("a % 2 == 0")    == None!int());
381     assert(None!int().filter!(x => x % 2 == 1) == None!int());
382     assert(None!int().filter!("a % 2 == 0")    == None!int());
383 
384     assert(Some!(const     int)(3).filter!(x => x % 2 == 1) == Some(3));
385     assert(Some!(immutable int)(3).filter!(x => x % 2 == 1) == Some(3));
386   }
387 
388   {// map
389     int sum = 0;
390     void voidFun(int i) { sum += 1; }
391     Some(1).map!voidFun();
392     assert(sum == 1);
393     None!int().map!voidFun();
394     assert(sum == 1);
395 
396     static int intFun(int x) { return x + 1; }
397     assert(Some(1).map!intFun()    == Some(2));
398     assert(None!int().map!intFun() == None!int());
399 
400     assert(Some(1)   .map!((int x) => x + 1) == Some(2));
401     assert(None!int().map!((int x) => x + 1) == None!int());
402     assert(Some(1)   .map!"a + 1"() == Some(2));
403     assert(None!int().map!"a + 1"() == None!int());
404 
405     // function passed to `map` should not return null
406     C nullFun(int i) { return null; }
407     assertThrown(Some(1).map!(nullFun));
408 
409     assert(Some!(const     int)(1).map!((int x) => x + 1) == Some(2));
410     assert(Some!(immutable int)(1).map!((int x) => x + 1) == Some(2));
411   }
412 
413   {// flatMap
414     Option!int fun(int i) { return Some(i+1); }
415     assert(Some(1)   .flatMap!fun() == Some(2));
416     assert(None!int().flatMap!fun() == None!int());
417 
418     assert(Some!(const     int)(1).flatMap!fun() == Some(2));
419     assert(Some!(immutable int)(1).flatMap!fun() == Some(2));
420   }
421 
422   {// method chaining
423     Option!int f1(int i) { return i % 2 == 0 ? Some(i / 2) : None!int(); }
424     Option!int f2(int i) { return i % 3 == 0 ? Some(i / 3) : None!int(); }
425     auto x = 10;
426     assert(Some(12).flatMap!f1.flatMap!f2.map!(i => to!string(i * x)).getOrElse("None") == "20"); // => "20"
427   }
428 
429   {// detect element from array
430     auto array1 = [1, 2, 3, 4, 5];
431     bool pred1(int i) { return i == 1; }
432     assert(array1.detect!pred1        == Some(1));
433     assert(array1.detect!"a % 2 == 0" == Some(2));
434     assert(array1.detect!(i => i > 7) == None!int());
435     const array2 = array1;
436     assert(array2.detect!pred1        == Some(1));
437     assert(array2.detect!"a % 2 == 0" == Some(2));
438     assert(array2.detect!(i => i > 7) == None!int());
439     immutable array3 = array1.idup;
440     assert(array3.detect!pred1        == Some(1));
441     assert(array3.detect!"a % 2 == 0" == Some(2));
442     assert(array3.detect!(i => i > 7) == None!int());
443   }
444 
445   {// flatten array of Option's
446     assert([None!int()]                  .flatten == []);
447     assert([Some(1), None!int(), Some(2)].flatten == [1, 2]);
448     assert([Some(1), Some(2), Some(3)]   .flatten == [1, 2, 3]);
449     const array1 = [None!int(), Some(1)];
450     assert(array1.flatten == [1]);
451     immutable array2 = [Some(1), Some(2)];
452     assert(array2.flatten == [1, 2]);
453   }
454 
455   {// fetch value from AA
456     int[string] aa1;
457     aa1["abc"] = 0;
458     assert(aa1.fetch("abc") == Some(0));
459     assert(aa1.fetch("xyz") == None!int());
460     const aa2 = aa1;
461     assert(aa2.fetch("abc") == Some(0));
462     assert(aa2.fetch("xyz") == None!int());
463     immutable aa3 = cast(immutable)aa1.dup;
464     assert(aa3.fetch("abc") == Some(0));
465     assert(aa3.fetch("xyz") == None!int());
466   }
467 
468   {// arguments should be lazily evaluated
469     int i = 1;
470     assert(s2.method2(i, i++) == Some!(int)(s2.get._i + 2));
471     assert(i == 2);
472     assert(n2.method2(i, i++) == None!(int)());
473     assert(i == 2);
474   }
475 
476   {// type conversion from derived class
477     auto d1 = new D1;
478     auto optD1    = Option!D1(new D1);
479     auto optD2    = Option!D2(new D2);
480     auto optD1AsC = Option!C (new D1);
481     auto optD2AsC = Option!C.fromRange([new D2]);
482   }
483 
484   {// equality
485     assert(None!(int )()  == None!(int )());
486     assert(None!(int )()  == None!(long)());
487     assert(None!(long)()  == None!(int )());
488     assert(Some!(int )(1) != None!(int )());
489     assert(None!(long)()  != Some!(int )(1));
490     assert(Some(1) == Some(1));
491     assert(Some(1) == Some(1L));
492     assert(Some(1) != Some(2));
493     assert(Some(1) != Some(2L));
494 
495     auto d1 = new D1;
496     auto d2 = new D2;
497     assert(Some!C (d1) == Some!C (d1));
498     assert(Some!C (d1) == Some!D1(d1));
499     assert(Some!D1(d1) == Some!C (d1));
500     assert(Some!D1(d1) == Some!D1(d1));
501     assert(Some!C (d1) != Some!C (d2));
502   }
503 
504   {// foreach
505     int x = 0;
506     foreach(elem; Some("hello")) { x++; }
507     assert(x == 1);
508     foreach(elem; None!int()) { x++; }
509     assert(x == 1);
510   }
511 }