1 /** 2 Copyright: Copyright (c) 2020, Meta. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Meta (https://forum.dlang.org/post/fshlmahxfaeqtwjbjouz@forum.dlang.org) 5 */ 6 module my.deref; 7 8 /** 9 * A safe-dereferencing wrapper resembling a Maybe monad. 10 * 11 * If the wrapped object is null, any further member dereferences will simply 12 * return a wrapper around the .init value of the member's type. Since non-null 13 * member dereferences will also return a wrapped value, any null value in the 14 * middle of a chain of nested dereferences will simply cause the final result 15 * to default to the .init value of the final member's type. 16 * 17 */ 18 template SafeDeref(T) { 19 static if (is(T U == SafeDeref!V, V)) { 20 // Merge SafeDeref!(SafeDeref!X) into just SafeDeref!X. 21 alias SafeDeref = U; 22 } else { 23 struct SafeDeref { 24 T t; 25 26 // Make the wrapper as transparent as possible. 27 alias t this; 28 29 // This is the magic that makes it all work. 30 auto opDispatch(string field)() 31 if (is(typeof(__traits(getMember, t, field)))) { 32 alias Memb = typeof(__traits(getMember, t, field)); 33 34 // If T is comparable with null, then we do a null check. 35 // Otherwise, we just dereference the member since it's 36 // guaranteed to be safe of null dereferences. 37 // 38 // N.B.: we always return a wrapped type in case the return 39 // type contains further nullable fields. 40 static if (is(typeof(t is null))) { 41 return safeDeref((t is null) ? Memb.init : __traits(getMember, t, field)); 42 } else { 43 return safeDeref(__traits(getMember, t, field)); 44 } 45 } 46 } 47 } 48 } 49 50 /** 51 * Wraps an object in a safe dereferencing wrapper resembling a Maybe monad. 52 * 53 * If the object is null, then any further member dereferences will just return 54 * a wrapper around the .init value of the wrapped type, instead of 55 * dereferencing null. This applies recursively to any element in a chain of 56 * dereferences. 57 * 58 * Params: t = data to wrap. 59 * Returns: A wrapper around the given type, with "safe" member dereference 60 * semantics. 61 */ 62 auto safeDeref(T)(T t) { 63 return SafeDeref!T(t); 64 } 65 66 unittest { 67 class Node { 68 int val; 69 Node left, right; 70 71 this(int _val, Node _left = null, Node _right = null) { 72 val = _val; 73 left = _left; 74 right = _right; 75 } 76 } 77 78 auto tree = new Node(1, new Node(2), new Node(3, null, new Node(4))); 79 80 import std.stdio; 81 82 writeln(safeDeref(tree).right.right.val); 83 writeln(safeDeref(tree).left.right.left.right); 84 writeln(safeDeref(tree).left.right.left.right.val); 85 } 86 87 // Static test of monadic composition of SafeDeref. 88 unittest { 89 { 90 struct Test { 91 } 92 93 alias A = SafeDeref!Test; 94 alias B = SafeDeref!A; 95 96 static assert(is(B == SafeDeref!Test)); 97 static assert(is(SafeDeref!B == SafeDeref!Test)); 98 } 99 100 // Timon Gehr's original test case 101 { 102 class C { 103 auto foo = safeDeref(C.init); 104 } 105 106 C c = new C; 107 108 //import std.stdio; 109 //writeln(safeDeref(c).foo); // SafeDeref(SafeDeref(null)) 110 111 import std..string; 112 113 auto type = "%s".format(safeDeref(c).foo); 114 assert(type == "SafeDeref!(C)(null)"); 115 } 116 }