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 }