1 /* 2 * The MIT License 3 * 4 * Copyright (c) 2014 Shunsuke Kirino 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25 module option; 26 27 import std.algorithm : find, reduce; 28 import std.functional : unaryFun; 29 import std.conv : to; 30 import std.traits : isCallable, isPointer, Unqual; 31 import std.range : isInputRange, ElementType, ElementEncodingType, empty, front; 32 import std.exception : enforce, assertThrown, assertNotThrown; 33 34 template isOptionType(T) { 35 static if(is(T U == Option!U)) 36 enum isOptionType = true; 37 else 38 enum isOptionType = false; 39 } 40 41 private template isNullableType(T) { 42 enum isNullableType = is(T == class) || is(T == interface) || isPointer!T; 43 } 44 45 struct Option(T) 46 { 47 private: 48 T _Option_value;// Avoid name conflict by prefixing 49 50 public: 51 alias T OptionValueType; 52 53 // constructor 54 static if(isNullableType!T) { 55 public: 56 this (inout(T) t) inout { _Option_value = t; } 57 this(U: T)(inout(U) u) inout { _Option_value = u; } 58 } else { 59 private: 60 this (inout(T) t, bool b) inout { _Option_value = t; _isDefined = b; } 61 this(U: T)(inout(U) u, bool b) inout { _Option_value = u; _isDefined = b; } 62 public: 63 this (inout(T) u) inout { _Option_value = u; _isDefined = true; } 64 this(U: T)(inout(U) u) inout { _Option_value = u; _isDefined = true; } 65 } 66 67 // isDefined 68 static if(isNullableType!T) { 69 public: 70 @property pure nothrow bool isDefined() const { return _Option_value !is null; } 71 } else { 72 private: 73 bool _isDefined; 74 public: 75 @property pure nothrow bool isDefined() const { return _isDefined; } 76 } 77 @property pure nothrow bool isEmpty() const { return !isDefined(); } 78 79 @property pure ref inout(T) get() inout { 80 enforce(isDefined, "No such element: None!(" ~ T.stringof ~ ").get"); 81 return _Option_value; 82 } 83 84 pure nothrow const(T) getOrElse(U: T)(const(U) other) const { 85 return isDefined ? _Option_value : other; 86 } 87 pure nothrow T getOrElse(U: T)(U other) { 88 return isDefined ? _Option_value : other; 89 } 90 pure nothrow const(Option!T) orElse(U: T)(const(Option!U) other) const { 91 return isDefined ? this : other; 92 } 93 pure nothrow Option!T orElse(U: T)(Option!U other) { 94 return isDefined ? this : other; 95 } 96 97 pure nothrow inout(T)[] array() inout { 98 return isDefined ? [_Option_value] : []; 99 } 100 static pure Option!T fromRange(R)(R r) if(isInputRange!R && is(ElementType!(R): T)) { 101 return r.empty ? None!T() : Some!T(r.front); 102 } 103 104 string toString() const { // toString must be "const" in order to avoid strange linker error 105 if(isDefined) { 106 static if(__traits(compiles, to!string(_Option_value))) 107 return "Some!(" ~ T.stringof ~ ")(" ~ to!string(_Option_value) ~ ')'; 108 else 109 return "Some!(" ~ T.stringof ~ ")(" ~ (cast(Unqual!T)_Option_value).toString ~ ')'; // Avoid error due to non-const toString() 110 } else { 111 return "None!(" ~ T.stringof ~ ")()"; 112 } 113 } 114 115 auto opDispatch(string fn, Args...)(lazy Args args) { 116 static if(args.length == 0) { 117 alias F = typeof(mixin("_Option_value." ~ fn)); 118 static if(isCallable!F) 119 enum MethodCall = "_Option_value." ~ fn ~ "()"; 120 else // property access 121 enum MethodCall = "_Option_value." ~ fn; 122 } else { 123 enum MethodCall = "_Option_value." ~ fn ~ "(args)"; 124 } 125 alias R = typeof(mixin(MethodCall)); 126 static if(is(R == void)) { 127 if(isDefined) mixin(MethodCall ~ ';'); 128 } else { 129 return isDefined ? Some!R(mixin(MethodCall)) : None!R(); 130 } 131 } 132 133 bool opEquals(U: T)(const(Option!U) rhs) const { 134 if( isDefined && rhs.isDefined) return _Option_value == rhs._Option_value; 135 if(!isDefined && !rhs.isDefined) return true; 136 return false; 137 } 138 139 // foreach support 140 int opApply(int delegate(ref const(T)) operation) const { 141 if(isDefined) 142 return operation(_Option_value); 143 else 144 return 0; 145 } 146 } 147 148 pure Option!T Some(T)(T t) 149 { 150 return Some!(T, T)(t); 151 } 152 pure Option!T Some(T, U = T)(U t) if(is(T: U)) 153 { 154 static if(isNullableType!T) { 155 enforce(t, "Value must not be null!"); 156 return Option!T(t); 157 } else { 158 return Option!T(t, true); 159 } 160 } 161 pure nothrow Option!T None(T)() 162 { 163 static if(isNullableType!T) 164 return Option!T(null); 165 else 166 return Option!T(T.init, false); 167 } 168 169 // `flatten` is defined as a top-level function in order to provide bettern type inference (to my best knowledge). 170 pure nothrow Option!T flatten(T)(Option!(Option!T) o) 171 { 172 return o.isDefined ? o._Option_value : None!T(); 173 } 174 175 // `filter`, `map` and `flatMap` is defined as a top-level function to avoid error: 176 // "cannot use local 'fun' as parameter to non-global template" 177 pure nothrow Option!T filter(alias pred, T)(Option!T o) if(is(typeof(unaryFun!pred(o.get)) : bool)) 178 { 179 if(o.isDefined && unaryFun!pred(o._Option_value)) 180 return o; 181 else 182 return None!T(); 183 } 184 auto map(alias fun, T)(Option!T o) if(is(typeof(unaryFun!fun(o.get)))) 185 { 186 alias R = typeof(unaryFun!fun(o.get)); 187 static if(is(R == void)) { 188 if(o.isDefined) unaryFun!fun(o._Option_value); 189 } else { 190 return o.isDefined ? Some!R(unaryFun!fun(o._Option_value)) : None!R(); 191 } 192 } 193 auto flatMap(alias fun, T)(Option!T o) if(isOptionType!(typeof(unaryFun!fun(o.get)))) 194 { 195 return map!fun(o).flatten; 196 } 197 198 // range helper 199 Option!(ElementEncodingType!R) detect(alias pred, R)(R range) if(isInputRange!R) 200 { 201 return Option!(ElementEncodingType!R).fromRange(find!(pred, R)(range)); 202 } 203 pure ElementEncodingType!(R).OptionValueType[] flatten(R)(R range) if(isInputRange!R && isOptionType!(ElementEncodingType!R)) 204 { 205 ElementEncodingType!(R).OptionValueType[] a0 = []; 206 return reduce!((a, b) => b.isDefined ? a ~ b.get : a)(a0, range); 207 } 208 209 // associative array helper 210 // Define each function overloads for mutable/const/immutable to workaround 211 // an issue about inout (https://issues.dlang.org/show_bug.cgi?id=9983). 212 pure nothrow Option!V fetch(K, V)(V[K] aa, const K key) 213 { 214 auto ptr = key in aa; 215 return (ptr == null) ? None!V() : Some(*ptr); 216 } 217 pure nothrow Option!(const V) fetch(K, V)(const V[K] aa, const K key) 218 { 219 auto ptr = key in aa; 220 return (ptr == null) ? None!(const V)() : Some(*ptr); 221 } 222 pure nothrow Option!(immutable V) fetch(K, V)(immutable V[K] aa, const K key) 223 { 224 auto ptr = key in aa; 225 return (ptr == null) ? None!(immutable V)() : Some(*ptr); 226 } 227 228 229 unittest { 230 class C 231 { 232 int _i = 5; 233 void method1() { _i = 10; } 234 int method2(int x, int y) { return _i + x + y; } 235 override string toString() const { return "C#toString"; } 236 } 237 class D1: C {} 238 class D2: C {} 239 240 // construction helpers 241 auto s0 = Some(3); 242 auto s1 = Some("hoge"); 243 auto s2 = Some(new C); 244 auto n0 = None!int(); 245 auto n1 = None!string(); 246 auto n2 = None!C(); 247 assert( s0.isDefined); 248 assert( s1.isDefined); 249 assert( s2.isDefined); 250 assert(!n0.isDefined); 251 assert(!n1.isDefined); 252 assert(!n2.isDefined); 253 assertThrown!Exception(Some!(int*)(null)); 254 assertThrown!Exception(Some!C(null)); 255 256 {// Option constructor with value type, pointer, array/AA, class (Note that empty arrays are treated as null) 257 int integer = 0; 258 int[string] aaEmpty, aaNonEmpty; 259 aaNonEmpty["abc"] = 10; 260 assert( Option!(int )(19 ).isDefined); 261 assert(!Option!(int* )(null ).isDefined); 262 assert( Option!(int* )(&integer ).isDefined); 263 assert( Option!(string )(null ).isDefined); 264 assert( Option!(string )("" ).isDefined); 265 assert( Option!(string )("hoge" ).isDefined); 266 assert( Option!(int[string])(cast(int[string]) null).isDefined); 267 assert( Option!(int[string])(aaEmpty ).isDefined); 268 assert( Option!(int[string])(aaNonEmpty ).isDefined); 269 assert( Option!(C )(s2.get ).isDefined); 270 assert(!Option!(C )(null ).isDefined); 271 assert(Option!(string )( null) == Some("")); 272 assert(Option!(int[string])(cast(int[string]) null) == Some(aaEmpty)); 273 274 assert(Option!(const int)(0).isDefined); 275 assert(Option!(immutable int)(0).isDefined); 276 assert(Option!(const C)( new C).isDefined); 277 assert(Option!(const C)(cast(const C) new C).isDefined); 278 279 assert(Some!(const int)(0).isDefined); 280 assert(Some!(immutable int)(0).isDefined); 281 assert(Some!(const C)( new C).isDefined); 282 assert(Some!(const C)(cast(const C) new C).isDefined); 283 284 assert(!None!(const int)().isDefined); 285 assert(!None!(immutable int)().isDefined); 286 assert(!None!(const C)().isDefined); 287 } 288 289 {// get, getOrElse, orElse 290 assert(s0.get == 3); 291 assert(s1.get == "hoge"); 292 assert(s2.get._i == 5); 293 assertThrown!Exception(n0.get); 294 assertThrown!Exception(n1.get); 295 assertThrown!Exception(n2.get); 296 297 assert(s0.getOrElse(7) == s0.get); 298 assert(s0.getOrElse(true) == s0.get); 299 assert(s1.getOrElse("fuga") == s1.get); 300 assert(s2.getOrElse(new C) is s2.get); 301 assert(n0.getOrElse(7) == 7); 302 assert(n1.getOrElse("fuga") == "fuga"); 303 assert(n2.getOrElse(new C)._i == 5); 304 305 assert(s0.orElse(None!int) == s0); 306 assert(s1.orElse(Some("fuga")) == s1); 307 assert(s2.orElse(None!C()) == s2); 308 assert(n0.orElse(Some(10)) == Some(10)); 309 assert(n1.orElse(None!string()) == None!string()); 310 assert(n2.orElse(s2) == s2); 311 312 assert(Some!(const int)(1).get == 1); 313 assert(Some!(immutable int)(1).get == 1); 314 assert(Some!(const int)(1).getOrElse(0) == 1); 315 assert(Some!(immutable int)(1).getOrElse(0) == 1); 316 assert(Some!(const int)(1).orElse(Some!(const int)(0)) == Some(1)); 317 assert(Some!(immutable int)(1).orElse(Some!(immutable int)(0)) == Some(1)); 318 } 319 320 {// array conversion 321 assert(s0.array == [s0.get]); 322 assert(s1.array == [s1.get]); 323 assert(s2.array == [s2.get]); 324 assert(n0.array == []); 325 assert(n1.array == []); 326 assert(n2.array == []); 327 assert(s0 == Option!(int ).fromRange(s0.array)); 328 assert(s1 == Option!(string).fromRange(s1.array)); 329 assert(s2 == Option!(C ).fromRange(s2.array)); 330 assert(n0 == Option!(int ).fromRange(n0.array)); 331 assert(n1 == Option!(string).fromRange(n1.array)); 332 assert(n2 == Option!(C ).fromRange(n2.array)); 333 334 assert(Option!(const int).fromRange(Some!(const int)(1).array) == Some(1)); 335 assert(Option!(immutable int).fromRange(Some!(immutable int)(1).array) == Some(1)); 336 } 337 338 {// toString 339 assert(s0.toString == "Some!(int)(3)"); 340 assert(s1.toString == "Some!(string)(hoge)"); 341 assert(s2.toString == "Some!(C)(C#toString)"); 342 assert(n0.toString == "None!(int)()"); 343 assert(n1.toString == "None!(string)()"); 344 assert(n2.toString == "None!(C)()"); 345 346 class X {} 347 static assert(__traits(compiles, Some(new X).toString)); 348 struct Y {} 349 static assert(__traits(compiles, Some(Y()).toString)); 350 351 assert(Some!(const int)(1).toString == "Some!(const(int))(1)"); 352 assert(Some!(immutable int)(1).toString == "Some!(immutable(int))(1)"); 353 } 354 355 {// access T's members 356 assert(s1.length == Some!size_t(4)); 357 s2.method1(); 358 assert(s2.get._i == 10); 359 assert(s2._i == Some(10)); 360 assert(s2.method2(1, 2) == Some(13)); 361 assert(n1.length == None!size_t()); 362 assertNotThrown(n2.method1()); 363 assert(n2.method2(1, 2) == None!int()); 364 365 assert(Some!(const string)("hello").length == Some(5)); 366 assert(Some!(immutable string)("hello").length == Some(5)); 367 } 368 369 {// flatten 370 assert(Some(Some(1)) .flatten == Some(1)); 371 assert(Some(None!int()) .flatten == None!int()); 372 assert(None!(Option!int)().flatten == None!int()); 373 374 assert(Some(Some!(const int)(1)).flatten == Some(1)); 375 assert(Some(Some!(immutable int)(1)).flatten == Some(1)); 376 } 377 378 {// filter 379 assert(Some(3) .filter!(x => x % 2 == 1) == Some(3)); 380 assert(Some(3) .filter!("a % 2 == 0") == None!int()); 381 assert(None!int().filter!(x => x % 2 == 1) == None!int()); 382 assert(None!int().filter!("a % 2 == 0") == None!int()); 383 384 assert(Some!(const int)(3).filter!(x => x % 2 == 1) == Some(3)); 385 assert(Some!(immutable int)(3).filter!(x => x % 2 == 1) == Some(3)); 386 } 387 388 {// map 389 int sum = 0; 390 void voidFun(int i) { sum += 1; } 391 Some(1).map!voidFun(); 392 assert(sum == 1); 393 None!int().map!voidFun(); 394 assert(sum == 1); 395 396 static int intFun(int x) { return x + 1; } 397 assert(Some(1).map!intFun() == Some(2)); 398 assert(None!int().map!intFun() == None!int()); 399 400 assert(Some(1) .map!((int x) => x + 1) == Some(2)); 401 assert(None!int().map!((int x) => x + 1) == None!int()); 402 assert(Some(1) .map!"a + 1"() == Some(2)); 403 assert(None!int().map!"a + 1"() == None!int()); 404 405 // function passed to `map` should not return null 406 C nullFun(int i) { return null; } 407 assertThrown(Some(1).map!(nullFun)); 408 409 assert(Some!(const int)(1).map!((int x) => x + 1) == Some(2)); 410 assert(Some!(immutable int)(1).map!((int x) => x + 1) == Some(2)); 411 } 412 413 {// flatMap 414 Option!int fun(int i) { return Some(i+1); } 415 assert(Some(1) .flatMap!fun() == Some(2)); 416 assert(None!int().flatMap!fun() == None!int()); 417 418 assert(Some!(const int)(1).flatMap!fun() == Some(2)); 419 assert(Some!(immutable int)(1).flatMap!fun() == Some(2)); 420 } 421 422 {// method chaining 423 Option!int f1(int i) { return i % 2 == 0 ? Some(i / 2) : None!int(); } 424 Option!int f2(int i) { return i % 3 == 0 ? Some(i / 3) : None!int(); } 425 auto x = 10; 426 assert(Some(12).flatMap!f1.flatMap!f2.map!(i => to!string(i * x)).getOrElse("None") == "20"); // => "20" 427 } 428 429 {// detect element from array 430 auto array1 = [1, 2, 3, 4, 5]; 431 bool pred1(int i) { return i == 1; } 432 assert(array1.detect!pred1 == Some(1)); 433 assert(array1.detect!"a % 2 == 0" == Some(2)); 434 assert(array1.detect!(i => i > 7) == None!int()); 435 const array2 = array1; 436 assert(array2.detect!pred1 == Some(1)); 437 assert(array2.detect!"a % 2 == 0" == Some(2)); 438 assert(array2.detect!(i => i > 7) == None!int()); 439 immutable array3 = array1.idup; 440 assert(array3.detect!pred1 == Some(1)); 441 assert(array3.detect!"a % 2 == 0" == Some(2)); 442 assert(array3.detect!(i => i > 7) == None!int()); 443 } 444 445 {// flatten array of Option's 446 assert([None!int()] .flatten == []); 447 assert([Some(1), None!int(), Some(2)].flatten == [1, 2]); 448 assert([Some(1), Some(2), Some(3)] .flatten == [1, 2, 3]); 449 const array1 = [None!int(), Some(1)]; 450 assert(array1.flatten == [1]); 451 immutable array2 = [Some(1), Some(2)]; 452 assert(array2.flatten == [1, 2]); 453 } 454 455 {// fetch value from AA 456 int[string] aa1; 457 aa1["abc"] = 0; 458 assert(aa1.fetch("abc") == Some(0)); 459 assert(aa1.fetch("xyz") == None!int()); 460 const aa2 = aa1; 461 assert(aa2.fetch("abc") == Some(0)); 462 assert(aa2.fetch("xyz") == None!int()); 463 immutable aa3 = cast(immutable)aa1.dup; 464 assert(aa3.fetch("abc") == Some(0)); 465 assert(aa3.fetch("xyz") == None!int()); 466 } 467 468 {// arguments should be lazily evaluated 469 int i = 1; 470 assert(s2.method2(i, i++) == Some!(int)(s2.get._i + 2)); 471 assert(i == 2); 472 assert(n2.method2(i, i++) == None!(int)()); 473 assert(i == 2); 474 } 475 476 {// type conversion from derived class 477 auto d1 = new D1; 478 auto optD1 = Option!D1(new D1); 479 auto optD2 = Option!D2(new D2); 480 auto optD1AsC = Option!C (new D1); 481 auto optD2AsC = Option!C.fromRange([new D2]); 482 } 483 484 {// equality 485 assert(None!(int )() == None!(int )()); 486 assert(None!(int )() == None!(long)()); 487 assert(None!(long)() == None!(int )()); 488 assert(Some!(int )(1) != None!(int )()); 489 assert(None!(long)() != Some!(int )(1)); 490 assert(Some(1) == Some(1)); 491 assert(Some(1) == Some(1L)); 492 assert(Some(1) != Some(2)); 493 assert(Some(1) != Some(2L)); 494 495 auto d1 = new D1; 496 auto d2 = new D2; 497 assert(Some!C (d1) == Some!C (d1)); 498 assert(Some!C (d1) == Some!D1(d1)); 499 assert(Some!D1(d1) == Some!C (d1)); 500 assert(Some!D1(d1) == Some!D1(d1)); 501 assert(Some!C (d1) != Some!C (d2)); 502 } 503 504 {// foreach 505 int x = 0; 506 foreach(elem; Some("hello")) { x++; } 507 assert(x == 1); 508 foreach(elem; None!int()) { x++; } 509 assert(x == 1); 510 } 511 }