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 }