June 18, 2014
On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne wrote:
> I find myself often repeating this boilerplate:
>
> if (obj.member !is null)
> {
> 	if (obj.member.nested !is null)
> 	{
> 		if (obj.member.nested.val !is null)
> 		{
> 			writeln(obj.member.nested.val);
> 		}
> 	}
> }
>
> I have to admit it's a little frustrating when you see swift's ?. syntax notation, it would be a little more practical to be able to write
>
> writeln(obj.member?.nested?.val);
>
>
> Based on http://appventure.me/2014/06/13/swift-optionals-made-simple/
>
>
> Any thoughts?

Use a maybe monad :
Maybe(obj).memeber.nested.val

This doesn't require a language construct. Also, if null check are pervasive in your code, you probably have a problem somewhere.
June 18, 2014
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?

Matheus.
June 18, 2014
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).
June 18, 2014
> Use a maybe monad :
> Maybe(obj).memeber.nested.val
>
> This doesn't require a language construct. Also, if null check are
> pervasive in your code, you probably have a problem somewhere.

There seems to be an implementation here:

https://bitbucket.org/qznc/d-monad/src/5b9d41c611093db74485b017a72473447f8d5595/generic.d?at=master

It doesn't look as good as what you're suggesting,

auto rht = Applicative!Maybe.right(r1, r2);

auto ii42 = Maybe!(Maybe!int).just(i42);

Any idea how that maybe monad would cascade through into the pointer accessors?
June 18, 2014
On Wednesday, 18 June 2014 at 19:04:34 UTC, Marc Schütz wrote:
> ... 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.

I got it! Thanks.

Matheus.
June 18, 2014
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:

	/**
	 * 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

-- 
Just because you survived after you did it, doesn't mean it wasn't stupid!
June 18, 2014
> 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. :)
>

Very clever, this should go straight to std.typecons. I'm definitely ripping it for my project, if you can just scratch something like a boost license on it ;)
June 18, 2014
H. S. Teoh:

> This also allows you to do a complete null check in a single statement:
>
> 	if (maybe(tree).left.right.right.left.right !is null) {

Better with UFCS?

if (tree.perhaps.left.right.right.left.right !is null) {

Bye,
bearophile
June 18, 2014
18-Jun-2014 23:36, H. S. Teoh via Digitalmars-d пишет:
>
> Here's a first stab at a library solution:

[snip]

> 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. :)
>

And most importantly make a pull request!


-- 
Dmitry Olshansky
June 18, 2014
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.