1 /**
2 Nullable extension.
3 */
4 module karasux.nullable;
5 
6 import std.functional : unaryFun;
7 import std.range : isInputRange;
8 import std.traits : ReturnType, isCallable;
9 import std.typecons : Nullable, nullable;
10 
11 /**
12 Nullable as range.
13 */
14 struct NullableRange(T)
15 {
16     static assert(isInputRange!(typeof(this)));
17 
18     /**
19     Initialize by inner value.
20 
21     Params:
22         value = inner value.
23     */
24     inout this(inout T value)
25         out(; !empty)
26     {
27         this.nullable_ = value;
28     }
29 
30     /**
31     Returns:
32        content reference. 
33     */
34     ref inout(T) front() inout pure @nogc @property nothrow @safe
35         in (!empty)
36     {
37         return nullable_.get;
38     }
39 
40     /**
41     Returns:
42         true if nullable is empty.
43     */
44     bool empty() const pure @property nothrow @safe
45     {
46         return nullable_.isNull;
47     }
48 
49     /**
50     pop nullable contents.
51     */
52     void popFront()()
53         out(; empty)
54     {
55         nullable_.nullify();
56     }
57 
58     /**
59     Returns:
60         nullable value reference.
61     */
62     ref inout(Nullable!T) asNullable() inout @nogc pure @property nothrow @safe
63     {
64         return nullable_;
65     }
66 
67 private:
68     Nullable!T nullable_;
69 }
70 
71 ///
72 @nogc nothrow pure @safe unittest
73 {
74     assert(NullableRange!int(99).front == 99);
75 
76     auto x = NullableRange!int(100);
77     assert(x.front == 100);
78     
79     x.front = 1234;
80     assert(x.front == 1234);
81     x.popFront;
82     assert(x.empty == true);
83 }
84 
85 ///
86 @nogc nothrow pure @safe unittest
87 {
88     auto x = NullableRange!int(99).asNullable;
89     assert(!x.isNull);
90     assert(x.get == 99);
91     x.nullify();
92     assert(x.isNull);
93 }
94 
95 /**
96 Params:
97     value = inner value.
98 Returns:
99     nullable range value.
100 */
101 NullableRange!T nullableRange(T)(inout T value)
102 {
103     return NullableRange!T(value);
104 }
105 
106 ///
107 @nogc nothrow pure @safe unittest
108 {
109     assert(99.nullableRange.front == 99);
110 
111     auto x = 100.nullableRange;
112     assert(x.front == 100);
113     
114     x.front = 1234;
115     assert(x.front == 1234);
116     x.popFront;
117     assert(x.empty == true);
118 }
119 
120 /**
121 Params:
122     value = inner value.
123 Returns:
124     nullable range value.
125 */
126 NullableRange!T toRange(T)(inout Nullable!T value)
127 {
128     return (value.isNull) ? typeof(return).init : NullableRange!T(value.get);
129 }
130 
131 ///
132 @nogc nothrow pure @safe unittest
133 {
134     import std.typecons : nullable;
135     auto range = 100.nullable.toRange;
136     assert(range.front == 100);
137     assert(!range.empty);
138     range.popFront();
139     assert(range.empty);
140 
141     auto emptyRange = Nullable!int.init.toRange;
142     assert(emptyRange.empty);
143 }
144 
145 /**
146 Get and map Nullable content.
147 
148 Params:
149     F = map function.
150     T = Nullable content type.
151     value = Nullable value.
152 Returns:
153     mapped Nullable value.
154 */
155 auto getMap(alias F, T)(Nullable!T value)
156 {
157     alias fun = unaryFun!F;
158     alias R = Nullable!(typeof(fun(value.get)));
159     return (value.isNull) ? R.init : nullable(fun(value.get));
160 }
161 
162 ///
163 nothrow pure @safe unittest
164 {
165     import std.conv : to;
166     Nullable!int value = 100.nullable;
167     auto mapped = value.getMap!((v) => v.to!string);
168 
169     static assert(is(typeof(mapped) == Nullable!string));
170     assert(!mapped.isNull);
171     assert(mapped.get == "100");
172 
173     assert(Nullable!int.init.getMap!((v) => v.to!string).isNull);
174 }
175 
176 /**
177 Get and flat map Nullable content.
178 
179 Params:
180     F = map function.
181     T = Nullable content type.
182     value = Nullable value.
183 Returns:
184     mapped Nullable value.
185 */
186 auto getFlat(alias F, T)(Nullable!T value)
187 {
188     alias fun = unaryFun!F;
189     alias R = typeof(fun(value.get));
190     static assert(is(R : Nullable!S, S), "required Nullable type. current: " ~ R.stringof);
191     return (value.isNull) ? R.init : fun(value.get);
192 }
193 
194 ///
195 nothrow pure @safe unittest
196 {
197     import std.conv : to;
198 
199     Nullable!int value = 100.nullable;
200     auto mapped = value.getFlat!((v) => v.to!string.nullable);
201 
202     static assert(is(typeof(mapped) == Nullable!string));
203     assert(!mapped.isNull);
204     assert(mapped.get == "100");
205 
206     assert(Nullable!int.init.getFlat!((v) => v.to!string.nullable).isNull);
207     assert(Nullable!int.init.getFlat!((v) => Nullable!string.init).isNull);
208 }
209