On Wednesday, 18 June 2014 at 19:37:42 UTC, H. S. Teoh via
Digitalmars-d wrote:
Here's a first stab at a library solution:
/**
* Simple-minded implementation of a Maybe monad.
*
* Params: t = data to wrap.
* Returns: A wrapper around the given type, with "safe"
member dereference
* semantics, that is, if t is null, then any further member
dereferences will
* just return a wrapper around the .init value of the wrapped
type, instead of
* deferencing the null and crashing.
*/
auto maybe(T)(T t) {
static struct Maybe {
T t;
// Make the wrapper as transparent as possible.
alias t this;
// This is the magic that makes it all work.
auto opDispatch(string field)()
if (is(typeof(__traits(getMember, t, field))))
{
alias Memb = typeof(__traits(getMember, t,
field));
// If T is comparable with null, then we do a
null
// check. Otherwise, we just dereference the
member
// since it's guaranteed to be safe of null
// dereferences.
//
// N.B.: we always return a wrapped type in
case the
// return type contains further nullable fields.
static if (is(typeof(t is null))) {
return maybe((t is null) ? Memb.init
:
__traits(getMember, t, field));
} else {
return maybe(__traits(getMember, t,
field));
}
}
}
return Maybe(t);
}
/**
* A sample tree structure to test the above code.
*/
class Node {
int val;
Node left, right;
this(int _val, Node _left=null, Node _right=null) {
val = _val;
left = _left;
right = _right;
}
}
void main() {
import std.stdio;
auto tree = new Node(1,
new Node(2),
new Node(3,
null,
new Node(4)
)
);
writeln(maybe(tree).right.right.val);
writeln(maybe(tree).left.right.left.right);
writeln(maybe(tree).left.right.left.right.val);
}
Program output:
4
Maybe(null)
0
It's not perfect, but as you can see, attempting to dereference
null
just returns the result type's .init value. You can also modify
the code
where it returns Memb.init, to also log a message to a debug
log that
indicates a possible logic problem with the code (failure to
check for
null, etc.).
This also allows you to do a complete null check in a single
statement:
if (maybe(tree).left.right.right.left.right !is null) {
// do something with that value
}
If nothing else, this at least saves you the trouble of having
to check
every intermediate reference in the chain. :)
The only wart I can see currently is the "Maybe(null)"
appearing in the
writeln output, instead of just "null", but that can be worked
around by
implementing a toString method in the Maybe struct that
forwards to the
wrapped type's toString method (or something along those lines).
Anyway, this is just a first shot at it. You can probably build
something more sophisticated out of it. :)
T
That's nifty. opDispatch can do some cool stuff.