1 module vtypechoice.optional; 2 3 import core.lifetime : forward; 4 import std.sumtype; 5 6 alias isOptional(T) = imported!"std.traits".isInstanceOf!(Optional, T); 7 alias isSome(T) = imported!"std.traits".isInstanceOf!(Some, T); 8 enum isNone(T) = is(imported!"std.traits".Unqual!T == None); 9 10 struct Optional(T) 11 { 12 import core.lifetime : forward; 13 import std.traits : CopyConstness, isInstanceOf, Unqual; 14 import std.meta : AliasSeq, IndexOf = staticIndexOf, Map = staticMap; 15 16 17 alias Some = .Some!T; 18 alias None = .None; 19 20 21 static some(U)(U s) 22 { 23 static if (.isSome!U) 24 { 25 static assert(.isOptional!T, "Cannot convert Some!("~U.stringof~") to "~Some.stringof); 26 // lets perform: Optional!(Optional!int).some(Some!int(3)); 27 CopyConstness!(U, Optional) res = { st: Some(T.some(s.get)) }; 28 } 29 else 30 CopyConstness!(U, Optional) res = { st: (CopyConstness!(U, Some)(s)) }; 31 32 return res; 33 } 34 35 36 static some()(None) 37 { 38 static assert(.isOptional!T, "Cannot convert Some!(None) to "~Some.stringof); 39 Optional res = { st: Some(T.none()) }; 40 41 return res; 42 } 43 44 45 static none() 46 { 47 Optional res = { st: .none() }; 48 49 return res; 50 } 51 52 53 bool opEquals(in Optional rhs) scope const 54 { 55 return st == rhs.st; 56 } 57 58 59 bool opEquals(in None _) scope const 60 { 61 return isNone(); 62 } 63 64 65 bool opEquals(U)(in U rhs) scope const 66 if (.isSome!U) 67 { 68 // allow comparing only with Some 69 // turning opEquals into a template forces it to never cast to alias this 70 // since Some alias to their template parameter type, 71 // restricting the opEquals' parameter to Some would not evaluate 72 // correctly if for example Some!(Some!T) was provided, as it would cast it 73 // to Some!T making it the wrong comparison to perform 74 enum errmsg = "Cannot compare "~U.stringof~" with "~Optional.stringof; 75 76 // ensure this is a valid type comparison 77 // fail here for a better error message, otherwise it'll fail with 78 // a generic SumType's message 79 static assert(__traits(compiles, T.init == rhs.get), errmsg); 80 return st.match!( 81 (in None _) => false, 82 (in T ok) => ok == rhs.get, 83 ); 84 } 85 86 87 auto ref opAssign(U)(U rhs) scope return 88 if (.isSome!U) 89 { 90 enum errmsg = "Cannot assign "~U.stringof~" to "~Optional.stringof; 91 static if (.isSome!(typeof(U.get))) 92 static assert (isSome!T, errmsg); 93 94 static assert(__traits(compiles, () { T some; some = rhs.get; }), errmsg); 95 96 st = rhs; 97 98 return this; 99 } 100 101 102 auto ref opAssign(None) scope return 103 { 104 st = None(); 105 106 return this; 107 } 108 109 110 bool isNone() scope const 111 { 112 return st.match!((in value) => .isNone!(typeof(value))); 113 } 114 115 bool isSome() scope const 116 { 117 return !isNone(); 118 } 119 120 121 // =================== 122 // # Optional as range # 123 // =================== 124 125 alias back = front; 126 alias empty = isNone; 127 128 auto ref inout(T) front() scope return inout 129 { 130 return this.unwrap(); 131 } 132 133 alias length = isSome; 134 135 136 alias opDollar(size_t dim : 0) = length; 137 138 ref opIndex() scope return 139 { 140 return this; 141 } 142 143 ref inout(T) opIndex(size_t index) scope return inout 144 in (index < length) 145 { 146 return this.unwrap(); 147 } 148 149 ref opIndex(size_t[2] dim) scope return 150 in (dim[0] <= dim[1]) 151 in (dim[1] <= length) 152 { 153 return this; 154 } 155 156 size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) scope return 157 { 158 return [start, end]; 159 } 160 161 162 alias popBack = popFront; 163 void popFront() scope { st = .none(); } 164 165 166 inout(Optional) save() scope inout { return this; } 167 168 169 SumType!(Some, None) st; 170 alias st this; 171 } 172 173 /// 174 version(vtypechoice_unittest) 175 @safe pure nothrow @nogc unittest 176 { 177 alias O = Optional!int; 178 179 assert(O.some(3) == Some!int(3)); 180 assert(O.none() == None()); 181 182 assert(O.some(3).match!( 183 (None _) => false, // use Some/None to match 184 (int) => true, // or use the underlying type to match 185 )); 186 187 assert(O.init == O.init); 188 assert(O.some(0) != O.none()); 189 190 static assert(!__traits(compiles, O.init = Some!(Some!int).init)); 191 static assert(!__traits(compiles, Optional!O.init = Some!int.init)); 192 static assert(!__traits(compiles, O.init = Some!short.init)); // TODO: should this be allowed? 193 194 static assert(!__traits(compiles, O.some(none))); 195 static assert( __traits(compiles, Optional!O.some(none))); 196 static assert( __traits(compiles, Optional!(Optional!O).some(none.some))); 197 static assert( __traits(compiles, Optional!(Optional!O).some(10.some.some))); 198 static assert(!__traits(compiles, Optional!(Optional!O).some(10.some.some.some))); 199 static assert(!__traits(compiles, Optional!(Optional!O).some(10.some))); 200 201 () @trusted 202 { 203 assert((O.some(3) = 10.some) == 10.some); 204 assert((O.some(3) = none) == none); 205 } 206 (); 207 } 208 209 210 struct Some(T) 211 { 212 T get; 213 alias get this; 214 } 215 216 auto some(T, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(T value) 217 { 218 enum untilFunName = () 219 { 220 for (size_t i; i + funName.length < prettyFun.length; i++) 221 if (prettyFun[i .. i + funName.length] == funName) 222 return i; 223 224 return 0; 225 } (); 226 227 static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Optional!U, U)) 228 return Type.some(value); 229 else 230 return Some!T(value); 231 } 232 233 /// 234 version(vtypechoice_unittest) 235 @safe pure nothrow @nogc unittest 236 { 237 static assert(__traits(compiles, () => 10.some)); 238 static assert(is(typeof(() { return 10.some; } ()) == Some!int)); 239 static assert(__traits(compiles, function Optional!int () => 10.some)); 240 } 241 242 243 244 struct None {} 245 246 auto none(string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)() 247 { 248 enum None n = {}; 249 250 enum untilFunName = () 251 { 252 for (size_t i; i + funName.length < prettyFun.length; i++) 253 if (prettyFun[i .. i + funName.length] == funName) 254 return i; 255 return 0; 256 } (); 257 258 static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Optional!U, U)) 259 return Type.none(); 260 else 261 return n; 262 } 263 264 /// 265 version(vtypechoice_unittest) 266 @safe pure nothrow @nogc unittest 267 { 268 static assert(__traits(compiles, () => none)); 269 static assert(is(typeof(() { return none; } ()) == None)); 270 271 static assert(__traits(compiles, function Optional!int () => none)); 272 } 273 274 275 // ==================================== 276 // # Optional's functional operations # 277 // ==================================== 278 279 auto andThen(alias pred, T)(auto ref scope Optional!T optional) 280 if (isOptional!(typeof(pred(T.init)))) 281 { 282 alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)).Types[0]); 283 284 return optional.match!( 285 (in None _) => Optional!Type.none(), 286 (ref scope T value) => pred(value), 287 ); 288 } 289 290 /// 291 version(vtypechoice_unittest) 292 @safe pure nothrow @nogc unittest 293 { 294 alias O = Optional!int; 295 assert(O.none.andThen!(i => O.some(i + 3)) == none); 296 assert(O.some(7).andThen!(i => O.some(i + 3)) == 10.some); 297 } 298 299 300 auto andThen(alias pred, T)(auto ref scope Optional!T optional) 301 if (isSome!(typeof(pred(T.init))) || isNone!(typeof(pred(T.init)))) 302 { 303 static if (isSome!(typeof(pred(T.init)))) 304 alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)))[0]; 305 else 306 alias Type = T; 307 308 return optional.match!( 309 (in None _) => Optional!Type.none(), 310 (ref scope T value) { Optional!Type opt = { st: (pred(value)) }; return opt; }, 311 ); 312 } 313 314 /// 315 version(vtypechoice_unittest) 316 @safe pure nothrow @nogc unittest 317 { 318 alias O = Optional!int; 319 assert(O.none.andThen!(i => some(i + 3)) == none); 320 assert(O.some(7).andThen!(i => none) == none); 321 } 322 323 324 auto andThen(alias pred, T)(auto ref scope Optional!T optional) 325 if (!isOptional!(typeof(pred(T.init))) 326 && !isSome!(typeof(pred(T.init))) 327 && !isNone!(typeof(pred(T.init)))) 328 { 329 alias Type = typeof(pred(T.init)); 330 331 return forward!optional.match!( 332 (in None _) => Optional!Type.none(), 333 (ref scope T value) => Optional!Type.some(pred(value)), 334 ); 335 } 336 337 /// 338 version(vtypechoice_unittest) 339 @safe pure nothrow @nogc unittest 340 { 341 alias O = Optional!int; 342 assert(O.none.andThen!(i => i + 3) == none); 343 assert(O.some(7).andThen!(i => i + 3) == 10.some); 344 } 345 346 347 Optional!T flatten(T)(auto ref scope Optional!(Optional!T) optional) 348 { 349 return forward!optional.match!( 350 (in None _) => Optional!T.none(), 351 (in value) => value, 352 ); 353 } 354 355 /// 356 version(vtypechoice_unittest) 357 @safe pure nothrow @nogc unittest 358 { 359 alias O = Optional!(Optional!int); 360 static assert(is(typeof(O.init.flatten()) == Optional!int)); 361 362 assert(O.some(3.some).flatten() == 3.some); 363 assert(O.none.flatten() == none); 364 } 365 366 367 template fmap(alias pred) 368 { 369 auto fmap(T)(auto ref scope Optional!T optional) 370 { 371 alias Type = typeof(pred(T.init)); 372 return optional.match!( 373 (in None _) => Optional!Type.none(), 374 (ref scope T value) => Optional!Type.some(pred(value)), 375 ); 376 } 377 } 378 379 /// 380 version(vtypechoice_unittest) 381 @safe pure nothrow @nogc unittest 382 { 383 assert(Optional!int.some(3).fmap!(n => n + 7) == 10.some); 384 385 import std.algorithm: equal, map; 386 import std.range : only; 387 assert(Optional!int.some(3).only.map!(fmap!(n => n + 7)).equal(10.some.only)); 388 } 389 390 391 template fmapOr(alias pred) 392 { 393 auto fmapOr(U, T)(auto ref scope Optional!T optional, auto ref scope U other) 394 { 395 // simmulate an implicit cast to pred's return type 396 // if the return type is know to be ulong and an int is provided, 397 // the program would compile and run if U was written as size_t 398 // however, as it isn't, it would fail because !is(size_t : int) 399 // by placing Ok!T first the return type will be pred's return type 400 // forcing other to be implicitly castable to it 401 return optional.match!( 402 (ref scope Some!T value) => pred(value.get), 403 (in None _) => other, 404 ); 405 } 406 } 407 408 /// 409 version(vtypechoice_unittest) 410 @safe pure nothrow @nogc unittest 411 { 412 auto x = Optional!string.some("foo"); 413 assert(x.fmapOr!(str => str.length)(42) == 3); 414 415 auto y = Optional!string.none(); 416 assert(y.fmapOr!(str => str.length)(42) == 42); 417 } 418 419 420 template fmapOrElse(alias pred, alias orElse) 421 { 422 auto fmapOrElse(T)(auto ref scope Optional!T optional) 423 if (is(typeof(orElse()) : typeof(pred(T.init)))) 424 { 425 // see: fmapOr 426 return optional.match!( 427 (ref scope Some!T value) => pred(value.get), 428 (in None _) => orElse(), 429 ); 430 } 431 } 432 433 /// 434 version(vtypechoice_unittest) 435 @safe pure nothrow @nogc unittest 436 { 437 auto x = Optional!string.some("foo"); 438 assert(x.fmapOrElse!(str => str.length, () => 42) == 3); 439 440 auto y = Optional!string.none(); 441 assert(y.fmapOrElse!(str => str.length, () => 42) == 42); 442 } 443 444 445 auto ref handle(alias pred, T)(return auto ref scope Optional!T optional) 446 { 447 optional.match!( 448 (in None _) {}, 449 (ref scope T value) { cast(void) pred(value); }, 450 ); 451 452 return optional; 453 } 454 455 /// 456 version(vtypechoice_unittest) 457 @safe pure nothrow @nogc unittest 458 { 459 assert(Optional!int.some(3).handle!((ref n) => n += 7) == 10.some); 460 assert(Optional!int.some(3).handle!((ref n) => n + 7) == 3.some); 461 assert(Optional!int.none.handle!((ref n) => n = 0) == .none); 462 } 463 464 465 bool has(T)(auto ref scope Optional!T optional, in T other) 466 { 467 return optional.match!( 468 (in None _) => false, 469 (in T value) => value == other, 470 ); 471 } 472 473 version(vtypechoice_unittest) 474 @safe pure nothrow @nogc unittest 475 { 476 assert(Optional!int.some(3).has(3)); 477 assert(!Optional!int.some(3).has(0)); 478 assert(!Optional!int.none.has(3)); 479 } 480 481 482 ref or(T)(return auto ref scope Optional!T optional, return auto ref scope Optional!T other) 483 { 484 return optional.isSome() ? optional : other; 485 } 486 487 /// 488 version(vtypechoice_unittest) 489 // @safe but @trusted due to assign 490 @trusted pure nothrow @nogc unittest 491 { 492 auto a = Optional!int.some(3); 493 auto b = Optional!int.none(); 494 assert(a.or(b) is a); 495 assert(b.or(a) is a); 496 497 a = Optional!int.none(); 498 b = Optional!int.none(); 499 assert(a.or(b) is b); 500 501 a = Optional!int.some(3); 502 b = Optional!int.some(10); 503 assert(a.or(b) is a); 504 } 505 506 507 auto ref orElse(alias pred, T)(return auto ref scope Optional!T optional) 508 if (is(typeof(pred()) == Optional!T)) 509 { 510 return optional.match!( 511 (in Some!T) => optional, 512 (in None _) => pred(), 513 ); 514 } 515 516 /// 517 version(vtypechoice_unittest) 518 @safe pure nothrow @nogc unittest 519 { 520 // source: https://doc.rust-lang.org/std/option/enum.Option.html#method.or_else 521 alias O = Optional!string; 522 523 auto nobody = function O() => none; 524 auto vikings = function O() => "vikings".some; 525 526 assert(O.some("barbarians").orElse!vikings == "barbarians".some); 527 assert(O.none.orElse!vikings == "vikings".some); 528 assert(O.none.orElse!nobody == none); 529 } 530 531 532 auto ref inout(T) unwrap(T)(auto ref scope inout Optional!T optional) 533 { 534 return optional.match!( 535 function ref inout(T) (in inout None _) => assert(0), 536 function ref inout(T) (return ref inout(T) value) => value, 537 ); 538 } 539 540 version(vtypechoice_unittest) 541 @safe pure nothrow @nogc unittest 542 { 543 assert(Optional!int.some(3).unwrap() == 3); 544 545 static assert(!__traits(compiles, &Optional!int.some(0).unwrap())); // not an lvalue 546 immutable opt = Optional!int.some(0); 547 static assert(__traits(compiles, &opt.unwrap())); // ok lvalue 548 549 static assert(is(typeof(opt.unwrap()) == immutable int)); 550 } 551 552 553 554 // ===================== 555 // # Optional as a range # 556 // ===================== 557 558 @safe pure nothrow @nogc unittest 559 { 560 static assert(__traits(compiles, () => Optional!int.init[0])); 561 562 auto r = Optional!int.some(3); 563 564 assert(r[0] == 3); 565 assert(r[0 .. $] is r); 566 567 568 import std.algorithm.comparison : equal; 569 import std.range : only; 570 571 assert(r.equal(3.only)); 572 assert(r[0 .. $].equal(3.only)); 573 assert(r.length == 1); 574 575 576 import std.algorithm.iteration : map, joiner; 577 578 assert(Optional!int.none.map!(n => n + 1).empty); 579 assert(r.only.joiner.front == 3); 580 }