├── .editorconfig ├── .gitignore ├── .codecov.yml ├── compattests ├── dub.json └── source │ └── vibe.d ├── tests ├── tooptional.d ├── match.d ├── or.d ├── oc.d └── optional.d ├── .github └── workflows │ └── test-on-main.yml ├── .vscode └── tasks.json ├── LICENSE ├── .travis.yml ├── dub.json ├── source └── optional │ ├── traits.d │ ├── package.d │ ├── match.d │ ├── oc.d │ ├── or.d │ └── optional.d ├── History.md └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{d,json,css}] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | optional.so 5 | optional.dylib 6 | optional.dll 7 | optional.a 8 | optional.lib 9 | optional-test-* 10 | *.exe 11 | *.o 12 | *.obj 13 | *.lst 14 | bin/ 15 | dub.selections.json 16 | package-lock.json 17 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | after_n_builds: 1 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: 80...100 9 | 10 | status: 11 | project: true 12 | patch: off 13 | changes: 14 | default: 15 | threshold: 10% 16 | 17 | comment: false -------------------------------------------------------------------------------- /compattests/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optional-compattests", 3 | "authors": ["Ali Akhtarzada"], 4 | "description": "compat tests for optional", 5 | "copyright": "Copyright © 2021, Ali Akhtarzada", 6 | "targetPath": "bin", 7 | "license": "MIT", 8 | "dependencies": { 9 | "optional": { 10 | "path": "../" 11 | }, 12 | "vibe-d:data": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/tooptional.d: -------------------------------------------------------------------------------- 1 | module tests.tooptional; 2 | 3 | import optional; 4 | 5 | @("works with Nullable") 6 | unittest { 7 | import std.typecons: nullable; 8 | auto a = 3.nullable; 9 | assert(a.toOptional == some(3)); 10 | a.nullify; 11 | assert(a.toOptional == no!int); 12 | } 13 | 14 | @("works with other ranges") 15 | unittest { 16 | import std.algorithm: map; 17 | assert([1].map!(r => r).toOptional == some(1)); 18 | } 19 | -------------------------------------------------------------------------------- /compattests/source/vibe.d: -------------------------------------------------------------------------------- 1 | module compattests.vibe; 2 | 3 | import optional; 4 | 5 | @("Should serialize and deserialize to json with vibe.data.serialization") 6 | unittest { 7 | import vibe.data.json; 8 | import vibe.data.serialization; 9 | 10 | struct User { 11 | int a; 12 | Optional!string b; 13 | } 14 | 15 | auto user0 = User(1, some("boo")); 16 | auto json0 = `{"a":1,"b":"boo"}`.parseJsonString; 17 | auto json1 = user0.serializeToJson; 18 | auto user1 = json0.deserializeJson!User; 19 | 20 | assert(user0 == user1); 21 | assert(json0 == json1); 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/test-on-main.yml: -------------------------------------------------------------------------------- 1 | name: Run all D Tests! 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | test: 9 | name: Dub Tests 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macOS-latest] 13 | dc: [dmd-2.085.0, ldc-1.17.0] 14 | exclude: 15 | - { os: macOS-latest, dc: dmd-2.085.0 } 16 | 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Install D compiler 22 | uses: dlang-community/setup-dlang@v1 23 | with: 24 | compiler: ${{ matrix.dc }} 25 | 26 | - name: Run Safe tests 27 | run: dub test -c unittest-safe 28 | 29 | - name: Run compat tests 30 | run: dub --root=compattests test -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "test-all", 8 | "type": "shell", 9 | "command": "dub test -c unittest-safe && dub --root=compattests test", 10 | "group": { 11 | "kind": "test", 12 | "isDefault": true 13 | } 14 | }, 15 | { 16 | "label": "test", 17 | "type": "shell", 18 | "command": "dub test -c unittest-safe", 19 | "group": "test" 20 | }, 21 | { 22 | "label": "test-ldc-asan", 23 | "type": "shell", 24 | "command": "ASAN_OPTIONS=debug=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=1 dub test -c unittest-safe-asan --compiler=ldc2 -- -t 1", 25 | "group": "test" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ali Akhtarzada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | 3 | env: 4 | - ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=1:verbosity=2 5 | 6 | matrix: 7 | include: 8 | - d: dmd 9 | script: 10 | - dub test -b unittest-cov -c unittest-safe 11 | - dub --root=compattests test 12 | - git clone https://github.com/aliak00/ddox-dark-theme.git 13 | - mv ddox-dark-theme/docs . 14 | - dub build -b ddox 15 | addons: 16 | apt: 17 | packages: 18 | - libevent-dev 19 | - libssl-dev 20 | - pkg-config 21 | - zlib1g-dev 22 | after_success: bash <(curl -s https://codecov.io/bash) 23 | 24 | deploy: 25 | local_dir: docs 26 | provider: pages 27 | skip_cleanup: true 28 | github_token: $GITHUB_TOKEN 29 | on: 30 | branch: master 31 | 32 | cache: 33 | directories: 34 | - $HOME/.dub 35 | 36 | - d: dmd 37 | os: osx 38 | script: 39 | - dub test -c unittest-safe 40 | - dub test -c unittest-compat 41 | - d: ldc 42 | os: osx 43 | script: 44 | - dub test -c unittest-safe -- -t 1 45 | - d: ldc 46 | script: 47 | - dub test -c unittest-safe -- -t 1 -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optional", 3 | "authors": ["Ali Akhtarzada"], 4 | "description": "An optional/maybe type with safe dispatchingrange semantics", 5 | "copyright": "Copyright © 2018, Ali Akhtarzada", 6 | "license": "MIT", 7 | "targetPath": "bin", 8 | "dependencies": { 9 | "bolts": "~>1.3.0" 10 | }, 11 | "configurations": [ 12 | { 13 | "name": "library", 14 | "targetType": "library" 15 | }, 16 | { 17 | "name": "unittest", 18 | "importPaths": ["./tests"], 19 | "sourcePaths": ["./tests"] 20 | }, 21 | { 22 | "name": "unittest-safe", 23 | "buildType": "unittest", 24 | "importPaths": ["./tests"], 25 | "sourcePaths": ["./tests"], 26 | "dflags": ["-dip1000", "-dip25"] 27 | }, 28 | { 29 | "name": "unittest-safe-asan", 30 | "buildType": "unittest", 31 | "importPaths": ["./tests"], 32 | "sourcePaths": ["./tests"], 33 | "dflags": ["-dip1000", "-dip25"], 34 | "dflags-ldc": ["-fsanitize=address", "-frame-pointer=all"] 35 | } 36 | ], 37 | "buildTypes": { 38 | "unittest-release": { 39 | "buildOptions": ["releaseMode", "optimize", "inline", "unittests"] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/match.d: -------------------------------------------------------------------------------- 1 | module tests.match; 2 | 3 | import optional; 4 | 5 | @("Should work with qualified optionals") 6 | @safe @nogc unittest { 7 | import std.meta: AliasSeq; 8 | foreach (T; AliasSeq!(Optional!int, const Optional!int, immutable Optional!int)) { 9 | T a = some(3); 10 | auto r = a.match!( 11 | (int a) => "yes", 12 | () => "no", 13 | ); 14 | assert(r == "yes"); 15 | } 16 | } 17 | 18 | @("Should work with void return") 19 | unittest { 20 | int i = 0; 21 | int j = 0; 22 | 23 | auto yes = some(1); 24 | auto no = no!int; 25 | 26 | void fun(int x) { j = x; } 27 | 28 | yes.match!( 29 | (int a) => i = a, 30 | () => fun(3), 31 | ); 32 | 33 | assert(i == 1); 34 | assert(j == 0); 35 | 36 | no.match!( 37 | (int a) => i = a + 1, 38 | () => fun(3), 39 | ); 40 | 41 | assert(i == 1); 42 | assert(j == 3); 43 | 44 | 45 | } 46 | 47 | @("Should work with mutable references") 48 | unittest { 49 | static class MyClass { 50 | int x = 0; 51 | } 52 | 53 | auto obj = some(new MyClass); 54 | int val; 55 | obj.match!( 56 | (obj) => val = (obj.x = 1), 57 | () => val = 0, 58 | ); 59 | 60 | assert(val == 1); 61 | } 62 | 63 | @("Works on ranges") 64 | @safe @nogc unittest { 65 | import std.algorithm: joiner; 66 | auto a = some(some(5)); 67 | const result = a.joiner.match!( 68 | b => b + 1, 69 | () => 0 70 | ); 71 | 72 | assert(result == 6); 73 | } 74 | -------------------------------------------------------------------------------- /source/optional/traits.d: -------------------------------------------------------------------------------- 1 | /** 2 | Optional compile time traits 3 | */ 4 | module optional.traits; 5 | 6 | /// Checks if T is an optional type 7 | template isOptional(T) { 8 | import optional: Optional; 9 | import std.traits: isInstanceOf; 10 | enum isOptional = isInstanceOf!(Optional, T); 11 | } 12 | 13 | /// 14 | @("Example of isOptional") 15 | unittest { 16 | import optional: Optional; 17 | 18 | assert(isOptional!(Optional!int) == true); 19 | assert(isOptional!int == false); 20 | assert(isOptional!(int[]) == false); 21 | } 22 | 23 | /// Returns the target type of a optional. 24 | template OptionalTarget(T) if (isOptional!T) { 25 | import std.traits: TemplateArgsOf; 26 | alias OptionalTarget = TemplateArgsOf!T[0]; 27 | } 28 | 29 | /// 30 | @("Example of OptionalTarget") 31 | unittest { 32 | import optional: Optional; 33 | 34 | class C {} 35 | struct S {} 36 | 37 | import std.meta: AliasSeq; 38 | foreach (T; AliasSeq!(int, int*, S, C, int[], S[], C[])) { 39 | alias CT = const T; 40 | alias IT = immutable T; 41 | alias ST = shared T; 42 | 43 | static assert(is(OptionalTarget!(Optional!T) == T)); 44 | static assert(is(OptionalTarget!(Optional!CT) == CT)); 45 | static assert(is(OptionalTarget!(Optional!IT) == IT)); 46 | static assert(is(OptionalTarget!(Optional!ST) == ST)); 47 | } 48 | } 49 | 50 | /// Checks if T is an optional chain 51 | template isOptionalChain(T) { 52 | import optional: OptionalChain; 53 | import std.traits: isInstanceOf; 54 | enum isOptionalChain = isInstanceOf!(OptionalChain, T); 55 | } 56 | 57 | /// 58 | @("Example of isOptionalChain") 59 | @safe @nogc unittest { 60 | import optional: oc, some; 61 | static assert(isOptionalChain!(typeof(oc(some(3))))); 62 | } 63 | -------------------------------------------------------------------------------- /source/optional/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Home of the `Optional` type 3 | */ 4 | module optional; 5 | 6 | /// 7 | @("Example 1") 8 | unittest { 9 | import std.algorithm: equal; 10 | 11 | // Create empty optional 12 | auto a = no!int; 13 | 14 | // Operating on an empty optional is safe and results in none 15 | assert(a == none); 16 | assert(++a == none); 17 | 18 | // Assigning a value and then operating yields results 19 | a = 9; 20 | assert(a == some(9)); 21 | assert(++a == some(10)); 22 | 23 | // It is a range 24 | import std.algorithm: map; 25 | auto b = some(10); 26 | auto c = no!int; 27 | assert(b.map!(a => a * 2).equal([20])); 28 | assert(c.map!(a => a * 2).empty); 29 | 30 | // Safely get the inner value 31 | assert(b.frontOr(3) == 10); 32 | assert(c.frontOr(3) == 3); 33 | 34 | // Unwrap to get to the raw data (returns a non-null pointer or reference if there's data) 35 | class C { 36 | int i = 3; 37 | } 38 | 39 | auto n = no!C; 40 | n.or!(() => n = some!C(null)); 41 | assert(n == none); 42 | n.or!(() => n = new C()); 43 | assert(n.front !is null); 44 | assert(n.front.i == 3); 45 | } 46 | 47 | /// Phobos equvalent range.only test 48 | @("Example 2") 49 | unittest { 50 | import std.algorithm: filter, joiner, map, equal; 51 | import std.uni: isUpper; 52 | 53 | assert(equal(some('♡'), "♡")); 54 | 55 | string title = "The D Programming Language"; 56 | assert(title 57 | .filter!isUpper // take the upper case letters 58 | .map!some // make each letter its own range 59 | .joiner(".") // join the ranges together lazily 60 | .equal("T.D.P.L")); 61 | } 62 | 63 | public { 64 | import optional.optional; 65 | import optional.traits; 66 | import optional.oc; 67 | import optional.or; 68 | import optional.match; 69 | } 70 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | ================== 3 | 4 | * Fixed: Make result of optional chanining act like range 5 | 6 | 7 | 1.1.0 8 | ================== 9 | 10 | * Changed: match will work on input ranges 11 | * Changed: compile-time error detection on match 12 | * Fixed: make match take auto ref instead of inout 13 | * Fixed: removed implicit calls to nullable.get 14 | 15 | 16 | 1.0.0-beta.1 / 2019-07-29 17 | ================== 18 | 19 | Big changes for version 1.0.0 20 | 21 | * Added: dip1000/dip25 conformance 22 | * Added: Can use optionals with @disabled copy types 23 | * Added: Tests for vibe-d integration 24 | * Added: Address sanitization to testing 25 | * Added: frontOr that works on ranges, optionals, and nullables 26 | * Changed: renamed `orElse` to `or` 27 | * Changed: renamed `dispatch` to `oc` 28 | * Changed: `or` will return only rhs or lhs, and not front of optional 29 | * Changed: `or` now accepts ranges, nullables, and references 30 | * Changed: `or` accepts void lambda 31 | * Changed: `match` returns void if either lambda returns void 32 | * Changed: pointer semantics to be the same as class semantics (i.e. `some!(int*)(null) == none`) 33 | * Fixed: Unsafe return values 34 | * Removed: NotNull 35 | * Removed: unwrap 36 | 37 | 38 | 0.10.0 / 2019-01-14 39 | ================== 40 | 41 | * Changed: make initial Optional!T state all zeros 42 | * Fixed: assert when front called on empty optional 43 | 44 | 0.9.0 / 2018-12-18 45 | ================== 46 | 47 | * Changed: make or value lazy 48 | * Added: predicate version of or 49 | 50 | v0.8.0 / 2018-12-11 51 | ================== 52 | 53 | * Added: betterC support PR #28 from @skoppe 54 | 55 | v0.7.4 / 2018-11-15 56 | =================== 57 | 58 | * Fixed: empty state when assigning another optional 59 | 60 | v0.7.3 / 2018-10-29 61 | =================== 62 | 63 | * Changed: pass range as auto ref to opEquals 64 | 65 | v0.7.2 / 2018-10-29 66 | =================== 67 | 68 | * Added: Allow optional to be compared with other ranges 69 | 70 | v0.7.0 / 2018-09-21 71 | ================== 72 | 73 | * Changed: dispatch does not mutate value types 74 | * Changed: dispatching is automatically flattened 75 | * Added: support for array indexing and slicing 76 | * Added: support for opOpAssign 77 | * Added: safe pure nogc dispatch 78 | * Added: dispatching on non optional nullable types 79 | * Added: covariant assignment to optional 80 | 81 | v0.6.0 / 2018-08-24 82 | =================== 83 | 84 | * Changed: Allow qualified optionals be used on free functions 85 | * Fixed: Don't call destroy on reference types when setting to none 86 | 87 | v0.5.0 / 2018-08-16 88 | =================== 89 | 90 | * Changed: Make dispatch return optional 91 | * Changed: Undo double optional chaining 92 | * Added: a contruct function for objects that can't copy 93 | * Added: opCall 94 | * Fixed: Return auto ref 95 | * Fixed: Use template this to maintain qualified this 96 | * Fixed: copmile for @disabled copy and nested struct init 97 | 98 | -------------------------------------------------------------------------------- /source/optional/match.d: -------------------------------------------------------------------------------- 1 | /** 2 | Provides a match function for optional types 3 | */ 4 | module optional.match; 5 | 6 | import optional.optional; 7 | 8 | /** 9 | Calls an appropriate handler depending on if the optional has a value or not 10 | 11 | If either handler returns void, the return type of match is void. 12 | 13 | Params: 14 | opt = The optional to call match on 15 | handlers = 2 predicates, one that takes the underlying optional type and another that names nothing 16 | */ 17 | public template match(handlers...) if (handlers.length == 2) { 18 | auto match(O)(auto ref O opt) { 19 | import optional.traits: isOptionalChain, isOptional; 20 | import std.range: ElementType, isInputRange; 21 | static if (isOptionalChain!O) { 22 | return .match!handlers(opt.value); 23 | // Check for isOptional as well because a const(Option!T) is not an input range, but it is an optional. 24 | } else static if (isInputRange!O || isOptional!O) { 25 | alias T = ElementType!O; 26 | static if (is(typeof(handlers[0](opt.front)))) { 27 | alias someHandler = handlers[0]; 28 | alias noHandler = handlers[1]; 29 | return doMatch!(someHandler, noHandler, T)(opt); 30 | } else static if (is(typeof(handlers[0]()))) { 31 | alias someHandler = handlers[1]; 32 | alias noHandler = handlers[0]; 33 | return doMatch!(someHandler, noHandler, T)(opt); 34 | } else { 35 | // One of these two is causing a compile error. 36 | // Let's call them so the compiler can show a proper error warning. 37 | failOnCompileError!(handlers[0], T); 38 | failOnCompileError!(handlers[1], T); 39 | } 40 | } else { 41 | static assert(0, "Cannot match!() on a " ~ O.stringof); 42 | } 43 | } 44 | } 45 | 46 | private auto doMatch(alias someHandler, alias noHandler, T, O)(auto ref O opt) { 47 | alias SomeHandlerReturn = typeof(someHandler(T.init)); 48 | alias NoHandlerReturn = typeof(noHandler()); 49 | enum isVoidReturn = is(typeof(someHandler(T.init)) == void) || is(typeof(noHandler()) == void); 50 | 51 | static assert( 52 | is(SomeHandlerReturn == NoHandlerReturn) || isVoidReturn, 53 | "Expected two handlers to return same type, found type '" ~ SomeHandlerReturn.stringof ~ "' and type '" ~ NoHandlerReturn.stringof ~ "'", 54 | ); 55 | 56 | if (opt.empty) { 57 | static if (isVoidReturn) { 58 | noHandler(); 59 | } else { 60 | return noHandler(); 61 | } 62 | } else { 63 | static if (isVoidReturn) { 64 | someHandler(opt.front); 65 | } else { 66 | return someHandler(opt.front); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | Prints out the correct compile error if the handler cannot be compiled. 73 | */ 74 | private void failOnCompileError(alias handler, T)() { 75 | static if (!is(typeof(handler(T.init))) && !is(typeof(handler()))) { 76 | cast(void) handler(T.init); 77 | } 78 | } 79 | 80 | /// 81 | @("Example of match()") 82 | @nogc @safe unittest { 83 | auto a = some(3); 84 | auto b = no!int; 85 | 86 | auto ra = a.match!( 87 | (int a) => "yes", 88 | () => "no", 89 | ); 90 | 91 | auto rb = b.match!( 92 | (a) => "yes", 93 | () => "no", 94 | ); 95 | 96 | assert(ra == "yes"); 97 | assert(rb == "no"); 98 | } 99 | -------------------------------------------------------------------------------- /tests/or.d: -------------------------------------------------------------------------------- 1 | module tests.orelse; 2 | 3 | import optional; 4 | import std.algorithm.comparison: equal; 5 | 6 | @("works with qualified optionals") 7 | unittest { 8 | import std.meta: AliasSeq; 9 | alias T = string; 10 | foreach (U; AliasSeq!(T, const T, immutable T)) { 11 | foreach (V; AliasSeq!(Optional!T, const Optional!T, immutable Optional!T)) { 12 | V a = some("hello"); 13 | T t = "world"; 14 | assert(a.frontOr("x") == "hello"); 15 | assert(t.frontOr('x') == 'w'); 16 | assert(a.or(some(t)) == a); 17 | assert(t.or("x") == "world"); 18 | } 19 | } 20 | } 21 | 22 | @("works with lambdas") 23 | unittest { 24 | auto a = some("hello"); 25 | auto b = no!string; 26 | assert(a.frontOr!(() => "world") == "hello"); 27 | assert(b.frontOr!(() => "world") == "world"); 28 | assert(a.or!(() => b) == a); 29 | assert(b.or!(() => a) == a); 30 | } 31 | 32 | @("works with strings") 33 | unittest { 34 | assert((cast(string)null).frontOr('h') == 'h'); 35 | assert("yo".frontOr('h') == 'y'); 36 | assert("".frontOr('x') == 'x'); 37 | assert((cast(string)null).or("hi") == "hi"); 38 | assert("yo".or("hi") == "yo"); 39 | assert("".or("x") == "x"); 40 | } 41 | 42 | @("range to mapped and mapped to range") 43 | unittest { 44 | import std.algorithm: map; 45 | auto r0 = [1, 2].or([1, 2].map!"a * 2"); 46 | assert(r0.equal([1, 2])); 47 | auto r1 = (int[]).init.or([1, 2].map!"a * 2"); 48 | assert(r1.equal([2, 4])); 49 | 50 | auto r2 = [1, 2].map!"a * 2".or([1, 2]); 51 | assert(r2.equal([2, 4])); 52 | auto r3 = (int[]).init.map!"a * 2".or([1, 2]); 53 | assert(r3.equal([1, 2])); 54 | } 55 | 56 | @("frontOr should work with Nullable") 57 | unittest { 58 | import std.typecons: nullable; 59 | auto a = "foo".nullable; 60 | assert(a.frontOr("bar") == "foo"); 61 | a.nullify; 62 | assert(a.frontOr("bar") == "bar"); 63 | } 64 | 65 | @("or should work with Nullable") 66 | unittest { 67 | import std.typecons: nullable, Nullable; 68 | auto a = "foo".nullable; 69 | auto b = Nullable!string(); 70 | assert(a.or(b) == a.get); 71 | assert(b.or(a) == a.get); 72 | } 73 | 74 | @("should work with mapping") 75 | unittest { 76 | import std.algorithm: map; 77 | import std.conv: to; 78 | auto a = [3].map!(to!string).or([""]); 79 | assert(a.equal(["3"])); 80 | } 81 | 82 | @("should work with two ranges") 83 | unittest { 84 | import std.typecons: tuple; 85 | import std.algorithm: map; 86 | auto func() { 87 | return [1, 2, 3].map!(a => tuple(a, a)); 88 | } 89 | assert(func().or(func()).equal(func())); 90 | } 91 | 92 | @("should work with class types") 93 | unittest { 94 | static class C {} 95 | 96 | auto a = new C(); 97 | auto b = new C(); 98 | C c = null; 99 | 100 | assert(a.or(b) == a); 101 | assert(c.or(b) == b); 102 | } 103 | 104 | @("should work with void callbacks") 105 | @nogc @safe unittest { 106 | int a = 0; 107 | auto b = no!int; 108 | b.or!(() => cast(void)(a = 3)); 109 | assert(a == 3); 110 | b = 3; 111 | b.or!(() => cast(void)(a = 7)); 112 | assert(a == 3); 113 | } 114 | 115 | 116 | @("should throw an OrElseException if the exception factory throws") 117 | @safe unittest { 118 | import std.exception: assertThrown; 119 | 120 | int boo() {throw new Exception(""); } 121 | 122 | "" 123 | .frontOrThrow!(() { boo; return new Exception(""); } ) 124 | .assertThrown!FrontOrThrowException; 125 | } 126 | 127 | @("Should throw exception if range empty") 128 | @safe unittest { 129 | import std.exception: assertThrown, assertNotThrown; 130 | import std.range: iota; 131 | 132 | 0.iota(0) 133 | .frontOrThrow(new Exception("")) 134 | .assertThrown!Exception; 135 | 136 | 0.iota(1) 137 | .frontOrThrow(new Exception("")) 138 | .assertNotThrown!Exception; 139 | } 140 | 141 | @("Should throw if nullable isNull") 142 | @safe unittest { 143 | import std.exception: assertThrown, assertNotThrown; 144 | import std.typecons: nullable; 145 | 146 | auto a = "foo".nullable; 147 | 148 | a.frontOrThrow(new Exception("")) 149 | .assertNotThrown!Exception; 150 | 151 | a.nullify; 152 | 153 | a.frontOrThrow(new Exception("")) 154 | .assertThrown!Exception; 155 | } 156 | 157 | @("or should work with rhs or lhs of null") 158 | unittest { 159 | auto a = "hello".or(null); 160 | auto b = null.or("hello"); 161 | assert(a == "hello"); 162 | assert(b == "hello"); 163 | 164 | auto c = "".or(null); 165 | auto d = null.or(""); 166 | assert(c == null); 167 | assert(d == ""); 168 | } 169 | 170 | @("Should work with optional chains") 171 | @safe unittest { 172 | import optional: oc; 173 | static class Class { 174 | int i; 175 | this(int i) { 176 | this.i = i; 177 | } 178 | } 179 | 180 | auto a = no!Class; 181 | auto b = some(new Class(3)); 182 | 183 | const x = oc(a).i.frontOr(7); 184 | const y = oc(b).i.frontOr(7); 185 | 186 | assert(x == 7); 187 | assert(y == 3); 188 | } 189 | -------------------------------------------------------------------------------- /source/optional/oc.d: -------------------------------------------------------------------------------- 1 | /** 2 | Provides safe dispatching utilities 3 | */ 4 | module optional.oc; 5 | 6 | import optional.optional: Optional; 7 | import std.typecons: Nullable; 8 | import bolts.from; 9 | 10 | private string autoReturn(string expression)() { 11 | return ` 12 | auto ref expr() { 13 | return ` ~ expression ~ `; 14 | } 15 | ` ~ q{ 16 | import optional.traits: isOptional; 17 | auto ref val() { 18 | // If the dispatched result is an Optional itself, we flatten it out so that client code 19 | // does not have to do a.oc.member.oc.otherMember 20 | static if (isOptional!(typeof(expr()))) { 21 | return expr().front; 22 | } else { 23 | return expr(); 24 | } 25 | } 26 | alias R = typeof(val()); 27 | static if (is(R == void)) { 28 | if (!value.empty) { 29 | val(); 30 | } 31 | } else { 32 | if (value.empty) { 33 | return OptionalChain!R(no!R()); 34 | } 35 | static if (isOptional!(typeof(expr()))) { 36 | // If the dispatched result is an optional, check if the expression is empty before 37 | // calling val() because val() calls .front which would assert if empty. 38 | if (expr().empty) { 39 | return OptionalChain!R(no!R()); 40 | } 41 | } 42 | return OptionalChain!R(some(val())); 43 | } 44 | }; 45 | } 46 | 47 | package struct OptionalChain(T) { 48 | import std.traits: hasMember; 49 | 50 | public Optional!T value; 51 | alias value this; 52 | 53 | this(Optional!T value) { 54 | this.value = value; 55 | } 56 | 57 | this(T value) { 58 | this.value = value; 59 | } 60 | 61 | static if (!hasMember!(T, "toString")) { 62 | public string toString()() { 63 | return value.toString; 64 | } 65 | } 66 | 67 | static if (hasMember!(T, "empty")) { 68 | public auto empty() { 69 | if (value.empty) { 70 | return no!(typeof(T.empty)); 71 | } else { 72 | return some(value.front.empty); 73 | } 74 | } 75 | } else { 76 | public auto empty() { 77 | return value.empty; 78 | } 79 | } 80 | 81 | static if (hasMember!(T, "front")) { 82 | public auto front() { 83 | if (value.empty) { 84 | return no!(typeof(T.front)); 85 | } else { 86 | return some(value.front.front); 87 | } 88 | } 89 | } else { 90 | public auto front() { 91 | return value.front; 92 | } 93 | } 94 | 95 | static if (hasMember!(T, "popFront")) { 96 | public auto popFront() { 97 | if (value.empty) { 98 | return no!(typeof(T.popFront)); 99 | } else { 100 | return some(value.front.popFront); 101 | } 102 | } 103 | } else { 104 | public auto popFront() { 105 | return value.popFront; 106 | } 107 | } 108 | 109 | public template opDispatch(string name) if (hasMember!(T, name)) { 110 | import optional: no, some; 111 | static if (is(typeof(__traits(getMember, T, name)) == function)) { 112 | auto opDispatch(Args...)(auto ref Args args) { 113 | mixin(autoReturn!("value.front." ~ name ~ "(args)")); 114 | } 115 | } else static if (__traits(isTemplate, mixin("T." ~ name))) { 116 | // member template 117 | template opDispatch(Ts...) { 118 | enum targs = Ts.length ? "!Ts" : ""; 119 | auto opDispatch(Args...)(auto ref Args args) { 120 | mixin(autoReturn!("value.front." ~ name ~ targs ~ "(args)")); 121 | } 122 | } 123 | } else { 124 | // non-function field 125 | auto opDispatch(Args...)(auto ref Args args) { 126 | static if (Args.length == 0) { 127 | mixin(autoReturn!("value.front." ~ name)); 128 | } else static if (Args.length == 1) { 129 | mixin(autoReturn!("value.front." ~ name ~ " = args[0]")); 130 | } else { 131 | static assert( 132 | 0, 133 | "Dispatched " ~ T.stringof ~ "." ~ name ~ " was resolved to non-function field that has more than one argument", 134 | ); 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | /** 142 | Allows you to call dot operator on a nullable type or an optional. 143 | 144 | If there is no value inside, or it is null, dispatching will still work but will 145 | produce a series of no-ops. 146 | 147 | Works with `std.typecons.Nullable` 148 | 149 | If you try and call a manifest constant or static data on T then whether the manifest 150 | or static immutable data is called depends on if the instance is valid. 151 | 152 | Returns: 153 | A type aliased to an Optional of whatever T.blah would've returned. 154 | --- 155 | struct A { 156 | struct Inner { 157 | int g() { return 7; } 158 | } 159 | Inner inner() { return Inner(); } 160 | int f() { return 4; } 161 | } 162 | auto a = some(A()); 163 | auto b = no!A; 164 | auto c = no!(A*); 165 | oc(a).inner.g; // calls inner and calls g 166 | oc(b).inner.g; // no op. 167 | oc(c).inner.g; // no op. 168 | --- 169 | */ 170 | auto oc(T)(auto ref T value) if (from.bolts.traits.isNullTestable!T) { 171 | return OptionalChain!T(value); 172 | } 173 | /// Ditto 174 | auto oc(T)(auto ref Optional!T value) { 175 | return OptionalChain!T(value); 176 | } 177 | /// Ditto 178 | auto oc(T)(auto ref Nullable!T value) { 179 | import optional: no; 180 | if (value.isNull) { 181 | return OptionalChain!T(no!T); 182 | } 183 | return OptionalChain!T(value.get); 184 | } 185 | -------------------------------------------------------------------------------- /tests/oc.d: -------------------------------------------------------------------------------- 1 | module tests.oc; 2 | 3 | import std.stdio; 4 | 5 | import optional; 6 | 7 | class Class { 8 | int i = 0; 9 | 10 | this(int i) @nogc @safe pure { this.i = i;} 11 | int getI() @nogc @safe pure { return i;} 12 | void setI(int i) @nogc @safe pure { this.i = i; } 13 | 14 | Struct getStruct() @nogc @safe pure { return Struct(this.i); } 15 | Class getClass() @safe pure { return new Class(this.i); } 16 | 17 | Struct* getStructRef() @safe pure { return new Struct(this.i); } 18 | Class getNullClass() @nogc @safe pure { return null; } 19 | Struct* getNullStruct() @nogc @safe pure { return null; } 20 | } 21 | 22 | struct Struct { 23 | int i = 0; 24 | 25 | this(int i) @nogc @safe pure { this.i = i;} 26 | int getI() @nogc @safe pure { return i;} 27 | void setI(int i) @nogc @safe pure { this.i = i; } 28 | 29 | Struct getStruct() @nogc @safe pure { return Struct(this.i);} 30 | Class getClass() @safe pure { return new Class(this.i); } 31 | 32 | Struct* getStructRef() @safe pure { return new Struct(this.i);} 33 | Class getNullClass() @nogc @safe pure { return null; } 34 | Struct* getNullStruct() @nogc @safe pure { return null; } 35 | } 36 | 37 | @("Should oc multiple functions of a reference type") 38 | @safe unittest { 39 | auto a = no!Class; 40 | auto b = some(new Class(3)); 41 | 42 | assert(oc(a).getI == no!int()); 43 | assert(oc(b).getI == some(3)); 44 | 45 | oc(a).setI(7); 46 | oc(b).setI(7); 47 | 48 | assert(oc(a).getClass.i == no!int); 49 | assert(oc(b).getClass.i == some(7)); 50 | } 51 | 52 | @("Should oc a function of a reference type") 53 | @safe unittest { 54 | Class a; 55 | Class b = new Class(3); 56 | 57 | assert(oc(a).getI == no!int()); 58 | assert(oc(b).getI == some(3)); 59 | 60 | assert(b.i == 3); 61 | 62 | oc(a).setI(5); 63 | oc(b).setI(5); 64 | 65 | assert(b.i == 5); 66 | } 67 | 68 | @("Should oc multiple functions of a pointer type") 69 | @safe unittest { 70 | auto a = no!(Struct*); 71 | auto b = some(new Struct(3)); 72 | 73 | assert(oc(a).getI == no!int()); 74 | assert(oc(b).getI == some(3)); 75 | 76 | oc(a).setI(7); 77 | oc(b).setI(7); 78 | 79 | assert(oc(a).getStruct.i == no!int); 80 | assert(oc(b).getStruct.i == some(7)); 81 | } 82 | 83 | @("Should oc a function of a pointer type") 84 | @safe unittest { 85 | Struct* a; 86 | Struct* b = new Struct(3); 87 | 88 | assert(oc(a).getI == no!int()); 89 | assert(oc(b).getI == some(3)); 90 | 91 | assert(b.i == 3); 92 | 93 | oc(a).setI(5); 94 | oc(b).setI(5); 95 | 96 | assert(b.i == 5); 97 | } 98 | 99 | @("Should oc to different member types") 100 | @safe @nogc unittest { 101 | struct A { 102 | enum aManifestConstant = "aManifestConstant"; 103 | static immutable aStaticImmutable = "aStaticImmutable"; 104 | auto aField = "aField"; 105 | auto aNonTemplateFunctionArity0() { 106 | return "aNonTemplateFunctionArity0"; 107 | } 108 | auto aNonTemplateFunctionArity1(string value) { 109 | return "aNonTemplateFunctionArity1"; 110 | } 111 | string aTemplateFunctionArity0()() { 112 | return "aTemplateFunctionArity0"; 113 | } 114 | string aTemplateFunctionArity1(string T)() { 115 | return "aTemplateFunctionArity1"; 116 | } 117 | string oc() { 118 | return "oc"; 119 | } 120 | } 121 | 122 | auto a = some(A()); 123 | auto b = no!A; 124 | assert(oc(a).aField == some("aField")); 125 | assert(oc(b).aField == no!string); 126 | assert(oc(a).aNonTemplateFunctionArity0 == some("aNonTemplateFunctionArity0")); 127 | assert(oc(b).aNonTemplateFunctionArity0 == no!string); 128 | assert(oc(a).aNonTemplateFunctionArity1("") == some("aNonTemplateFunctionArity1")); 129 | assert(oc(b).aNonTemplateFunctionArity1("") == no!string); 130 | assert(oc(a).aTemplateFunctionArity0 == some("aTemplateFunctionArity0")); 131 | assert(oc(b).aTemplateFunctionArity0 == no!string); 132 | assert(oc(a).aTemplateFunctionArity1!("") == some("aTemplateFunctionArity1")); 133 | assert(oc(b).aTemplateFunctionArity1!("") == no!string); 134 | assert(oc(a).oc == some("oc")); 135 | assert(oc(b).oc == no!string); 136 | assert(oc(a).aManifestConstant == some("aManifestConstant")); 137 | assert(oc(b).aManifestConstant == no!string); 138 | assert(oc(a).aStaticImmutable == some("aStaticImmutable")); 139 | assert(oc(b).aStaticImmutable == no!string); 140 | } 141 | 142 | @("Should work for all qualifiers") 143 | @safe unittest { 144 | import optional: Optional, none; 145 | 146 | class A { 147 | void nonConstNonSharedMethod() {} 148 | void constMethod() const {} 149 | void sharedNonConstMethod() shared {} 150 | void sharedConstMethod() shared const {} 151 | } 152 | 153 | alias IA = immutable A; 154 | alias CA = const A; 155 | alias SA = shared A; 156 | alias SCA = shared const A; 157 | 158 | Optional!IA ia = new IA; 159 | Optional!CA ca = new CA; 160 | Optional!SA sa = new SA; 161 | Optional!SCA sca = new SA; 162 | 163 | static assert(!__traits(compiles, () { oc(ia).nonConstNonSharedMethod; } )); 164 | static assert(!__traits(compiles, () { oc(ca).nonConstNonSharedMethod; } )); 165 | static assert(!__traits(compiles, () { oc(sa).nonConstNonSharedMethod; } )); 166 | static assert(!__traits(compiles, () { oc(sca).nonConstNonSharedMethod; } )); 167 | 168 | static assert( __traits(compiles, () { oc(ia).constMethod; } )); 169 | static assert( __traits(compiles, () { oc(ca).constMethod; } )); 170 | static assert(!__traits(compiles, () { oc(sa).constMethod; } )); 171 | static assert(!__traits(compiles, () { oc(sca).constMethod; } )); 172 | 173 | static assert(!__traits(compiles, () { oc(ia).sharedNonConstMethod; } )); 174 | static assert(!__traits(compiles, () { oc(ca).sharedNonConstMethod; } )); 175 | static assert( __traits(compiles, () { oc(sa).sharedNonConstMethod; } )); 176 | static assert(!__traits(compiles, () { oc(sca).sharedNonConstMethod; } )); 177 | 178 | static assert( __traits(compiles, () { oc(ia).sharedConstMethod; } )); 179 | static assert(!__traits(compiles, () { oc(ca).sharedConstMethod; } )); 180 | static assert( __traits(compiles, () { oc(sa).sharedConstMethod; } )); 181 | static assert( __traits(compiles, () { oc(sca).sharedConstMethod; } )); 182 | } 183 | 184 | @("Should be safe nogc and pure") 185 | @nogc @safe pure unittest { 186 | auto a = some(Struct(7)); 187 | auto b = no!Struct; 188 | assert(oc(a).i == some(7)); 189 | assert(oc(a).getI == some(7)); 190 | assert(oc(a).getStruct.i == some(7)); 191 | assert(oc(b).i == no!int); 192 | assert(oc(b).getI == no!int); 193 | assert(oc(b).getStruct.i == no!int); 194 | } 195 | 196 | @("Should be safe with null pointer members") 197 | @safe unittest { 198 | struct B { 199 | int f() { 200 | return 8; 201 | } 202 | int m = 3; 203 | } 204 | struct A { 205 | B* b_; 206 | B* b() { 207 | return b_; 208 | } 209 | } 210 | 211 | auto a = some(new Struct(3)); 212 | auto b = some(new Struct(7)); 213 | 214 | assert(oc(a).getStruct.getStructRef.i == some(3)); 215 | assert(oc(a).getStruct.getStructRef.getI == some(3)); 216 | 217 | assert(oc(b).getStruct.getNullStruct.i == no!int); 218 | assert(oc(b).getStruct.getNullStruct.getI == no!int); 219 | } 220 | 221 | @("Should chain template functions") 222 | unittest { 223 | class C { 224 | void method() {} 225 | void tmethod(T)() {} 226 | } 227 | auto c = some(new C()); 228 | 229 | static assert(__traits(compiles, oc(c).method())); 230 | static assert(__traits(compiles, oc(c).tmethod!int())); 231 | } 232 | 233 | @("Should flatten inner optional members") 234 | @safe unittest { 235 | class Residence { 236 | auto numberOfRooms = 1; 237 | } 238 | class Person { 239 | Optional!Residence residence; 240 | } 241 | 242 | auto john = some(new Person()); 243 | auto n = oc(john).residence.numberOfRooms; 244 | 245 | assert(n == no!int); 246 | 247 | oc(john).residence = new Residence(); 248 | n = oc(john).residence.numberOfRooms; 249 | 250 | assert(n == some(1)); 251 | } 252 | 253 | @("Should use Optional.toString") 254 | @safe unittest { 255 | assert(some(Struct(3)).oc.i.toString == "[3]"); 256 | } 257 | 258 | @("Should work with some deep nesting") 259 | @safe unittest { 260 | assert( 261 | some(new Class(10)) 262 | .oc 263 | .getStruct 264 | .getClass 265 | .getStructRef 266 | .i == some(10) 267 | ); 268 | 269 | assert( 270 | some(new Class(10)) 271 | .oc 272 | .getNullStruct 273 | .getNullClass 274 | .getNullClass 275 | .i == no!int 276 | ); 277 | } 278 | 279 | @("Should work on std.typecons.Nullable") 280 | @safe @nogc unittest { 281 | import std.typecons; 282 | auto a = nullable(Struct(3)); 283 | auto b = Nullable!Struct.init; 284 | 285 | assert(oc(a).i == some(3)); 286 | assert(oc(b).i == no!int); 287 | } 288 | 289 | @("Result of optional chain must be pattern matchable") 290 | @safe @nogc unittest { 291 | static struct TypeA { 292 | string x; 293 | } 294 | static struct TypeB { 295 | auto getValue() { 296 | return TypeA("yes"); 297 | } 298 | } 299 | auto b = some(TypeB()); 300 | const result = oc(b).getValue().match!( 301 | (a) => a.x, 302 | () => "no" 303 | ); 304 | assert(result == "yes"); 305 | } 306 | -------------------------------------------------------------------------------- /source/optional/or.d: -------------------------------------------------------------------------------- 1 | /** 2 | Gets the value or else something else 3 | */ 4 | module optional.or; 5 | 6 | import bolts.traits: isNullTestable; 7 | import std.typecons: Nullable; 8 | import std.range: isInputRange; 9 | import optional.traits: isOptional, isOptionalChain; 10 | 11 | private enum isTypeconsNullable(T) = is(T : Nullable!U, U); 12 | private auto ret(ElseType, T)(auto ref T v) { 13 | static if (!is(ElseType == void)) 14 | return v; 15 | } 16 | 17 | /** 18 | If value is valid, it returns the internal value. This means .front for a range, .get for a Nullable!T, etc. 19 | If value is invalid, then elseValue is returned. If an elsePred is provided than that is called. 20 | 21 | `elsePred` can return void as well, in which case frontOr also returns void. 22 | 23 | Params: 24 | value = the value to check 25 | elseValue = the value to get if `value` is invalid 26 | elsePred = the perdicate to call if `value` is invalid 27 | 28 | Returns: 29 | $(LI `Nullable!T`: `value.get` or `elseValue`) 30 | $(LI `Optional!T`: `value.front` or `elseValue`) 31 | $(LI `Range!T`: `value.front` or `elseValue`) 32 | */ 33 | auto frontOr(alias elsePred, T)(auto ref T value) { 34 | 35 | alias ElseType = typeof(elsePred()); 36 | 37 | // The order of these checks matter 38 | 39 | static if (isTypeconsNullable!T) { 40 | // Do this before Range because it could be aliased to a range, in which canse if there's 41 | // nothing inside, simply calling .empty on it will get Nullables's .get implicitly. BOOM! 42 | if (value.isNull) { 43 | static if (isTypeconsNullable!ElseType) { 44 | return elsePred().get; 45 | } else { 46 | return elsePred(); 47 | } 48 | } else { 49 | return ret!ElseType(value.get); 50 | } 51 | } else static if (isOptional!T) { 52 | // Specifically seperate form isInputRange because const optionals are not ranges 53 | if (value.empty) { 54 | return elsePred(); 55 | } else { 56 | return ret!ElseType(value.front); 57 | } 58 | } else static if (isInputRange!T) { 59 | import std.range: empty, front; 60 | if (value.empty) { 61 | return elsePred(); 62 | } else { 63 | return ret!ElseType(value.front); 64 | } 65 | } else { 66 | static assert(0, 67 | "Unable to call frontOr on type " ~ T.stringof ~ ". It has to either be an input range," 68 | ~ " a Nullable!T, or an Optional!T" 69 | ); 70 | } 71 | } 72 | 73 | /// Ditto 74 | auto frontOr(T, U)(auto ref T value, lazy U elseValue) { 75 | return value.frontOr!(elseValue); 76 | } 77 | 78 | /// 79 | @("frontOr example") 80 | @safe unittest { 81 | import optional.optional: some, no; 82 | 83 | auto opt0 = no!int; 84 | auto opt1 = some(1); 85 | 86 | // Get or optional 87 | assert(opt0.frontOr(789) == 789); 88 | assert(opt1.frontOr(789) == 1); 89 | 90 | // Lambdas 91 | () @nogc { 92 | assert(opt0.frontOr!(() => 789) == 789); 93 | assert(opt1.frontOr!(() => 789) == 1); 94 | }(); 95 | 96 | // Same with arrays/ranges 97 | 98 | int[] arr0; 99 | int[] arr1 = [1, 2]; 100 | 101 | // Get frontOr optional 102 | assert(arr0.frontOr(789) == 789); 103 | assert(arr1.frontOr(789) == 1); 104 | 105 | // Lambdas 106 | () @nogc { 107 | assert(arr0.frontOr!(() => 789) == 789); 108 | assert(arr1.frontOr!(() => 789) == 1); 109 | }(); 110 | } 111 | 112 | /** 113 | If value is valid, it returns the value. If value is invalid, then elseValue is returned. 114 | If an elsePred is provided than that is called. 115 | 116 | `elsePred` can return void as well, in which case frontOr also returns void. 117 | 118 | Params: 119 | value = the value to check 120 | elseValue = the value to get if `value` is invalid 121 | elsePred = the perdicate to call if `value` is invalid 122 | 123 | Returns: 124 | $(LI `Nullable!T`: `value.isNull ? elseValue : value`) 125 | $(LI `Optional!T`: `value.empty ? elseValue : value`) 126 | $(LI `Range!T`: `value.empty ? elseValue : value`) 127 | $(LI `Null-testable type`: `value is null ? elseValue : value`) 128 | */ 129 | auto or(alias elsePred, T)(auto ref T value) { 130 | 131 | alias ElseType = typeof(elsePred()); 132 | 133 | // The order of these checks matter 134 | 135 | static if (isTypeconsNullable!T) { 136 | // Do this before Range because it could be aliased to a range, in which case if there's 137 | // nothing inside, simply calling .empty on it will get Nullables's .get implicitly. BOOM! 138 | if (value.isNull) { 139 | static if (isTypeconsNullable!ElseType) { 140 | return elsePred().get; 141 | } else { 142 | return elsePred(); 143 | } 144 | } else { 145 | return ret!ElseType(value.get); 146 | } 147 | } else static if (isOptional!T) { 148 | // Specifically seperate form isInputRange because const optionals are not ranges 149 | if (value.empty) { 150 | return elsePred(); 151 | } else { 152 | return ret!ElseType(value); 153 | } 154 | } else static if (isInputRange!T) { 155 | import std.range: empty; 156 | static if (is(ElseType : T)) { 157 | // Coalescing to the same range type 158 | if (value.empty) { 159 | return elsePred(); 160 | } else { 161 | return ret!ElseType(value); 162 | } 163 | } else { 164 | // If it's a range but not implicly convertible we can use choose 165 | static if (!is(ElseType == void)) { 166 | import std.range: choose; 167 | return choose(value.empty, elsePred(), value); 168 | } else { 169 | if (value.empty) { 170 | elsePred(); 171 | } 172 | } 173 | } 174 | } else static if (isNullTestable!T) { 175 | if (value is null) { 176 | return elsePred(); 177 | } 178 | return ret!ElseType(value); 179 | } else { 180 | static assert(0, 181 | "Unable to call or on type " ~ T.stringof ~ ". It has to either be an input range," 182 | ~ " a null testable type, a Nullable!T, or an Optional!T" 183 | ); 184 | } 185 | } 186 | 187 | /// Ditto 188 | auto or(T, U)(auto ref T value, lazy U elseValue) { 189 | return value.or!(elseValue); 190 | } 191 | 192 | /// 193 | @("or example") 194 | @safe unittest { 195 | import optional.optional: some, no; 196 | 197 | auto opt0 = no!int; 198 | auto opt1 = some(1); 199 | 200 | // Get or optional 201 | assert(opt0.or(opt1) == opt1); 202 | assert(opt1.or(opt0) == opt1); 203 | 204 | // Lambdas 205 | () @nogc { 206 | assert(opt0.or!(() => opt1) == opt1); 207 | assert(opt1.or!(() => opt0) == opt1); 208 | }(); 209 | 210 | // Same with arrays/ranges 211 | 212 | int[] arr0; 213 | int[] arr1 = [1, 2]; 214 | 215 | // Get or optional 216 | assert(arr0.or(arr1) == arr1); 217 | assert(arr1.or(arr0) == arr1); 218 | 219 | // Lambdas 220 | () @nogc { 221 | assert(arr0.or!(() => arr1) == arr1); 222 | assert(arr1.or!(() => arr0) == arr1); 223 | }(); 224 | } 225 | 226 | /** 227 | An exception that's throw by `frontOrThrow` should the exception maker throw 228 | */ 229 | public class FrontOrThrowException : Exception { 230 | /// Original cause of this exception 231 | Exception cause; 232 | 233 | package(optional) this(Exception cause) @safe nothrow pure { 234 | super(cause.msg); 235 | this.cause = cause; 236 | } 237 | } 238 | 239 | /** 240 | Same as `frontOr` except it throws an error if it can't get the value 241 | 242 | Params: 243 | value = the value to resolve 244 | makeThrowable = the predicate that creates exception `value` cannot be resolved 245 | throwable = the value to throw if value cannot be resolved 246 | 247 | Returns: 248 | $(LI `Nullable!T`: `value.get` or throw) 249 | $(LI `Optional!T`: `value.front` or throw) 250 | $(LI `Range!T`: `value.front` or throw) 251 | */ 252 | auto frontOrThrow(alias makeThrowable, T)(auto ref T value) { 253 | // The orer of these checks matter 254 | 255 | static if (isTypeconsNullable!T) { 256 | // Do this before Range because it could be aliased to a range, in which canse if there's 257 | // nothing inside, simply calling .empty on it will get Nullables's .get implicitly. BOOM! 258 | if (!value.isNull) { 259 | return value.get; 260 | } 261 | } else static if (isOptional!T) { 262 | if (!value.empty) { 263 | return value.front; 264 | } 265 | } else static if (isInputRange!T) { 266 | import std.range: empty, front; 267 | if (!value.empty) { 268 | return value.front; 269 | } 270 | } else { 271 | static assert(0, 272 | "Unable to call frontOrThrow on type " ~ T.stringof ~ ". It has to either be an input range," 273 | ~ " a Nullable!T, or an Optional!T" 274 | ); 275 | } 276 | 277 | // None of the static branches returned a value, throw! 278 | throw () { 279 | try { 280 | return makeThrowable(); 281 | } catch (Exception ex) { 282 | throw new FrontOrThrowException(ex); 283 | } 284 | }(); 285 | } 286 | 287 | /// Ditto 288 | auto frontOrThrow(T, U : Throwable)(auto ref T value, lazy U throwable) { 289 | return value.frontOrThrow!(throwable); 290 | } 291 | 292 | /// 293 | @("frontOrThrow example") 294 | @safe unittest { 295 | import std.exception: assertThrown, assertNotThrown; 296 | 297 | "" 298 | .frontOrThrow(new Exception("")) 299 | .assertThrown!Exception; 300 | 301 | auto b = "yo" 302 | .frontOrThrow(new Exception("")) 303 | .assertNotThrown!Exception; 304 | 305 | assert(b == 'y'); 306 | } 307 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [This is not being maintained anymore - no time - and not using D anymore]. 2 | 3 | # Optional type for D with safe dispatching 4 | 5 | [![Latest version](https://img.shields.io/dub/v/optional.svg)](https://code.dlang.org/packages/optional) [![Build Status](https://travis-ci.org/aliak00/optional.svg?branch=master)](https://travis-ci.org/aliak00/optional) [![codecov](https://codecov.io/gh/aliak00/optional/branch/master/graph/badge.svg)](https://codecov.io/gh/aliak00/optional) [![license](https://img.shields.io/github/license/aliak00/optional.svg)](https://github.com/aliak00/optional/blob/master/LICENSE) [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/CxJwiO) 6 | 7 | Full API docs available [here](https://aliak00.github.io/optional/optional.html) 8 | 9 | * [Features](#features) 10 | * [Summary](#summary) 11 | * [Motivation for Optional](#motivation-for-optional) 12 | * [Use pointers?](#use-pointers) 13 | * [How about ranges?](#how-about-ranges) 14 | * [Let's try an Optional!int](#lets-try-an-optionalint) 15 | * [FAQ](#faq) 16 | * [Can't I just use a pointer as an optional](#cant-i-just-use-a-pointer-as-an-optional) 17 | * [What about std.typecons.Nullable?](#what-about-stdtypeconsnullable) 18 | * [Scala we have a Swift comparison](#scala-we-have-a-swift-comparison) 19 | * [Examples](#examples) 20 | * [Example Optional!T usage](#example-optionalt-usage) 21 | * [Example dispatch usage](#example-dispatch-usage) 22 | 23 | 24 | ## Features 25 | 26 | * `@nogc` and `@safe` 27 | * Shows the intent of your code that may or may not return a value 28 | ```d 29 | Optional!int fun() {} // Might return an int, or might not 30 | ``` 31 | * Includes a generic `or` range algorithm: 32 | ```d 33 | auto a = some(3); 34 | auto b = a.or(7); 35 | auto c = a.or(some(4)); 36 | c.or!(() => writeln("c is empty")); 37 | ``` 38 | * Use pattern matching 39 | ```d 40 | fun.match!( 41 | (int value) => writeln("it returns an int"), 42 | () => writeln("did not return anything"), 43 | ); 44 | ``` 45 | * Safely call functions on classes that are null, structs that don't exist, or `std.typecons.Nullable` 46 | ```d 47 | class C { int fun() { return 3; } } 48 | Optional!C a = null; 49 | oc(a).fun; // no crash, returns no!int 50 | ``` 51 | * Forwards any operator calls to the wrapped typed only if it exists, else just returns a `none` 52 | ```d 53 | Optional!int a = 3; 54 | Optional!int b = none; 55 | a + 3; // evaluates to some(6); 56 | b + 3; // evaluates to no!int; 57 | 58 | int f0(int) { return 4; } 59 | auto a0 = some(&f0); // return some(4) 60 | ``` 61 | * Compatible with `std.algorithm` and `std.range` 62 | ```d 63 | fun.each!(value => writeln("I got the value")); 64 | fun.filter!"a % 2 == 0".each!(value => writeln("got even value")); 65 | ``` 66 | 67 | ## Summary 68 | 69 | The pupose of this library is to provide an [Optional type](https://en.wikipedia.org/wiki/Option_type). 70 | 71 | It contains the following constructs: 72 | * `Optional!T`: Represents an optional data type that may or may not contain a value that acts like a range. 73 | * `oc`: A null-safe optional chaining (oc) utility that allows you to chain methos through possible empty objects. 74 | * `or`: A range algorithm that also acts as a coalescing operator 75 | * `match`: Pattern match on optionals 76 | 77 | An `Optional!T` signifies the intent of your code, works as a range and is therefore usable with Phobos algorithms, and allows you to call methods and operators on your types even if they are null references - i.e. safe dispatching. 78 | 79 | Some use cases: 80 | * When you need a type that may have a value or may not (`Optional!Type`) 81 | * When you want to safely dispatch on types (`oc(obj).someFunction // always safe`) 82 | * When you want to not crash with array access (`some([1, 2])[7] == none // no out of bounds exception`) 83 | * When you want to perform an operation if you get a value (`obj.map!doSomething.or!doSomethingElse`) 84 | 85 | ## Motivation for Optional 86 | 87 | Let's take a very contrived example, and say you have a function that may return a value (that should be some integer) or not (config file, server, find operation, whatever), and then you have functions add1 and add2, that have the requirements that they may or may not produce a valid value. (maybe they do some crazy division, or they contact a server themselves to fetch a value, whatevs). 88 | 89 | How can you go about this? 90 | 91 | ### Use pointers? 92 | 93 | ```d 94 | int* add1(int *v) { 95 | // Gotta remember to protect against null 96 | if (!v) { 97 | return v; 98 | } 99 | *v += 1; 100 | return v; 101 | } 102 | 103 | int* add2(int *v); // might forget to check for null 104 | 105 | void f() { 106 | int* v = maybeGet(); 107 | if (v) 108 | v = v.add1; 109 | if (v) 110 | v = v.add2; 111 | if (v) 112 | writeln(*v); 113 | } 114 | ``` 115 | 116 | You can also replace int* with Nullable!int and then instead of `if (v)` you'd have to do `if (!v.isNull)` and instead of `*v` you'd do `v.get`. 117 | 118 | ### How about ranges? 119 | 120 | There's std.range.only: 121 | 122 | ```d 123 | auto add2(Range)(Range r) 124 | if (isInputRange!Range && is(ElementType!Range == int)) 125 | // constrain to range type only and int element type? 126 | // I need to ensure it has a length of one. 127 | // And there's no way to ensure that in compile time without severly constraigning the type 128 | { 129 | // do we have one element or more now? 130 | // what do we do if there's more than one? 131 | // do we restrain it at run time to being there? 132 | enforce(r.walkLength <= 1); // ?? 133 | // Should we map all of it? 134 | return v.map!(a => a + 1); 135 | // Or just the first? 136 | return v.take(1).map!(a => a + 1); 137 | // But what do I do with the rest then? 138 | } 139 | 140 | auto add2(Range)(Range r) if (isInputRange!Range) { 141 | // same headache as above 142 | } 143 | 144 | void f() { 145 | auto v = maybeGet(); 146 | // can we assign it to itself? 147 | v = v.add1.add2; 148 | // No, no idea what it returns, not really the same type 149 | // so this... 150 | refRange(&v).add1.add2; // ?? 151 | // no that won't work (can it?), lets create a new var 152 | auto v2 = v.add1.add2 // and let type inference do its thing 153 | writeln(v2); // now ok. 154 | } 155 | ``` 156 | 157 | ### Let's try an Optional!int 158 | 159 | ```d 160 | auto add1(Optional!int v) { 161 | v += 1; 162 | return v; 163 | } 164 | auto add2(Optional!int v); // same as above 165 | 166 | void f() { 167 | auto v = maybeGet().add1.add2; 168 | writeln(v); 169 | } 170 | ``` 171 | 172 | ## FAQ 173 | 174 | ### Can't I just use a pointer as an optional 175 | 176 | Well yes, you can, but you *can* also stick a pencil up your nostril. It's a bad idea for the following reasons: 177 | 178 | 1. In order to achieve stability, you have to enforce checking for null. Which you cannot do 179 | 1. Null is part of the value domain of pointers. This means you can't use an optional of null 180 | 1. The caller doesn't know who owns the pointer returned. Is it garbage collected? If not should you deallocate it? 181 | 1. It says nothing about intent. 182 | 183 | ### What about `std.typecons.Nullable`? 184 | 185 | It is not like the `Nullable` type in Phobos. `Nullable` is basically a pointer and applies pointer semantics to value types. It does not give you any safety guarantees and says nothing about the intent of "I might not return a value". It does not have range semantics so you cannot use it with algorithms in phobos. And it treats null class objects as valid. 186 | 187 | It does, however, tell you if something has been assigned a value or not. Albeit a bit counterintuitively, and in some cases nonsensically: 188 | 189 | ```d 190 | class C {} 191 | Nullable!C a = null; 192 | writeln(a.isNull); // prints false 193 | ``` 194 | 195 | With refernece types (e.g., pointers, classes, functions) you end up having to write code like this: 196 | 197 | ```d 198 | void f(T)(Nullable!T a) { 199 | if (!a.isNull) { 200 | static if (is(T == class) || (T == interface) || /* what else have I missed? */) { 201 | if (a.get !is null) { 202 | a.callSomeFunction; 203 | } 204 | } else { 205 | a.callSomeFunction; 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | ## Scala we have a Swift comparison 212 | 213 | In this section we'll see how this Optional is similar to [Scala's `Option[T]`](https://www.scala-lang.org/api/current/scala/Option.html) and [Swift's `Optional`](https://developer.apple.com/documentation/swift/optional) type (similar to Kotlin's [nullable type handling](https://kotlinlang.org/docs/reference/null-safety.html)) 214 | 215 | Idiomatic usage of optionals in Swift do not involve treating it like a range. They use optional unwrapping to ensure safety and dispatch chaining. Scala on the other hand, treats optionals like a range and provides primitives to get at the values safely. 216 | 217 | Like in swift, you can chain functions safely so in case they are null, nothing will happen: 218 | 219 | **D**: Unfortunately the lack of operator overloading makes dispatching a bit verbose. 220 | ```d 221 | class Residence { 222 | auto numberOfRooms = 1; 223 | } 224 | class Person { 225 | Optional!Residence residence = new Residence(); 226 | } 227 | 228 | auto john = some(new Person()); 229 | 230 | auto n = oc(john).residence.numberOfRooms; 231 | 232 | writeln(n); // prints [1] 233 | ``` 234 | 235 | **Swift** 236 | ```swift 237 | class Person { 238 | var residence: Residence? 239 | } 240 | 241 | class Residence { 242 | var numberOfRooms = 1 243 | } 244 | 245 | let john: Person? = Person() 246 | let n = john?.residence?.numberOfRooms; 247 | 248 | print(n) // prints "nil" 249 | ``` 250 | 251 | Like in Scala, a number of range primitives are provided to help (not to mention we have Phobos as well) 252 | 253 | **D** 254 | ```d 255 | auto x = toInt("1").or(0); 256 | 257 | import std.algorithm: each; 258 | import std.stdio: writeln; 259 | 260 | toInt("1").each!writeln; 261 | 262 | toInt("1").match!( 263 | (i) => writeln(i), 264 | () => writeln("😱"), 265 | ); 266 | 267 | // For completeness, the implementation of toInt: 268 | Optional!int toInt(string str) { 269 | import std.conv: to; 270 | scope(failure) return no!int; 271 | return some(str.to!int); 272 | } 273 | ``` 274 | 275 | **Scala** 276 | ```scala 277 | val x = toInt("1").getOrElse(0) 278 | 279 | toInt("1").foreach{ i => 280 | println(s"Got an int: $i") 281 | } 282 | 283 | toInt("1") match { 284 | case Some(i) => println(i) 285 | case None => println("😱") 286 | } 287 | 288 | // Implementation of toInt 289 | def toInt(s: String): Option[Int] = { 290 | try { 291 | Some(Integer.parseInt(s.trim)) 292 | } catch { 293 | case e: Exception => None 294 | } 295 | } 296 | ``` 297 | 298 | ## Examples 299 | 300 | The following section has example usage of the various types 301 | 302 | ### Example Optional!T usage 303 | ```d 304 | import optional; 305 | 306 | // Create empty optional 307 | auto a = no!int; 308 | assert(a == none); 309 | 310 | ++a; // safe; 311 | a - 1; // safe; 312 | 313 | // Assign and try doing the same stuff 314 | a = 9; 315 | assert(a == some(9)); 316 | 317 | ++a; // some(10); 318 | a - 1; // some(9); 319 | 320 | // Acts like a range as well 321 | import std.algorithm : map; 322 | import std.conv : to; 323 | 324 | cast(void)some(10).map!(to!double); // [10.0] 325 | cast(void)no!int.map!(to!double); // empty 326 | 327 | auto r = some(1).match!((int a) => "yes", () => "no",); 328 | assert(r == "yes"); 329 | ``` 330 | [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/PWRvg2) 331 | 332 | ### Example optional chaining usage 333 | ```d 334 | // Safely dispatch to whatever inner type is 335 | struct A { 336 | struct Inner { 337 | int g() { return 7; } 338 | } 339 | Inner inner() { return Inner(); } 340 | int f() { return 4; } 341 | } 342 | 343 | auto d = some(A()); 344 | 345 | // Dispatch to one of its methods 346 | 347 | oc(d).f(); // calls a.f, returns some(4) 348 | oc(d).inner.g(); // calls a.inner.g, returns some(7) 349 | 350 | // Use on a pointer or reference type as well 351 | A* e = null; 352 | 353 | // If there's no value in the reference type, dispatching works, and produces an optional 354 | assert(e.oc.f() == none); 355 | assert(e.oc.inner.g() == none); 356 | ``` 357 | [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/fcTxfL) 358 | -------------------------------------------------------------------------------- /source/optional/optional.d: -------------------------------------------------------------------------------- 1 | /** 2 | Optional type 3 | */ 4 | module optional.optional; 5 | 6 | import std.typecons: Nullable; 7 | import bolts.from; 8 | 9 | package struct None {} 10 | 11 | /** 12 | Represents an empty optional value. This is used to set `Optional`s to have no value 13 | or for comparisons 14 | 15 | SeeAlso: 16 | - `Optional.opEquals` 17 | */ 18 | immutable none = None(); 19 | 20 | private static string autoReturn(string expression)() { 21 | return ` 22 | auto ref expr() { 23 | return ` ~ expression ~ `; 24 | } 25 | ` ~ q{ 26 | alias R = typeof(expr()); 27 | static if (!is(R == void)) 28 | return empty ? no!R : some!R(expr()); 29 | else { 30 | if (!empty) { 31 | expr(); 32 | } 33 | } 34 | }; 35 | } 36 | 37 | /** 38 | Optional type. Also known as a Maybe or Option type in some languages. 39 | 40 | This can either contain a value or be `none`. If the value is a refernce type then 41 | `null` is considered `none`. 42 | 43 | It also has range like behavior. So this acts as a range that contains 1 element or 44 | is empty. 45 | 46 | And all operations that can be performed on a T can also be performed on an Optional!T. 47 | The behavior of applying an operation on a no-value or null pointer is well defined 48 | and safe. 49 | */ 50 | 51 | struct Optional(T) { 52 | import std.traits: isMutable, isSomeFunction, isAssignable, isPointer, isArray; 53 | 54 | private enum isNullInvalid = is(T == class) || is(T == interface) || isSomeFunction!T || isPointer!T; 55 | 56 | private T _value = T.init; // Set to init for when T has @disable this() 57 | private bool defined = false; 58 | 59 | private enum setDefinedTrue = q{ 60 | static if (isNullInvalid) { 61 | this.defined = this._value !is null; 62 | } else { 63 | this.defined = true; 64 | } 65 | }; 66 | 67 | /** 68 | Constructs an Optional!T value by assigning T 69 | 70 | If T is of class type, interface type, or some function pointer then passing in null 71 | sets the optional to `none` interally 72 | */ 73 | this(T value) pure { 74 | import std.traits: isCopyable; 75 | static if (!isCopyable!T) { 76 | import std.functional: forward; 77 | this._value = forward!value; 78 | } else { 79 | this._value = value; 80 | } 81 | mixin(setDefinedTrue); 82 | } 83 | /// Ditto 84 | this(const None) pure { 85 | // For Error: field _value must be initialized in constructor, because it is nested struct 86 | this._value = T.init; 87 | } 88 | 89 | @property bool empty() const nothrow @safe { 90 | static if (isNullInvalid) { 91 | return !this.defined || this._value is null; 92 | } else { 93 | return !this.defined; 94 | } 95 | } 96 | @property ref inout(T) front() inout return @safe nothrow { 97 | assert(!empty, "Attempting to fetch the front of an empty optional."); 98 | return this._value; 99 | } 100 | void popFront() { this.defined = false; } 101 | 102 | /** 103 | Compare two optionals or an optional with some value 104 | Returns: 105 | - If the two are optionals then they are both unwrapped and compared. If either are empty 106 | this returns false. And if compared with `none` and there's a value, also returns false 107 | --- 108 | auto a = some(3); 109 | a == some(2); // false 110 | a == some(3); // true 111 | a == none; // false 112 | --- 113 | */ 114 | bool opEquals(const None) const @safe nothrow { return this.empty; } 115 | /// Ditto 116 | bool opEquals(U : T)(const auto ref Optional!U rhs) const { 117 | if (this.empty || rhs.empty) return this.empty == rhs.empty; 118 | return this._value == rhs._value; 119 | } 120 | /// Ditto 121 | bool opEquals(U : T)(const auto ref U rhs) const { 122 | return !this.empty && this._value == rhs; 123 | } 124 | /// Ditto 125 | bool opEquals(R)(auto ref R other) const if (from.std.range.isInputRange!R) { 126 | import std.range: empty, front; 127 | 128 | if (this.empty && other.empty) return true; 129 | if (this.empty || other.empty) return false; 130 | return this.front == other.front; 131 | } 132 | 133 | /** 134 | Assigns a value to the optional or sets it to `none`. 135 | 136 | If T is of class type, interface type, or some function pointer than passing in null 137 | sets the optional to `none` internally 138 | */ 139 | auto ref opAssign()(const None) if (isMutable!T) { 140 | if (!this.empty) { 141 | static if (isNullInvalid) { 142 | this._value = null; 143 | } else { 144 | destroy(this._value); 145 | } 146 | this.defined = false; 147 | } 148 | return this; 149 | } 150 | /// Ditto 151 | auto ref opAssign(U : T)(auto ref U lhs) if (isMutable!T && isAssignable!(T, U)) { 152 | this._value = lhs; 153 | mixin(setDefinedTrue); 154 | return this; 155 | } 156 | /// Ditto 157 | auto ref opAssign(U : T)(auto ref Optional!U lhs) if (isMutable!T && isAssignable!(T, U)) { 158 | static if (__traits(isRef, lhs) || !isMutable!U) { 159 | this._value = lhs._value; 160 | } else { 161 | import std.algorithm: move; 162 | this._value = move(lhs._value); 163 | } 164 | 165 | this.defined = lhs.defined; 166 | return this; 167 | } 168 | 169 | /** 170 | Applies unary operator to internal value of optional. 171 | Returns: 172 | - If the optional is some value it returns an optional of some `op value`. 173 | --- 174 | auto a = no!(int*); 175 | auto b = *a; // ok 176 | b = 3; // b is an Optional!int because of the deref 177 | --- 178 | */ 179 | auto opUnary(string op, this This)() { 180 | mixin(autoReturn!(op ~ "front")); 181 | } 182 | 183 | /** 184 | If the optional is some value it returns an optional of some `value op rhs` 185 | */ 186 | auto opBinary(string op, U : T, this This)(auto ref U rhs) { 187 | mixin(autoReturn!("front" ~ op ~ "rhs")); 188 | } 189 | /** 190 | If the optional is some value it returns an optional of some `lhs op value` 191 | */ 192 | auto opBinaryRight(string op, U : T, this This)(auto ref U lhs) { 193 | mixin(autoReturn!("lhs" ~ op ~ "front")); 194 | } 195 | 196 | /** 197 | If there's a value that's callable it will be called else it's a noop 198 | 199 | Returns: 200 | Optional value of whatever `T(args)` returns 201 | */ 202 | auto opCall(Args...)(Args args) if (from.std.traits.isCallable!T) { 203 | mixin(autoReturn!("this._value(args)")); 204 | } 205 | 206 | /** 207 | If the optional is some value, op assigns rhs to it 208 | */ 209 | auto opOpAssign(string op, U : T, this This)(auto ref U rhs) { 210 | mixin(autoReturn!("front" ~ op ~ "= rhs")); 211 | } 212 | 213 | static if (isArray!T) { 214 | /** 215 | Provides indexing into arrays 216 | 217 | The indexes and slices are also checked to be valid and `none` is returned if they are 218 | not 219 | */ 220 | auto opIndex(this This)(size_t index) { 221 | enum call = "front[index]"; 222 | import std.range: ElementType; 223 | if (empty || index >= front.length || index < 0) { 224 | return no!(mixin("typeof("~call~")")); 225 | } 226 | mixin(autoReturn!(call)); 227 | } 228 | /// Ditto 229 | auto opIndex(this This)() { 230 | mixin(autoReturn!("front[]")); 231 | } 232 | /// Ditto 233 | auto opSlice(this This)(size_t begin, size_t end) { 234 | enum call = "front[begin .. end]"; 235 | import std.range: ElementType; 236 | if (empty || begin > end || end > front.length) { 237 | return no!(mixin("typeof("~call~")")); 238 | } 239 | mixin(autoReturn!(call)); 240 | } 241 | /// Ditto 242 | auto opDollar() const { 243 | return empty ? 0 : front.length; 244 | } 245 | } 246 | 247 | /// Converts value to string 248 | string toString()() inout { 249 | if (empty) { 250 | return "[]"; 251 | } 252 | static if (__traits(compiles, { this._value.toString; } )) { 253 | auto str = this._value.toString; 254 | } else { 255 | import std.conv: to; 256 | auto str = to!string(this._value); 257 | } 258 | return "[" ~ str ~ "]"; 259 | } 260 | 261 | static if (__traits(compiles, { 262 | import vibe.data.serialization; 263 | import vibe.data.json; 264 | auto a = T.init.serializeToJson; 265 | auto b = deserializeJson!T(a); 266 | })) { 267 | import vibe.data.json; 268 | Json toRepresentation() const { 269 | if (empty) { 270 | return Json.undefined; 271 | } 272 | return _value.serializeToJson; 273 | } 274 | static Optional!T fromRepresentation(Json value) { 275 | if (value == Json.undefined) { 276 | return Optional!T(); 277 | } 278 | return Optional!T(deserializeJson!T(value)); 279 | } 280 | } 281 | } 282 | 283 | /** 284 | Type constructor for an optional having some value of `T` 285 | */ 286 | public auto some(T)(auto ref T value) { 287 | import std.traits: isMutable, isCopyable; 288 | static if (!isCopyable!T) { 289 | import std.functional: forward; 290 | return Optional!T(forward!value); 291 | } else { 292 | return Optional!T(value); 293 | } 294 | } 295 | 296 | /// 297 | @("Example of some()") 298 | @nogc @safe unittest { 299 | import std.range: only; 300 | import std.algorithm: equal; 301 | 302 | auto a = no!int; 303 | assert(a == none); 304 | a = 9; 305 | assert(a == some(9)); 306 | assert(a != none); 307 | 308 | import std.algorithm: map; 309 | assert(only(1, 2, 3).map!some.equal(only(some(1), some(2), some(3)))); 310 | } 311 | 312 | /// Type constructor for an optional having no value of `T` 313 | public auto no(T)() { 314 | return Optional!T(); 315 | } 316 | 317 | /// 318 | @("Example of no()") 319 | @safe unittest { 320 | auto a = no!(int*); 321 | assert(a == none); 322 | assert(*a != 9); 323 | a = new int(9); 324 | assert(*a == 9); 325 | assert(a != none); 326 | a = null; 327 | assert(a == none); 328 | } 329 | 330 | /** 331 | Converts a range or Nullable to an optional type 332 | 333 | Params: 334 | range = the range to convert. It must have no more than 1 element 335 | nullable = the Nullable to convert 336 | 337 | Returns: 338 | an optional of the element of range or Nullable 339 | */ 340 | auto toOptional(R)(auto ref R range) if (from.std.range.isInputRange!R) { 341 | import std.range: walkLength, ElementType, front; 342 | assert(range.empty || range.walkLength == 1); 343 | if (range.empty) { 344 | return no!(ElementType!R); 345 | } else { 346 | return some(range.front); 347 | } 348 | } 349 | 350 | /// Ditto 351 | auto toOptional(T)(auto ref inout Nullable!T nullable) { 352 | if (nullable.isNull) { 353 | return inout Optional!T(); 354 | } else { 355 | return inout Optional!T(nullable.get); 356 | } 357 | } 358 | 359 | /// 360 | @("Example of toOptional") 361 | unittest { 362 | import std.algorithm: map; 363 | import optional; 364 | 365 | assert(no!int.map!"a".toOptional == none); 366 | assert(some(1).map!"a".toOptional == some(1)); 367 | } 368 | 369 | /** 370 | Turns an Optional in to a Nullable 371 | 372 | Params: 373 | opt = the optional to convert from a Nullable!T 374 | 375 | Returns: 376 | An Nullable!T 377 | */ 378 | auto toNullable(T)(auto ref Optional!T opt) { 379 | import std.typecons: nullable; 380 | if (opt.empty) { 381 | return Nullable!T(); 382 | } else { 383 | return opt.front.nullable; 384 | } 385 | } 386 | 387 | /// 388 | @("Example of toNullable") 389 | unittest { 390 | assert(some(3).toNullable == Nullable!int(3)); 391 | assert(no!int.toNullable == Nullable!int()); 392 | } 393 | -------------------------------------------------------------------------------- /tests/optional.d: -------------------------------------------------------------------------------- 1 | module tests.optional; 2 | 3 | import optional.optional; 4 | 5 | import std.meta: AliasSeq; 6 | import std.stdio: writeln; 7 | import std.algorithm: equal; 8 | 9 | alias QualifiedAlisesOf(T) = AliasSeq!(T, const T, immutable T); 10 | alias OptionalsOfQualified(T) = AliasSeq!(Optional!T, Optional!(const T), Optional!(immutable T)); 11 | alias QualifiedOptionalsOfQualified(T) = AliasSeq!(QualifiedAlisesOf!(Optional!T), OptionalsOfQualified!T); 12 | 13 | private enum isObject(T) = is(T == class) || is(T == interface); 14 | 15 | import std.range, std.traits; 16 | 17 | @("Should allow equalify with all qualifiers") 18 | @nogc @safe unittest { 19 | foreach (T; QualifiedOptionalsOfQualified!int) { 20 | auto a = T(); 21 | auto b = T(3); 22 | auto c = T(4); 23 | assert(a == none); 24 | assert(b == b); 25 | assert(b != c); 26 | assert(c == 4); 27 | } 28 | } 29 | 30 | @("Should wotk with opUnary, opBinary, and opRightBinary") 31 | @nogc @safe unittest { 32 | import std.meta: AliasSeq; 33 | import std.traits: isMutable; 34 | import std.range: ElementType; 35 | foreach (T; QualifiedOptionalsOfQualified!int) { 36 | T a = 10; 37 | T b = none; 38 | static assert(!__traits(compiles, { int x = a; })); 39 | static assert(!__traits(compiles, { void func(int n){} func(a); })); 40 | assert(a == 10); 41 | assert(b == none); 42 | assert(a != 20); 43 | assert(a != none); 44 | assert((+a) == some(10)); 45 | assert((-b) == none); 46 | assert((-a) == some(-10)); 47 | assert((+b) == none); 48 | assert((-b) == none); 49 | assert((a + 10) == some(20)); 50 | assert((b + 10) == none); 51 | assert((a - 5) == some(5)); 52 | assert((b - 5) == none); 53 | assert((a * 20) == some(200)); 54 | assert((b * 20) == none); 55 | assert((a / 2) == some(5)); 56 | assert((b / 2) == none); 57 | assert((10 + a) == some(20)); 58 | assert((10 + b) == none); 59 | assert((15 - a) == some(5)); 60 | assert((15 - b) == none); 61 | assert((20 * a) == some(200)); 62 | assert((20 * b) == none); 63 | assert((50 / a) == some(5)); 64 | assert((50 / b) == none); 65 | static if (isMutable!(ElementType!T) && isMutable!(T)) { 66 | assert((++a) == some(11)); 67 | assert((a++) == some(11)); 68 | assert(a == some(12)); 69 | assert((--a) == some(11)); 70 | assert((a--) == some(11)); 71 | assert(a == some(10)); 72 | a = a; 73 | assert(a == some(10)); 74 | a = 20; 75 | assert(a == some(20)); 76 | } else { 77 | static assert(!__traits(compiles, { ++a; })); 78 | static assert(!__traits(compiles, { a++; })); 79 | static assert(!__traits(compiles, { --a; })); 80 | static assert(!__traits(compiles, { a--; })); 81 | static assert(!__traits(compiles, { a = a; })); 82 | static assert(!__traits(compiles, { a = 20; })); 83 | } 84 | } 85 | } 86 | 87 | @("Should be mappable") 88 | @safe unittest { 89 | import std.algorithm: map; 90 | import std.conv: to; 91 | auto a = some(10); 92 | auto b = no!int; 93 | assert(a.map!(to!double).equal([10.0])); 94 | assert(b.map!(to!double).empty); 95 | } 96 | 97 | @("Should have opBinary return an optional") 98 | @nogc @safe unittest { 99 | auto a = some(3); 100 | assert(a + 3 == some(6)); 101 | auto b = no!int; 102 | assert(b + 3 == none); 103 | } 104 | 105 | 106 | @("Should allow equality and opAssign between all qualified combinations") 107 | @nogc @safe unittest { 108 | import std.meta: AliasSeq; 109 | 110 | alias U = int; 111 | alias T = Optional!U; 112 | immutable U other = 4; 113 | 114 | alias Constructors = AliasSeq!( 115 | AliasSeq!( 116 | () => T(), 117 | () => const T(), 118 | () => immutable T(), 119 | () => T(U.init), 120 | () => const T(U.init), 121 | () => immutable T(U.init), 122 | ), 123 | AliasSeq!( 124 | () => no!U, 125 | () => no!(const U), 126 | () => no!(immutable U), 127 | () => some!U(U.init), 128 | () => some!(const U)(U.init), 129 | () => some!(immutable U)(U.init), 130 | ) 131 | ); 132 | 133 | static foreach (I; 0 .. 2) {{ 134 | auto nm = Constructors[I * 6 + 0](); 135 | auto nc = Constructors[I * 6 + 1](); 136 | auto ni = Constructors[I * 6 + 2](); 137 | auto sm = Constructors[I * 6 + 3](); 138 | auto sc = Constructors[I * 6 + 4](); 139 | auto si = Constructors[I * 6 + 5](); 140 | 141 | assert(sm != nm); 142 | assert(sm != nc); 143 | assert(sm != ni); 144 | assert(sc != nm); 145 | assert(sc != nc); 146 | assert(sc != ni); 147 | assert(si != nm); 148 | assert(si != nc); 149 | assert(si != ni); 150 | 151 | assert(sm == sc); 152 | assert(sm == si); 153 | assert(sc == si); 154 | 155 | assert(nm == nc); 156 | assert(nm == ni); 157 | assert(nc == ni); 158 | 159 | sm = other; 160 | nm = other; 161 | assert(sm == nm); 162 | 163 | static assert( __traits(compiles, { nm = other; })); 164 | static assert(!__traits(compiles, { ni = other; })); 165 | static assert(!__traits(compiles, { nc = other; })); 166 | static assert( __traits(compiles, { sm = other; })); 167 | static assert(!__traits(compiles, { si = other; })); 168 | static assert(!__traits(compiles, { sc = other; })); 169 | }} 170 | } 171 | 172 | @("Should not allow properties of type to be reachable") 173 | @nogc @safe unittest { 174 | static assert(!__traits(compiles, some(3).max)); 175 | static assert(!__traits(compiles, some(some(3)).max)); 176 | } 177 | 178 | @("Should be filterable") 179 | @safe unittest { 180 | import std.algorithm: filter; 181 | import std.range: array; 182 | foreach (T; QualifiedOptionalsOfQualified!int) { 183 | const arr = [ 184 | T(), 185 | T(3), 186 | T(), 187 | T(7), 188 | ]; 189 | assert(arr.filter!(a => a != none).array == [some(3), some(7)]); 190 | } 191 | } 192 | 193 | @("Should print like a range") 194 | unittest { 195 | assert(no!int.toString == "[]"); 196 | assert(some(3).toString == "[3]"); 197 | 198 | static class A { 199 | override string toString() { return "Yo"; } 200 | string toString() const { return "Yo"; } 201 | } 202 | Object a = new A; 203 | 204 | assert(some(cast(A)a).toString == "[Yo]"); 205 | assert(some(cast(const A)a).toString == "[Yo]"); 206 | } 207 | 208 | @("Should print out const optional") 209 | @safe unittest { 210 | const a = some(3); 211 | assert(a.toString == "[3]"); 212 | } 213 | 214 | @("Should be joinerable and eachable") 215 | @safe unittest { 216 | import std.uni: toUpper; 217 | import std.range: only; 218 | import std.algorithm: joiner, map, each; 219 | 220 | static maybeValues = only(no!string, some("hello"), some("world")); 221 | assert(maybeValues.joiner.map!toUpper.joiner(" ").equal("HELLO WORLD")); 222 | 223 | static moreValues = only(some("hello"), some("world"), no!string); 224 | uint count = 0; 225 | foreach (value; moreValues.joiner) ++count; 226 | assert(count == 2); 227 | moreValues.joiner.each!(value => ++count); 228 | assert(count == 4); 229 | } 230 | 231 | @("Should not allow assignment to const") 232 | @nogc @safe unittest { 233 | Optional!(const int) opt = Optional!(const int)(42); 234 | static assert(!__traits(compiles, opt = some(24))); 235 | static assert(!__traits(compiles, opt = none)); 236 | } 237 | 238 | @("Should treat null as valid values for pointer types") 239 | @nogc @safe unittest { 240 | auto a = no!(int*); 241 | auto b = *a; 242 | assert(a == no!(int*)); 243 | assert(b == no!(int)); 244 | b = 3; 245 | assert(b == some(3)); 246 | a = null; 247 | assert(a == some!(int*)(null)); 248 | assert(*a == no!int); 249 | } 250 | 251 | @("Should not allow assignment to immutable") 252 | @nogc @safe unittest { 253 | auto a = some!(immutable int)(1); 254 | static assert(!__traits(compiles, { a = 2; })); 255 | } 256 | 257 | @("Should forward to opCall if callable") 258 | @nogc @safe unittest { 259 | int f0(int) { return 4; } 260 | alias A = typeof(&f0); 261 | auto a0 = some(&f0); 262 | auto a1 = no!A; 263 | assert(a0(3) == some(4)); 264 | assert(a1(3) == no!int); 265 | 266 | void f1() {} 267 | alias B = typeof(&f1); 268 | auto b0 = some(&f1); 269 | auto b1 = no!B; 270 | static assert(is(typeof(b0()) == void)); 271 | static assert(is(typeof(b1()) == void)); 272 | } 273 | 274 | @("Should work with disabled this") 275 | @nogc @safe unittest { 276 | struct S { 277 | @disable this(); 278 | this(int) {} 279 | } 280 | 281 | Optional!S a = none; 282 | static assert(__traits(compiles, { Optional!S a; })); 283 | auto b = some(S(1)); 284 | auto c = b; 285 | } 286 | 287 | @("Should work with disabled post blit") 288 | @nogc @safe unittest { 289 | import std.conv: to; 290 | static struct S { 291 | int i; 292 | @disable this(this); 293 | this(int i) { this.i = i; } 294 | } 295 | 296 | auto a = some(S(3)); 297 | assert(a != none); 298 | assert(a.front.i == 3); 299 | } 300 | 301 | @("Should not destroy references") 302 | unittest { 303 | class C { 304 | int i; 305 | this(int ai) { i = ai; } 306 | } 307 | 308 | C my = new C(3); 309 | Optional!C opt = some(my); 310 | assert(my.i == 3); 311 | 312 | opt = none; 313 | assert(my.i == 3); 314 | } 315 | 316 | @("Should assign convertaible type optional") 317 | unittest { 318 | class A {} 319 | class B : A {} 320 | 321 | auto a = some(new A()); 322 | auto b = some(new B()); 323 | a = b; 324 | assert(a.front is b.front); 325 | } 326 | 327 | @("Should call opOpAssign if value present") 328 | @nogc @safe unittest { 329 | import std.meta: AliasSeq; 330 | import std.traits: isMutable; 331 | import std.range: ElementType; 332 | foreach (T; QualifiedOptionalsOfQualified!int) { 333 | T a = 10; 334 | T b = none; 335 | static if (isMutable!(ElementType!T) && isMutable!(T)) { 336 | a += 10; 337 | b += 10; 338 | assert(a == some(20)); 339 | assert(b == none); 340 | a -= 5; 341 | b -= 5; 342 | assert(a == some(15)); 343 | assert(b == none); 344 | a %= 2; 345 | b %= 2; 346 | assert(a == some(1)); 347 | assert(b == none); 348 | } else { 349 | static assert(!__traits(compiles, { a += 10; b += 10; } )); 350 | static assert(!__traits(compiles, { a -= 10; b -= 10; } )); 351 | static assert(!__traits(compiles, { a %= 10; b %= 10; } )); 352 | } 353 | } 354 | } 355 | 356 | @("Should work on arrays") 357 | unittest { 358 | foreach (T; QualifiedAlisesOf!(int[])) { 359 | T data = [1, 2]; 360 | 361 | auto a = some(data); 362 | auto b = no!T; 363 | 364 | assert(a[0] == some(1)); 365 | assert(b[0] == none); 366 | assert(a[1] == some(2)); 367 | assert(b[1] == none); 368 | 369 | // Invalid index 370 | assert(a[2] == none); 371 | assert(b[2] == none); 372 | 373 | // Slice 374 | assert(a[] == data); 375 | assert(b[] == none); 376 | 377 | // opSlice 378 | assert(a[0..1] == data[0..1]); 379 | assert(b[0..1] == none); 380 | 381 | // Invalid slice 382 | assert(a[0..7] == none); 383 | assert(b[0..7] == none); 384 | 385 | // opDollar 386 | assert(a[0 .. $] == data); 387 | assert(b[0 .. $] == none); 388 | } 389 | } 390 | 391 | @("Should compare with other ranges") 392 | unittest { 393 | import std.algorithm: map, filter; 394 | auto a = some(1); 395 | 396 | assert(a == [1]); 397 | assert(a == [1].map!"a"); 398 | assert(a == [1].filter!"true"); 399 | } 400 | 401 | @("Should maintain empty state after being assigned to another optional") 402 | unittest { 403 | Optional!string makeNone() { 404 | Optional!string v; 405 | return v; 406 | } 407 | Optional!string makeSome() { 408 | auto v = Optional!string("hi"); 409 | return v; 410 | } 411 | Optional!string o; 412 | o = makeNone(); 413 | assert(o.empty); 414 | o = makeSome(); 415 | assert(!o.empty); 416 | } 417 | 418 | @("Should handle nulls with array of pointers") 419 | @safe unittest { 420 | struct S { 421 | int i; 422 | } 423 | 424 | auto a = [new S(1), new S(2)].some; 425 | auto b = [new S(1), new S(2), null].some; 426 | auto c = [new S(1), new S(2), new S(3)].some; 427 | 428 | assert(a[2] == none); 429 | assert(b[2] == none); 430 | assert(c[2] != none); 431 | } 432 | 433 | @("should throw if out of range") 434 | unittest { 435 | import core.exception: AssertError; 436 | auto a = no!int; 437 | bool thrown = false; 438 | try { 439 | cast(void)a.front; 440 | } catch (AssertError err) { 441 | thrown = true; 442 | } 443 | assert(thrown); 444 | } 445 | 446 | @("should work with const Nullable") 447 | unittest { 448 | import std.typecons: nullable; 449 | const a = 3.nullable; 450 | static assert(__traits(compiles, { auto b = a.toOptional; } )); 451 | } 452 | 453 | @("should work with assigning of @disabled lvalue") 454 | unittest { 455 | struct S { 456 | @disable this(this); 457 | } 458 | 459 | Optional!S fun() { return some(S()); } 460 | 461 | Optional!S b; 462 | b = fun; 463 | } 464 | 465 | @("Should assign a const") 466 | unittest { 467 | const b = 3; 468 | Optional!int a; 469 | a = some(b); 470 | } 471 | 472 | @("Should work with memoize") { 473 | import std.functional: memoize; 474 | static assert(__traits(compiles, { 475 | alias testMemo = memoize!(() => some(1)); 476 | })); 477 | } 478 | --------------------------------------------------------------------------------