1 /******************************************************************************
2 
3     Wraps any type in a struct that also contains boolean field indicating if
4     value is in defined state.
5 
6     If T is a value type, `Optional!(T)` is value type too.
7 
8     Copyright:
9         Copyright (c) 2009-2017 dunnhumby Germany GmbH. All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 
17 *******************************************************************************/
18 
19 module ocean.core.Optional;
20 
21 version (unittest)
22 {
23     import ocean.core.Test;
24 }
25 
26 /// ditto
27 struct Optional ( T )
28 {
29     /// Alias to make code working with undefined state more readable
30     public enum undefined = Optional!(T).init;
31 
32     /// wrapped arbitrary value
33     private T value;
34 
35     /// flag indicating if stored value is in defined state
36     private bool defined = false;
37 
38     /**************************************************************************
39 
40         Puts `this` into defined state
41 
42         Params:
43             rhs = value to assign
44 
45     **************************************************************************/
46 
47     public void opAssign ( T rhs )
48     {
49         this.defined = true;
50         this.value = rhs;
51     }
52 
53     /**************************************************************************
54 
55         Puts `this` into undefined state
56 
57     **************************************************************************/
58 
59     public void reset()
60     {
61         this.tupleof[] = Optional.undefined.tupleof[];
62     }
63 
64     /**************************************************************************
65 
66         Interface to retrieve stored value. It is intentionally designed in a
67         way that forces you to handle "undefined" state to avoid issues akin
68         to "null pointer".
69 
70         This will become more convenient with D2 when lambda type inference
71         will happen, as well as shorter notation:
72 
73         ---
74             value.visit(
75                 ()  => { },
76                 (x) => { }
77             );
78         ---
79 
80         Parameters:
81             cb_undefined = action to take if value is not defined
82             cb_defined   = ditto for defined. May modify internal value via
83                 reference
84 
85     **************************************************************************/
86 
87     public void visit ( scope void delegate() cb_undefined,
88         scope void delegate(ref T) cb_defined )
89     {
90         if (this.defined)
91             cb_defined(this.value);
92         else
93             cb_undefined();
94     }
95 
96     /**************************************************************************
97 
98         A more "old-school" version of `visit`. Provided both to conform style
99         of existing code and avoid delegate bugs in dmd1.
100 
101         Discouraged by default as more error-prone than `visit`.
102 
103         Parameters:
104             value = will be set to content of `this` if defined, will remain
105                 unchanged otherwise
106 
107         Return:
108             `true` if this is defined and `value` was updated.
109 
110     **************************************************************************/
111 
112     public bool get ( ref T value )
113     {
114         if (this.defined)
115             value = this.value;
116         return this.defined;
117     }
118 
119     /**************************************************************************
120 
121         Returns:
122             `true` if this is defined.
123 
124     **************************************************************************/
125 
126     public bool isDefined ( )
127     {
128         return this.defined;
129     }
130 }
131 
132 ///
133 unittest
134 {
135     alias Optional!(bool) Maybe;
136 
137     Maybe x, y, z;
138     x = true;
139     y = false;
140     z = Maybe.undefined;
141 
142     x.visit(
143         ()               { test(false); },
144         (ref bool value) { test(value); }
145     );
146 
147     y.visit(
148         ()               { test(false); },
149         (ref bool value) { test(!value); }
150     );
151 
152     z.visit(
153         ()               { test(true); },
154         (ref bool value) { test(false); }
155     );
156 }
157 
158 unittest
159 {
160     Optional!(int) x;
161     test(!x.isDefined());
162     int y = 10;
163     bool ok = x.get(y);
164     test(!ok);
165     test(y == 10);
166     x = 42;
167     ok = x.get(y);
168     test(ok);
169     test(y == 42);
170 }
171 
172 /******************************************************************************
173 
174     Shortcut on top of Optional to created defined value, uses IFTI to reduce
175     the noise
176 
177     Parameters:
178         value = value to wrap into Optional
179 
180     Returns:
181         wrapped value
182 
183 ******************************************************************************/
184 
185 public Optional!(T) optional ( T ) ( T value )
186 {
187     return Optional!(T)(value, true);
188 }
189 
190 ///
191 unittest
192 {
193     Optional!(int) foo ( bool x )
194     {
195         if (x)
196             return optional(42);
197         else
198             return Optional!(int).undefined;
199     }
200 
201     foo(true).visit(
202         ()              { test(false); },
203         (ref int value) { test(value == 42); }
204     );
205 
206     foo(false).visit(
207         ()              { },
208         (ref int value) { test(false); }
209     );
210 }