1 /**
2     Optional type
3 */
4 module optional.optional;
5 
6 import std.typecons: Nullable;
7 
8 import optional.internal;
9 
10 package struct None {}
11 
12 /**
13     Represents an empty optional value. This is used to set `Optional`s to have no value
14     or for comparisons
15 
16     SeeAlso:
17         - `Optional.opEquals`
18 */
19 immutable none = None();
20 
21 private static string autoReturn(string expression)() {
22     return `
23         auto ref expr() {
24             return ` ~ expression ~ `;
25         }
26         ` ~ q{
27         alias R = typeof(expr());
28         static if (!is(R == void))
29             return empty ? no!R : some!R(expr());
30         else {
31             if (!empty) {
32                 expr();
33             }
34         }
35     };
36 }
37 
38 /**
39     Optional type. Also known as a Maybe or Option type in some languages.
40 
41     This can either contain a value or be `none`. It works with any value, including
42     values that can be null. I.e. null is a valid value that can be contained inside
43     an optional if T is a pointer type
44 
45     It also has range like behavior. So this acts as a range that contains 1 element or
46     is empty. Similar to `std.algorithm.only`
47 
48     And all operations that can be performed on a T can also be performed on an Optional!T.
49     The behavior of applying an operation on a no-value or null pointer is well defined
50     and safe.
51 */
52 
53 struct Optional(T) {
54     import std.traits: isMutable, isSomeFunction, isAssignable, Unqual;
55 
56     private enum isNullInvalid = is(T == class) || is(T == interface) || isSomeFunction!T;
57     private enum isNullable = is(typeof(T.init is null));
58 
59     private T _value = T.init; // Set to init for when T has @disable this()
60     private bool defined = false;
61 
62     private enum nonEmpty = q{
63         static if (isNullInvalid) {
64             this.defined = this._value !is null;
65         } else {
66             this.defined = true;
67         }
68     };
69     private void setNonEmptyState() {
70         mixin(nonEmpty);
71     }
72 
73     /**
74         Allows you to create an Optional type in place.
75 
76         This is useful if type T has a @disable this(this) for e.g.
77     */
78     static Optional!T construct(Args...)(auto ref Args args) {
79         import std.algorithm: move;
80         auto value = T(args);
81         Optional!T opt;
82         opt._value = move(value);
83         opt.setNonEmptyState;
84         return move(opt);
85     }
86 
87     /**
88         Constructs an Optional!T value by assigning T
89 
90         If T is of class type, interface type, or some function pointer then passing in null
91         sets the optional to `none` interally
92     */
93     this(U : T, this This)(auto ref U value) {
94         this._value = value;
95         mixin(nonEmpty);
96     }
97     /// Ditto
98     this(const None) {
99         // For Error: field _value must be initialized in constructor, because it is nested struct
100         this._value = T.init;
101     }
102 
103     @property bool empty() const {
104         static if (isNullInvalid) {
105             return !this.defined || this._value is null;
106         } else {
107             return !this.defined;
108         }
109     }
110     @property ref inout(T) front() inout {
111         assert(!empty, "Attempting to fetch the front of an empty optional.");
112         return this._value;
113     }
114     void popFront() { this.defined = false; }
115 
116     /**
117         Compare two optionals or an optional with some value
118         Returns:
119             - If the two are optionals then they are both unwrapped and compared. If either are empty
120             this returns false. And if compared with `none` and there's a value, also returns false
121         ---
122         auto a = some(3);
123         a == some(2); // false
124         a == some(3); // true
125         a == none; // false
126         ---
127     */
128     bool opEquals(const None) const { return this.empty; }
129     /// Ditto
130     bool opEquals(U : T)(const auto ref Optional!U rhs) const {
131         if (this.empty || rhs.empty) return this.empty == rhs.empty;
132         return this._value == rhs._value;
133     }
134     /// Ditto
135     bool opEquals(U : T)(const auto ref U rhs) const {
136         return !this.empty && this._value == rhs;
137     }
138     /// Ditto
139 
140     bool opEquals(R)(auto ref R other) const if (from!"std.range".isInputRange!R) {
141         import std.range: empty, front;
142 
143         if (this.empty && other.empty) return true;
144         if (this.empty || other.empty) return false;
145         return this.front == other.front;
146     }
147 
148     /**
149         Assigns a value to the optional or sets it to `none`.
150 
151         If T is of class type, interface type, or some function pointer than passing in null
152         sets the optional to `none` internally
153     */
154     void opAssign()(const None) if (isMutable!T) {
155         if (!this.empty) {
156             static if (isNullInvalid) {
157                 this._value = null;
158             } else {
159                 destroy(this._value);
160             }
161             this.defined = false;
162         }
163     }
164     /// Ditto
165     void opAssign(U : T)(auto ref U lhs) if (isMutable!T && isAssignable!(T, U)) {
166         this._value = lhs;
167         mixin(nonEmpty);
168     }
169     /// Ditto
170     void opAssign(U : T)(auto ref Optional!U lhs) if (isMutable!T && isAssignable!(T, U))  {
171         this._value = lhs._value;
172         this.defined = lhs.defined;
173     }
174 
175     /**
176         Applies unary operator to internal value of optional.
177         Returns:
178             - If the optional is some value it returns an optional of some `op value`.
179         ---
180         auto a = no!(int*);
181         auto b = *a; // ok
182         b = 3; // b is an Optional!int because of the deref
183         ---
184     */
185     auto ref opUnary(string op, this This)() {
186         import std.traits: isPointer;
187         static if (op == "*" && isPointer!T) {
188             import std.traits: PointerTarget;
189             alias P = PointerTarget!T;
190             return empty || front is null ? no!P : some(*this.front);
191         } else {
192             alias R = typeof(mixin(op ~ "_value"));
193             static if (is(R == void)) {
194                 if (!empty) mixin(op ~ "_value");
195             } else {
196                 alias NoType = typeof(some(mixin(op ~ "_value")));
197                 if (!empty) {
198                     return some(mixin(op ~ "_value"));
199                 } else {
200                     return NoType();
201                 }
202             }
203         }
204     }
205 
206     /**
207         If the optional is some value it returns an optional of some `value op rhs`
208     */
209     auto ref opBinary(string op, U : T, this This)(auto ref U rhs) {
210         mixin(autoReturn!("front" ~ op ~ "rhs"));
211     }
212     /**
213         If the optional is some value it returns an optional of some `lhs op value`
214     */
215     auto ref opBinaryRight(string op, U : T, this This)(auto ref U lhs) {
216         mixin(autoReturn!("lhs"  ~ op ~ "front"));
217     }
218 
219     /**
220         If there's a value that's callable it will be called else it's a noop
221 
222         Returns:
223             Optional value of whatever `T(args)` returns
224     */
225     auto ref opCall(Args...)(Args args) if (from!"std.traits".isCallable!T) {
226         mixin(autoReturn!("this._value(args)"));
227     }
228 
229     /**
230         If the optional is some value, op assigns rhs to it
231     */
232     auto ref opOpAssign(string op, U : T, this This)(auto ref U rhs) {
233         mixin(autoReturn!("front" ~ op ~ "= rhs"));
234     }
235 
236     static if (from!"std.traits".isArray!T) {
237         /**
238             Provides indexing into arrays
239 
240             The indexes and slices are also checked to be valid and `none` is returned if they are
241             not
242         */
243         /*auto opIndex(this This)(size_t index) {
244             enum call = "front[index]";
245             import std.range: ElementType;
246             if (empty || index >= front.length || index < 0) {
247                 return no!(mixin("typeof("~call~")"));
248             }
249             mixin(autoReturn!(call));
250         }*/
251         /// Ditto
252         auto ref opIndex(this This)() {
253             mixin(autoReturn!("front[]"));
254         }
255         /// Ditto
256         /*
257         auto opSlice(this This)(size_t begin, size_t end) {
258             enum call = "front[begin .. end]";
259             import std.range: ElementType;
260             if (empty || begin > end || end > front.length) {
261                 return no!(mixin("typeof("~call~")"));
262             }
263             mixin(autoReturn!(call));
264         }*/
265         /// Ditto
266         auto opDollar() const {
267             return empty ? 0 : front.length;
268         }
269     }
270 
271 }
272 
273 /**
274     Type constructor for an optional having some value of `T`
275 
276     Calling some on the result of a dispatch chain will result
277     in the original optional value.
278 */
279 public auto ref some(T)(auto ref T value) {
280     return Optional!T(value);
281 }
282 
283 /// Type constructor for an optional having no value of `T`
284 public auto no(T)() {
285     return Optional!T();
286 }
287 
288 
289 /**
290     Get pointer to value. If T is a reference type then T is returned
291 
292     Use this to safely access reference types, or to get at the raw value
293     of non reference types via a non-null pointer.
294 
295     It is recommended that you access internal values by using `orElse` instead though
296 
297     Returns:
298         Pointer to value or null if empty. If T is reference type, returns reference
299 */
300 public auto ref unwrap(T)(inout auto ref Optional!T opt) {
301     static if (is(T == class) || is(T == interface)) {
302         return opt.empty ? null : opt.front;
303     } else {
304         return opt.empty ? null : &opt.front();
305     }
306 }
307 
308 
309 /**
310     Returns the value contained within the optional _or else_ another value if there's `no!T`
311 
312     Params:
313         opt = the optional to call orElse on
314         value = The value to return if the optional is empty
315         pred = The predicate to call if the optional is empty
316 */
317 public auto ref U orElse(T, U)(inout auto ref Optional!T opt, lazy U value) if (is(U : T)) {
318     return opt.orElse!value;
319 }
320 
321 /// Ditto
322 public auto ref orElse(alias pred, T)(inout auto ref Optional!T opt) if (is(typeof(pred()) : T)) {
323     return opt.empty ? pred() : opt.front;
324 }
325 
326 
327 /**
328     Calls an appropriate handler depending on if the optional has a value or not
329 
330     Params:
331         opt = The optional to call match on
332         handlers = 2 predicates, one that takes the underlying optional type and another that names nothing
333 */
334 public template match(handlers...) if (handlers.length == 2) {
335 	auto ref match(T)(inout auto ref Optional!T opt) {
336 
337         static if (is(typeof(handlers[0](opt.front)))) {
338             alias someHandler = handlers[0];
339             alias noHandler = handlers[1];
340         } else {
341             alias someHandler = handlers[1];
342             alias noHandler = handlers[0];
343         }
344 
345         import optional.bolts: isFunctionOver;
346 
347         static assert(
348             isFunctionOver!(someHandler, T) && isFunctionOver!(noHandler),
349             "One handler must have one parameter of type '" ~ T.stringof ~ "' and the other no parameter"
350         );
351 
352         alias RS = typeof(someHandler(opt.front));
353         alias RN = typeof(noHandler());
354 
355         static assert(
356             is(RS == RN),
357             "Expected two handlers to return same type, found type '" ~ RS.stringof ~ "' and type '" ~ RN.stringof ~ "'",
358         );
359 
360         if (opt.empty) {
361             return noHandler();
362         } else {
363             return someHandler(opt.front);
364         }
365 	}
366 }
367 
368 
369 /**
370     Converts a range or Nullable to an optional type
371 
372     Params:
373         range = the range to convert. It must have no more than 1 element
374         nullable = the Nullable to convert
375 
376     Returns:
377         an optional of the element of range or Nullable
378 */
379 /*
380 auto toOptional(R)(auto ref R range) if (from!"std.range".isInputRange!R) {
381     import std.range: walkLength, ElementType, front;
382     assert(range.empty || range.walkLength == 1);
383     if (range.empty) {
384         return no!(ElementType!R);
385     } else {
386         return some(range.front);
387     }
388 }
389 */
390 /// Ditto
391 auto toOptional(T)(auto ref Nullable!T nullable) {
392     if (nullable.isNull) {
393         return no!T;
394     } else {
395         return some(nullable.get);
396     }
397 }
398 
399 
400 /**
401     Turns an Optional in to a Nullable
402 
403     Params:
404         opt = the optional to convert from a Nullable!T
405 
406     Returns:
407         An Nullable!T
408 */
409 auto toNullable(T)(auto ref Optional!T opt) {
410     import std.typecons: nullable;
411     if (opt.empty) {
412         return Nullable!T();
413     } else {
414         return opt.front.nullable;
415     }
416 }