June 19, 2014
On Thursday, 19 June 2014 at 15:40:29 UTC, Nick Treleaven wrote:

> fallback? It could have an optional argument to override init.

I like that, makes it obvious what it does. The optional override is also very good and stays in line with the idea of a fallback.
June 19, 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
June 19, 2014
On Wednesday, 18 June 2014 at 19:37:42 UTC, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Jun 18, 2014 at 07:04:33PM +0000, via Digitalmars-d wrote:
>> On Wednesday, 18 June 2014 at 17:56:46 UTC, Mattcoder wrote:
>> >On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne wrote:
>> >>it would be a little more practical to be able to write
>> >>
>> >>writeln(obj.member?.nested?.val);
>> >
>> >If one of these: member, nested or val == null, what will happen with
>> >writeln()? It will print null or it will be avoided?
>> 
>> The expression needs to have exactly one type, and because all of the
>> components can be non-null, it needs to be the type of the last
>> component, in this case `val`. This means that if the one of the
>> components is null, the entire expression needs to return a value of
>> this type, presumably the `.init` value.
>> 
>> The alternative is to raise an exception (or error), but that would
>> defeat the purpose (almost, as it would be slightly better than
>> segfaulting).
>
> Here's a first stab at a library solution:
>...
> T

Won't opDispatch here destroy any hope for statement completion in the future?  I feel like D already has little hope for such tooling features, but this puts the final nail in the coffin.
June 19, 2014
On Thursday, 19 June 2014 at 16:58:03 UTC, Yota wrote:
> Won't opDispatch here destroy any hope for statement completion in the future?  I feel like D already has little hope for such tooling features, but this puts the final nail in the coffin.

Yeah.... D is already really bad for auto completion, opDispatch just makes it harder. If we ever get to the point where the compiler is usable in a library form, that might pave the way to much better auto complete, but it looks far off.
June 19, 2014
On Thu, Jun 19, 2014 at 05:26:31PM +0000, Tofu Ninja via Digitalmars-d wrote:
> On Thursday, 19 June 2014 at 16:58:03 UTC, Yota wrote:
> >Won't opDispatch here destroy any hope for statement completion in the future?  I feel like D already has little hope for such tooling features, but this puts the final nail in the coffin.
> 
> Yeah.... D is already really bad for auto completion, opDispatch just makes it harder. If we ever get to the point where the compiler is usable in a library form, that might pave the way to much better auto complete, but it looks far off.

In this case, the signature constraint on opDispatch could be used for auto completion:

            auto opDispatch(string field)()
                if (is(typeof(__traits(getMember, t, field))))
		...

>From the signature constraint, it should be obvious that "field" must be
among the members of t.

Of course, checking signature constraints isn't always practical in the general case where it may be arbitrarily complex, but obvious cases like this one should be easily manageable, no?


T

-- 
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry
June 19, 2014
>
> In this case, the signature constraint on opDispatch could be used for
> auto completion:
>
>             auto opDispatch(string field)()
>                 if (is(typeof(__traits(getMember, t, field))))
> 		...
>
>>From the signature constraint, it should be obvious that "field" must be
> among the members of t.
>
> Of course, checking signature constraints isn't always practical in the
> general case where it may be arbitrarily complex, but obvious cases like
> this one should be easily manageable, no?
>
In this case auto completion could work flawless because of

alias this t;

At least for cases where even a frontend based tool has a hard time, we could introduce a ddoc section for this, if there really is a need.

/**
 * Dispatch method call.
 * Completion: LIKE t
 */
auto opDispatch(string field)() { ... }

If phobos does this consistently the tools will recognize this.

June 19, 2014
> In this case auto completion could work flawless because of
>
> alias this t;
>
> At least for cases where even a frontend based tool has a hard time, we
> could introduce a ddoc section for this, if there really is a need.
>
> /**
>   * Dispatch method call.
>   * Completion: LIKE t
>   */
> auto opDispatch(string field)() { ... }
>
> If phobos does this consistently the tools will recognize this.
>

Nice, that's a great trick (recognizing alias this t). Mono-D doesn't have that yet, I'll get the word accross
June 19, 2014
On Thu, Jun 19, 2014 at 04:40:22PM +0100, Nick Treleaven via Digitalmars-d wrote:
> On 19/06/2014 16:04, H. S. Teoh via Digitalmars-d wrote:
> >we really should call it something else.  "failsafe" sounds a bit too generic. What about "safeDeref" or just "deref"?
> 
> fallback? It could have an optional argument to override init.

I've thought about adding an optional argument to override init, but unfortunately I can't think of a nice way to implement it. The problem is that in order for the wrapper to work, it has to wrap around each individual component of the dereferencing chain. So if you have a chain like this:

	safeDeref(obj).subobj.prop.field.val

then the safeDeref wrapper only knows about obj, so it wouldn't know what type .val is, because it can't tell beforehand which field you're going to dereference next, and different fields may have different types, nor where is the end of the chain. So it wouldn't know when to return the specified default value.

As of now, I don't know of any way of passing the entire chain to safeDeref without making it really ugly. Writing:

	safeDeref(obj.subobj.prop.field.val, defaultValue)

doesn't work because before the wrapper even gets invoked, you've already dereferenced all the fields (and crashed on the null if there's one in there). The only way I can think of to pass everything to the wrapper is via a variadic list of alias parameters, but it looks very ugly in practice:

	safeDeref!(defaultValue, obj, subobj, prop, field, val)

I've thought about having a penultimate wrapper in the chain:

	safeDeref(obj).subobj.prop.field.failsafe(defaultVal).val

But this doesn't work either for the same reason: failsafe() doesn't
know the next item in the chain will be .val (instead of, say, .length
which has a different type).

A possible compromise is:

	safeDeref(obj).subobj.prop.field.failsafe!val(defaultVal)

where the last element is passed as an alias to the failsafe template, which then does the .init-replacement magic. It looks a lot uglier, though. :-(


T

-- 
People demand freedom of speech to make up for the freedom of thought which they avoid. -- Soren Aabye Kierkegaard (1813-1855)
June 19, 2014
On 06/19/2014 06:58 PM, Yota wrote:
>
> Won't opDispatch here destroy any hope for statement completion in the
> future?  I feel like D already has little hope for such tooling
> features, but this puts the final nail in the coffin.

auto opDispatch(string field)()
    if(is(typeof(__traits(getMember, t, field)))) // <-- not a nail
June 19, 2014
On 2014-06-19 3:11 PM, H. S. Teoh via Digitalmars-d wrote:
>
> 	safeDeref(obj).subobj.prop.field.failsafe!val(defaultVal)
>
> where the last element is passed as an alias to the failsafe template,
> which then does the .init-replacement magic. It looks a lot uglier,
> though. :-(
>
>
> T
>

meh, this works:


writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

..
	static struct SafeDeref {
		T t;
		
		// Make the wrapper as transparent as possible.
		alias t this;

		// this overrides if null
		T or(T defVal){
			if (t is t.init)
			{
				return defVal;
			}
			else
			{
				return t;
			}
			
		}
..