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