Adding the ?. null verification

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Thu Jun 19 09:52:40 PDT 2014


On Thu, Jun 19, 2014 at 10:35:59AM +0200, Timon Gehr via Digitalmars-d wrote:
> On 06/18/2014 09:36 PM, H. S. Teoh via Digitalmars-d wrote:
> >Here's a first stab at a library solution:
> >
> >	/**
> >	 * Simple-minded implementation of a Maybe monad.
> >	 *
> 
> Nitpick: Please do not call it a 'Maybe monad'.
> It is not a monad: It's neither a functor not does it have a μ
> operator.  (This could be fixed though.) Furthermore, opDispatch does
> not behave analogously to a (restricted) monadic bind operator:
> 
> class C{ auto foo=maybe(C.init); }
> 
> void main(){
>     import std.stdio;
>     C c=new C;
>     writeln(maybe(c).foo); // Maybe(Maybe(null))
> }
> 
> The result should be Maybe(null), if the data type was to remotely
> resemble a monad.

Here's a slightly improved version that collapses nested wrappers into a
single wrapper, so that Maybe!(Maybe!(Maybe!...Maybe!T)...) == Maybe!T:

	/**
	 * A safe-dereferencing wrapper resembling a Maybe monad.
	 *
	 * If the wrapped object is null, any further member dereferences will simply
	 * return a wrapper around the .init value of the member's type. Since non-null
	 * member dereferences will also return a wrapped value, any null value in the
	 * middle of a chain of nested dereferences will simply cause the final result
	 * to default to the .init value of the final member's type.
	 */
	template SafeDeref(T)
	{
	    static if (is(T U == SafeDeref!V, V))
	    {
	        // Merge SafeDeref!(SafeDeref!X) into just SafeDeref!X.
	        alias SafeDeref = U;
	    }
	    else
	    {
	        struct SafeDeref
	        {
	            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 safeDeref((t is null) ? Memb.init
	                                                 : __traits(getMember, t,
	                                                            field));
	                } else {
	                    return safeDeref(__traits(getMember, t, field));
	                }
	            }
	        }
	    }
	}
	
	/**
	 * Wraps an object in a safe dereferencing wrapper resembling a Maybe monad.
	 *
	 * If the object is null, then any further member dereferences will just return
	 * a wrapper around the .init value of the wrapped type, instead of
	 * dereferencing null. This applies recursively to any element in a chain of
	 * dereferences.
	 *
	 * Params: t = data to wrap.
	 * Returns: A wrapper around the given type, with "safe" member dereference
	 * semantics.
	 */
	auto safeDeref(T)(T t)
	{
	    return SafeDeref!T(t);
	}
	
	unittest
	{
	    class Node
	    {
	        int val;
	        Node left, right;
	
	        this(int _val, Node _left=null, Node _right=null)
	        {
	            val = _val;
	            left = _left;
	            right = _right;
	        }
	    }
	
	    auto tree = new Node(1,
	        new Node(2),
	        new Node(3,
	            null,
	            new Node(4)
	        )
	    );
	
	    import std.stdio;
	    writeln(safeDeref(tree).right.right.val);
	    writeln(safeDeref(tree).left.right.left.right);
	    writeln(safeDeref(tree).left.right.left.right.val);
	}
	
	// Static test of monadic composition of SafeDeref.
	unittest
	{
	    {
	        struct Test {}
	        alias A = SafeDeref!Test;
	        alias B = SafeDeref!A;
	
	        static assert(is(B == SafeDeref!Test));
	        static assert(is(SafeDeref!B == SafeDeref!Test));
	    }
	
	    // Timon Gehr's original test case
	    {
	        class C
	        {
	            auto foo = safeDeref(C.init);
	        }
	
	        C c = new C;
	
	        //import std.stdio;
	        //writeln(safeDeref(c).foo); // SafeDeref(SafeDeref(null))
	
	        import std.string;
	        auto type = "%s".format(safeDeref(c).foo);
	        assert(type == "SafeDeref!(C)(null)");
	    }
	}


> Furthermore, 'Maybe' is a more natural name for a type constructor
> that adds an additional element to another type, and 'Maybe monad' in
> particular is a term that already refers to this different meaning
> even more strongly in other communities.

Agreed.  I've renamed it to "SafeDeref", since it's not really a monad
per se, just a safe dereferencing wrapper that has Maybe-monad-like
properties.


T

-- 
If it breaks, you get to keep both pieces. -- Software disclaimer notice


More information about the Digitalmars-d mailing list