www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Adding the ?. null verification

reply Etienne <etcimon gmail.com> writes:
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?
Jun 18 2014
next sibling parent reply "Kapps" <opantm2+spam gmail.com> writes:
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?

C# is getting the same syntax, and I remember there being some discussion about it here. It's somewhat useful I suppose, though I think it's made significantly more useful in C# with 'a ?? b' (a if a is not null, else b).
Jun 18 2014
next sibling parent Etienne <etcimon gmail.com> writes:
 C# is getting the same syntax, and I remember there being some
 discussion about it here. It's somewhat useful I suppose, though I think
 it's made significantly more useful in C# with 'a ?? b' (a if a is not
 null, else b).

I've seen too many bugs where you end up dereferencing a null pointer, also with the -> being replaced by . in D this is even more confusing when you switch a member from a pointer to a struct. The compiler could easily advise you to use the .? notation when you're moving in too deep into no man's land.
Jun 18 2014
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2014-06-18 17:46, Kapps wrote:

 C# is getting the same syntax, and I remember there being some
 discussion about it here. It's somewhat useful I suppose, though I think
 it's made significantly more useful in C# with 'a ?? b' (a if a is not
 null, else b).

And "a ??= b", assigne "b" to "a", only if "a" is null. -- /Jacob Carlborg
Jun 19 2014
parent Jacob Carlborg <doob me.com> writes:
On 2014-06-19 16:37, Craig Dillabaugh wrote:

 Is this any better than?

 if(!a) a = b;

I would say it's about the same as "a ?? b" is better than "a ? a : b". It's get better since you can use it directly in a return statement: void a () { return a ??= new Object; } -- /Jacob Carlborg
Jun 20 2014
prev sibling next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Etienne:

 writeln(obj.member?.nested?.val);

What about an approach like Scala instead? Bye, bearophile
Jun 18 2014
parent Etienne <etcimon gmail.com> writes:
On 2014-06-18 11:55 AM, bearophile wrote:
 Etienne:

 writeln(obj.member?.nested?.val);

What about an approach like Scala instead? Bye, bearophile

You mean like this? http://stackoverflow.com/questions/1163393/best-scala-imitation-of-groovys-safe-dereference-operator def ?[A](block: => A) = try { block } catch { case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null case e => throw e } val a = ?(b.c.d.e)
Jun 18 2014
prev sibling next sibling parent reply Lionello Lunesu <lionello lunesu.remove.com> writes:
On 18/06/14 23:42, 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?

I want non-null in the type system! To put a nullable reference into a non-null variable you need to check first, but from then on the compiler can ensure it's never null!
Jun 18 2014
parent reply Etienne <etcimon gmail.com> writes:
 I want non-null in the type system! To put a nullable reference into a
 non-null variable you need to check first, but from then on the compiler
 can ensure it's never null!

That would be great but useless for my situation involving some complex AST's class AEntity { Module head; string ident; Type type; AEntity typeInfo; AEntity[] members; // for definitions AEntity[] parameters; // if parameterized ... } It's very useful to descend through parts of a complex tree without adding plenty of bool checks foreach (AEntity member; entity.members) { if (member.typeInfo !is null) { AEntity curr = member; member.ident ~= member.typeInfo.ident; curr = member.typeInfo; while (curr.typeInfo !is null) { member.ident ~= "." ~ curr.typeInfo.ident; curr = curr.typeInfo; } } }
Jun 18 2014
next sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
On 6/18/14, 1:17 PM, Etienne wrote:
 I want non-null in the type system! To put a nullable reference into a
 non-null variable you need to check first, but from then on the compiler
 can ensure it's never null!

That would be great but useless for my situation involving some complex AST's class AEntity { Module head; string ident; Type type; AEntity typeInfo; AEntity[] members; // for definitions AEntity[] parameters; // if parameterized ... } It's very useful to descend through parts of a complex tree without adding plenty of bool checks foreach (AEntity member; entity.members) { if (member.typeInfo !is null) { AEntity curr = member; member.ident ~= member.typeInfo.ident; curr = member.typeInfo; while (curr.typeInfo !is null) { member.ident ~= "." ~ curr.typeInfo.ident; curr = curr.typeInfo; } } }

But if you don't add the bool checks you might get segmentation fault. Or am I missing something?
Jun 18 2014
parent Etienne <etcimon gmail.com> writes:
 But if you don't add the bool checks you might get segmentation fault.
 Or am I missing something?

In D all pointers are zero-filled on initialization so it counts as a bool for me and null is the same as if you had a *.init
Jun 18 2014
prev sibling parent Lionello Lunesu <lionello lunesu.remove.com> writes:
On 19/06/14 00:17, Etienne wrote:
 I want non-null in the type system! To put a nullable reference into a
 non-null variable you need to check first, but from then on the compiler
 can ensure it's never null!

That would be great but useless for my situation involving some complex AST's class AEntity { Module head; string ident; Type type; AEntity typeInfo; AEntity[] members; // for definitions AEntity[] parameters; // if parameterized ... } It's very useful to descend through parts of a complex tree without adding plenty of bool checks foreach (AEntity member; entity.members) { if (member.typeInfo !is null) { AEntity curr = member; member.ident ~= member.typeInfo.ident; curr = member.typeInfo; while (curr.typeInfo !is null) { member.ident ~= "." ~ curr.typeInfo.ident; curr = curr.typeInfo; } } }

And all of those references can be null at any time? Non-null would force you to initialize some/all of those values to a non-null reference, with the compiler ensuring that that is really the case.
Jun 18 2014
prev sibling next sibling parent "Frustrated" <Who where.com> writes:
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?

Would be great! I think though, one could simplify it, you don't need multiple ?'s(one would do) if (?obj.member.nested.val == null) { ... } ?object returns null if the object is null, even if it is dereferenced. possibly a better syntax would be if (??obj.member.nested.val == null) If used as a rval one could have auto x = ??obj.member.nested.val; x is null if obj, obj.member, or obj.member.nested is null. Else obj.member.nested.val. I'm just curious who is going to add the the code to D? If you don't like ?? (confusing it with the null-coalescing operator) then what about ?*, *?, ?&, &?, ???, etc...
Jun 18 2014
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
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.
Jun 18 2014
next sibling parent reply Etienne <etcimon gmail.com> writes:
 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?
Jun 18 2014
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/14, 12:26 PM, Etienne wrote:
 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?

opDispatch?
Jun 18 2014
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/14, 10:39 AM, deadalnix wrote:
 Use a maybe monad :
 Maybe(obj).memeber.nested.val

Yah, I keep on thinking we should explore the maybe monad more thoroughly as a library in D. -- Andrei
Jun 19 2014
prev sibling next sibling parent reply "Mattcoder" <fromtheotherside mail.com> writes:
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.
Jun 18 2014
next sibling parent Etienne <etcimon gmail.com> writes:
 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 ;)
Jun 18 2014
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
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
Jun 18 2014
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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. 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.
Jun 19 2014
parent Etienne <etcimon gmail.com> writes:
 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

Yes, it's more like a failsafe than a maybe. failsafe(c).left.right ...
Jun 19 2014
prev sibling next sibling parent Etienne <etcimon gmail.com> writes:
 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
Jun 19 2014
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
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
Jun 19 2014
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2014-06-18 21:36, H. S. Teoh via Digitalmars-d wrote:

 Here's a first stab at a library solution:

I thought of adding a field to indicate if a value if present or not. If the value is accessed when it's not present it would assert/throw. -- /Jacob Carlborg
Jun 20 2014
prev sibling next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
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).
Jun 18 2014
prev sibling next sibling parent "Mattcoder" <fromtheotherside mail.com> writes:
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.
Jun 18 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
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!
Jun 18 2014
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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
Jun 18 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
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.
Jun 18 2014
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Wednesday, 18 June 2014 at 19:26:21 UTC, Etienne wrote:
 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?

With opDispatch. That is how it is supposed to work.
Jun 18 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Wed, Jun 18, 2014 at 03:46:40PM -0400, Etienne via Digitalmars-d wrote:
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 ;)

Feel free to copy it and do whatever you want with it. It's public domain code, as of right now. :) T -- Marketing: the art of convincing people to pay for what they didn't need before which you can't deliver after.
Jun 18 2014
prev sibling next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
Fun fact btw: if you are using methods via ufcs you can check for 
null in there:

class Foo {}

void something(Foo foo) {
    if(foo is null) return;
    foo.something_internal();
}

auto foo = new Foo();
foo.something(); // cool

foo = null;
foo.something(); // still cool
Jun 18 2014
prev sibling next sibling parent reply "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne wrote:
 writeln(obj.member?.nested?.val);

Optional chaining in swift is meant to be used more like this: if let v = ptr?.attr?.getobj?()?.attr? { writeln(v) } else { writeln("oops?!") } (I don't think Maybe will look as good.)
Jun 18 2014
next sibling parent reply Nick Treleaven <ntrel-public yahoo.co.uk> writes:
On 18/06/2014 21:20, "Ola Fosheim Grøstad" 
<ola.fosheim.grostad+dlang gmail.com>" wrote:
 On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne wrote:
 writeln(obj.member?.nested?.val);

Optional chaining in swift is meant to be used more like this: if let v = ptr?.attr?.getobj?()?.attr? { writeln(v) } else { writeln("oops?!") } (I don't think Maybe will look as good.)

Perhaps Maybe could implement opCast!bool, then we could do: if (auto v = ...)
Jun 19 2014
parent reply Etienne <etcimon gmail.com> writes:
On 2014-06-19 7:31 AM, Nick Treleaven wrote:
 Perhaps Maybe could implement opCast!bool, then we could do:

 if (auto v = ...)

That would be amazing, but maybe is used in haskell for a different purpose, so failsafe could be a more appropriate name I think: if (auto k = tree.failsafe.left && auto v = tree.failsafe.right.leftArr.front.rightMap[k].left) { ... } Would be great
Jun 19 2014
next sibling parent Etienne <etcimon gmail.com> writes:
 I've been thinking about the name. I realize that it's not a true monad
 in the Haskell sense, even though the implementation *was* inspired by
 deadalnix's mentioning of the Maybe monad, so we really should call it
 something else.  "failsafe" sounds a bit too generic. What about
 "safeDeref" or just "deref"?

 OTOH, I wonder how easy it would be to refine the current implementation
 to become a true monad?


 T

ValueType!T / valueType(t) maybe? Its only purpose is to treat ref types as value types in a cascading way without restriction even when they're null... No idea how it could become a monad.
Jun 19 2014
prev sibling next sibling parent reply Nick Treleaven <ntrel-public yahoo.co.uk> writes:
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.
Jun 19 2014
next sibling parent reply Etienne <etcimon gmail.com> writes:
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; } } ..
Jun 19 2014
next sibling parent reply Etienne <etcimon gmail.com> writes:
On 2014-06-19 4:51 PM, H. S. Teoh via Digitalmars-d wrote:
 This assumes that t.init is not a possible valid field value. But in
 that case, there's no need to remap it, you just check for t.init
 instead. For pointers, where .init is null, this isn't a problem, but
 for things like int, where 0 is possible valid value, you may be
 accidentally mapping 0 to the default value when the given field
 actually exists (and has value 0)!

True, you need to mark failure and drag it to the end. Here's another try at it: auto safeDeref(T)(T t, bool failed = false) { static struct SafeDeref { T t; bool fail; // Make the wrapper as transparent as possible. alias t this; auto or(T defVal){ if (fail) { return defVal; } else { return t; } } // 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)); static if (is(typeof(t is null))) { return safeDeref((t is null) ? Memb.init : __traits(getMember, t, field), (t is null) ? true : false); } else { return safeDeref(__traits(getMember, t, field), fail); } } } return SafeDeref(t, failed); }
Jun 19 2014
next sibling parent Etienne <etcimon gmail.com> writes:
On 2014-06-19 5:05 PM, Etienne wrote:
 : __traits(getMember, t, field), (t is null) ? true : false);

(t is null || fail) ? true : false
Jun 19 2014
prev sibling next sibling parent reply Etienne <etcimon gmail.com> writes:
On 2014-06-19 6:23 PM, H. S. Teoh via Digitalmars-d wrote:
 Unfortunately, it appears that opDispatch has become too complex to be
 inlined, so now gdc is unable to simplify it to a series of nested if's.
 :-(


 T

Meh, I don't mind specifying that condition manually after all... having a default value isn't really on top of my list =)
Jun 20 2014
parent Etienne <etcimon gmail.com> writes:
On 2014-06-20 10:29 AM, H. S. Teoh via Digitalmars-d wrote:
 True. Actually, I did my disassembly test again, and now I can't seem to
 coax gdc to optimize out the .exists flag, esp. when .or is involved.
 Perhaps that was a little too ambitious; maybe it's better to stick with
 the original simple solution after all. :P


 T

Try marking the or method as const, and the bool as immutable maybe?
Jun 20 2014
prev sibling parent Etienne <etcimon gmail.com> writes:
On 2014-06-19 6:30 PM, H. S. Teoh via Digitalmars-d wrote:
 On Thu, Jun 19, 2014 at 03:23:33PM -0700, H. S. Teoh via Digitalmars-d wrote:
 [...]
 Unfortunately, it appears that opDispatch has become too complex to be
 inlined, so now gdc is unable to simplify it to a series of nested
 if's.  :-(

Surprisingly, if we just stick .exists in there unconditionally, like you did, then gdc actually optimizes it away completely, so that we're back to the equivalent of nested if's! So your solution is superior after all. :) T

Oh I just saw this. Good, so I can keep my .or() method ! :)
Jun 20 2014
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name. Andrei
Jun 21 2014
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei
Jun 22 2014
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/22/14, 7:26 AM, H. S. Teoh via Digitalmars-d wrote:
 On Sun, Jun 22, 2014 at 01:15:46AM -0700, Andrei Alexandrescu via
Digitalmars-d wrote:
 On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

Yes I got that. I was asking what you thought was a better name for safeDeref.

I'd gotten your point too, and I jokingly answered in the subtext that I don't have a better name. -- Andrei
Jun 22 2014
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2014-06-24 18:52, Yota wrote:
 On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:
 On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
 Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

C# calls the ?? operator the "null-coalescing" operator. I have absolutely no idea why, but that does give precedence for calling it "coalesce".

In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator. -- /Jacob Carlborg
Jun 24 2014
parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
On 6/24/14, 4:43 PM, Jacob Carlborg wrote:
 On 2014-06-24 18:52, Yota wrote:
 On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:
 On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
 Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

C# calls the ?? operator the "null-coalescing" operator. I have absolutely no idea why, but that does give precedence for calling it "coalesce".

In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator.

And in Ruby it's just "||=". How more intuitive can it get? a = nil a ||= 1 The "or" that you are discussing here is just an "||" in Ruby: b = nil || 1
Jun 24 2014
parent Jacob Carlborg <doob me.com> writes:
On 2014-06-24 22:52, Ary Borenszweig wrote:

 And in Ruby it's just "||=". How more intuitive can it get?

 a = nil
 a ||= 1

 The "or" that you are discussing here is just an "||" in Ruby:

 b = nil || 1

Yeah, but that behaves a bit different in D. -- /Jacob Carlborg
Jun 25 2014
prev sibling parent David Gileadi <gileadis NSPMgmail.com> writes:
On 6/21/14, 4:32 PM, deadalnix wrote:
 On Saturday, 21 June 2014 at 22:40:32 UTC, H. S. Teoh via Digitalmars-d
 wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
 Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion? T

maybe is fine. Last time I checked, it isn't only a monad in haskell, but also vastly used by these peoples who speak english.

I like "maybe" too. "nullSafe" might also be a reasonable bikeshed color.
Jun 23 2014
prev sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Tue, Oct 21, 2014 at 11:37:09PM +0000, Meta via Digitalmars-d wrote:
 Did you ever get around to making a pull request for this? It'd be
 nice to have this in Phobos, so I can make one for you (all
 attribution to you, of course) if you don't have time to push it
 through.

No, I've been too busy to work on this. Feel free to make a PR on my behalf. You don't have to attribute it to me. T -- Today's society is one of specialization: as you grow, you learn more and more about less and less. Eventually, you know everything about nothing.
Oct 21 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 03:23:33PM -0700, H. S. Teoh via Digitalmars-d wrote:
[...]
 Unfortunately, it appears that opDispatch has become too complex to be
 inlined, so now gdc is unable to simplify it to a series of nested
 if's.  :-(

Surprisingly, if we just stick .exists in there unconditionally, like you did, then gdc actually optimizes it away completely, so that we're back to the equivalent of nested if's! So your solution is superior after all. :) T -- "No, John. I want formats that are actually useful, rather than over-featured megaliths that address all questions by piling on ridiculous internal links in forms which are hideously over-complex." -- Simon St. Laurent on xml-dev
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Jun 20, 2014 at 08:57:46AM -0400, Etienne via Digitalmars-d wrote:
 On 2014-06-19 6:23 PM, H. S. Teoh via Digitalmars-d wrote:
Unfortunately, it appears that opDispatch has become too complex to
be inlined, so now gdc is unable to simplify it to a series of nested
if's.
:-(


T

Meh, I don't mind specifying that condition manually after all... having a default value isn't really on top of my list =)

True. Actually, I did my disassembly test again, and now I can't seem to coax gdc to optimize out the .exists flag, esp. when .or is involved. Perhaps that was a little too ambitious; maybe it's better to stick with the original simple solution after all. :P T -- Laissez-faire is a French term commonly interpreted by Conservatives to mean 'lazy fairy,' which is the belief that if governments are lazy enough, the Good Fairy will come down from heaven and do all their work for them.
Jun 20 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Tue, Jun 24, 2014 at 09:43:21PM +0200, Jacob Carlborg via Digitalmars-d
wrote:
 On 2014-06-24 18:52, Yota wrote:
On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:
On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via
Digitalmars-d wrote:
On 6/19/14, 1:29 PM, Etienne wrote:
writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

C# calls the ?? operator the "null-coalescing" operator. I have absolutely no idea why, but that does give precedence for calling it "coalesce".

In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator.

So D code is going to start looking like this now?! if (myobj.elvis.subobj.memb.isAlive.or(false)) { ... } T -- "How are you doing?" "Doing what?"
Jun 24 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Tuesday, 24 June 2014 at 19:56:14 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 So D code is going to start looking like this now?!

 	if (myobj.elvis.subobj.memb.isAlive.or(false)) {
 		...
 	}

What about ifExists? if (myObj.ifExists.subobj.member.isAlive.or(false)) { //... }
Jun 24 2014
prev sibling parent "Meta" <jared771 gmail.com> writes:
Did you ever get around to making a pull request for this? It'd 
be nice to have this in Phobos, so I can make one for you (all 
attribution to you, of course) if you don't have time to push it 
through.
Oct 21 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 02:37:33PM -0700, H. S. Teoh via Digitalmars-d wrote:
[...]
 Here, I've made it so that the .exists field is only present if the
 wrapped type T doesn't have a null value that can serve as an
 existence marker.
 
 I'm not sure if this is the right thing to do 100% of the time,
 because conceivably, somebody might want to distinguish between a
 pointer field that has null as a value, as opposed to a pointer field
 that doesn't exist. But I think such cases should be very rare; and
 cutting out the .exists field where unnecessary also keeps the size of
 the wrapper struct minimal, so that it's easier for the compiler's
 optimizer to completely optimize it out for pointer values.
 (Unfortunately I ran into a snag with gdc due to latest dmd features
 that haven't made it into gdc, so I haven't been able to actually
 check the assembly output -- I'll try to iron that out and post the
 results.)

Unfortunately, it appears that opDispatch has become too complex to be inlined, so now gdc is unable to simplify it to a series of nested if's. :-( T -- It's bad luck to be superstitious. -- YHL
Jun 19 2014
prev sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Sun, Jun 22, 2014 at 01:15:46AM -0700, Andrei Alexandrescu via Digitalmars-d
wrote:
 On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d
wrote:
On 6/19/14, 1:29 PM, Etienne wrote:
writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

Yes I got that. I was asking what you thought was a better name for safeDeref. T -- Fact is stranger than fiction.
Jun 22 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Wed, Jun 18, 2014 at 12:36:01PM -0700, H. S. Teoh via Digitalmars-d wrote:
 On Wed, Jun 18, 2014 at 07:04:33PM +0000, via Digitalmars-d wrote:

 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:

I decided to run some tests on the compiled code to see how performant it was. With dmd, I was unable to get it to inline all the opDispatch calls, even with -O -inline, so the result was rather disappointing. However, with gdc -O3 -finline, all of the opDispatch calls got inlined, and the assembly is the direct equivalent of: if (tree !is null) if (tree.left !is null) ... writeln(tree.left.right. ... .val); So this is evidence that even this preliminary implementation already has rather good potential. Perhaps I'll kick up a pull request for it soon. :) T -- In theory, software is implemented according to the design that has been carefully worked out beforehand. In practice, design documents are written after the fact to describe the sorry mess that has gone on before.
Jun 18 2014
prev sibling next sibling parent "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne wrote:
 writeln(obj.member?.nested?.val);

You can implement something similar to this using UFCS: auto q(C)(auto ref C c) { struct Q { template opDispatch(string name) { property auto opDispatch() { return c ? mixin(q{c.}~name) : null; } } property auto dummy() { } } return Q(); } unittest { class C { C c; } C c = new C; c.c = new C; assert(c.q.c !is null); assert(c.q.c.q.c is null); assert(c.q.c.q.c.q.c is null); }
Jun 18 2014
prev sibling next sibling parent "logicchains" <jonathan.t.barnard gmail.com> writes:
On Thursday, 19 June 2014 at 00:36:23 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 I decided to run some tests on the compiled code to see how 
 performant
 However, with gdc -O3 -finline, all of the opDispatch calls got 
 inlined,
 and the assembly is the direct equivalent of:

 	if (tree !is null)
 		if (tree.left !is null)
 			...
 				writeln(tree.left.right. ... .val);

Somebody should blog on this or put it on the front page or something; how many other languages allow a cost-free maybe monad to be implemented in library code?
Jun 18 2014
prev sibling next sibling parent =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= <per.nordlow gmail.com> writes:
On Thursday, 19 June 2014 at 03:10:50 UTC, logicchains wrote:
 Somebody should blog on this or put it on the front page or 
 something; how many other languages allow a cost-free maybe 
 monad to be implemented in library code?

I agree. This is supercool!
Jun 19 2014
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Etienne:

 Yes, it's more like a failsafe than a maybe. 
 failsafe(c).left.right ...

I suggest to not call it Maybe/maybe to not confuse it with the Haskell ones. Bye, bearophile
Jun 19 2014
prev sibling next sibling parent "Craig Dillabaugh" <craig.dillabaugh gmail.com> writes:
On Thursday, 19 June 2014 at 10:10:30 UTC, Jacob Carlborg wrote:
 On 2014-06-18 17:46, Kapps wrote:

 C# is getting the same syntax, and I remember there being some
 discussion about it here. It's somewhat useful I suppose, 
 though I think
 it's made significantly more useful in C# with 'a ?? b' (a if 
 a is not
 null, else b).

And "a ??= b", assigne "b" to "a", only if "a" is null.

Is this any better than? if(!a) a = b;
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 09:15:47AM -0400, Etienne via Digitalmars-d wrote:
 On 2014-06-19 7:31 AM, Nick Treleaven wrote:
Perhaps Maybe could implement opCast!bool, then we could do:

if (auto v = ...)

That would be amazing, but maybe is used in haskell for a different purpose, so failsafe could be a more appropriate name I think:

I've been thinking about the name. I realize that it's not a true monad in the Haskell sense, even though the implementation *was* inspired by deadalnix's mentioning of the Maybe monad, so we really should call it something else. "failsafe" sounds a bit too generic. What about "safeDeref" or just "deref"? OTOH, I wonder how easy it would be to refine the current implementation to become a true monad? T -- What is Matter, what is Mind? Never Mind, it doesn't Matter.
Jun 19 2014
prev sibling next sibling parent "Tofu Ninja" <emmons0 purdue.edu> writes:
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.
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
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
Jun 19 2014
prev sibling next sibling parent "Yota" <yotaxp thatGoogleMailThing.com> writes:
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.
Jun 19 2014
prev sibling next sibling parent "Tofu Ninja" <emmons0 purdue.edu> writes:
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.
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
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

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
Jun 19 2014
prev sibling next sibling parent "Tobias Pankrath" <tobias pankrath.net> writes:
 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

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?

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.
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
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)
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 04:29:12PM -0400, Etienne via Digitalmars-d wrote:
 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)

This assumes that t.init is not a possible valid field value. But in that case, there's no need to remap it, you just check for t.init instead. For pointers, where .init is null, this isn't a problem, but for things like int, where 0 is possible valid value, you may be accidentally mapping 0 to the default value when the given field actually exists (and has value 0)!
 			{
 				return defVal;
 			}
 			else
 			{
 				return t;
 			}
 			
 		}
 ..

T -- Gone Chopin. Bach in a minuet.
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 01:51:17PM -0700, H. S. Teoh via Digitalmars-d wrote:
 On Thu, Jun 19, 2014 at 04:29:12PM -0400, Etienne via Digitalmars-d wrote:

 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)

This assumes that t.init is not a possible valid field value. But in that case, there's no need to remap it, you just check for t.init instead. For pointers, where .init is null, this isn't a problem, but for things like int, where 0 is possible valid value, you may be accidentally mapping 0 to the default value when the given field actually exists (and has value 0)!

Actually, on second thoughts, maybe it *is* possible after all. The key is to differentiate between null-able types, and non-nullable types. For the latter, we expand the SafeDeref struct to store a boolean flag to indicate whether or not the value exists, then .or() can check that flag to see if it should return the default value. Basically, something like this: struct SafeDeref { T t; static if (!is(t = null)) bool exists = true; ... T or(T defVal) { return (exists) ? t : defVal; } ... auto opDispatch(...) { ... if (t is null) return safeDeref(Memb.init, false) else return safeDeref(__traits(getMember...), true); } } I'll try this out to see if it works! This also has the advantage of becoming even closer to a real monad: by checking for .exists, we can also overload operators to return a wrapper with exists=false whenever one of the operands also has exists=false. T -- Why can't you just be a nonconformist like everyone else? -- YHL
Jun 19 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Jun 19, 2014 at 05:05:51PM -0400, Etienne via Digitalmars-d wrote:
 On 2014-06-19 4:51 PM, H. S. Teoh via Digitalmars-d wrote:
This assumes that t.init is not a possible valid field value. But in
that case, there's no need to remap it, you just check for t.init
instead. For pointers, where .init is null, this isn't a problem, but
for things like int, where 0 is possible valid value, you may be
accidentally mapping 0 to the default value when the given field
actually exists (and has value 0)!

True, you need to mark failure and drag it to the end. Here's another try at it: auto safeDeref(T)(T t, bool failed = false) { static struct SafeDeref { T t; bool fail;

The trouble with this is that you pay for the storage cost of .fail even if T already has a perfectly fine null value which serves the same purpose. Here's my take on it: /** * 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 { enum hasNullValue(U) = is(typeof(U is null)); struct SafeDeref { T t; // Make the wrapper as transparent as possible. alias t this; // Wrapped types that don't have a null value get an additional // flag to indicate existence. static if (!hasNullValue!T) { bool exists = true; T or(T defaultVal) { return exists ? t : defaultVal; } } else { T or(T defaultVal) { return t is null ? T.init : t; } } // 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))) { if (t is null) { static if (hasNullValue!Memb) return SafeDeref!Memb(Memb.init); else return SafeDeref!Memb(Memb.init, false); } return SafeDeref!Memb(__traits(getMember, t, field)); } else { return SafeDeref!Memb(__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; assert(safeDeref(tree).right.right.val == 4); assert(safeDeref(tree).left.right.left.right is null); assert(safeDeref(tree).left.right.left.right.val == 0); // The wrapper also exposes an .or method that returns the specified // default value if the dereferenced value doesn't exist. assert(safeDeref(tree).right.right.val.or(-1) == 4); assert(safeDeref(tree).left.right.left.right.val.or(-1) == -1); } // ... snip ... The last 2 lines of the unittest demonstrate the new functionality. Here, I've made it so that the .exists field is only present if the wrapped type T doesn't have a null value that can serve as an existence marker. I'm not sure if this is the right thing to do 100% of the time, because conceivably, somebody might want to distinguish between a pointer field that has null as a value, as opposed to a pointer field that doesn't exist. But I think such cases should be very rare; and cutting out the .exists field where unnecessary also keeps the size of the wrapper struct minimal, so that it's easier for the compiler's optimizer to completely optimize it out for pointer values. (Unfortunately I ran into a snag with gdc due to latest dmd features that haven't made it into gdc, so I haven't been able to actually check the assembly output -- I'll try to iron that out and post the results.) T -- I am not young enough to know everything. -- Oscar Wilde
Jun 19 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Thursday, 19 June 2014 at 21:34:07 UTC, Andrei Alexandrescu 
wrote:
 On 6/18/14, 10:39 AM, deadalnix wrote:
 Use a maybe monad :
 Maybe(obj).memeber.nested.val

Yah, I keep on thinking we should explore the maybe monad more thoroughly as a library in D. -- Andrei

The other night for fun I tried implementing an Option type as a RandomAccessRange. When a value exists, the range is not empty and has length 1, otherwise it is empty and has length 0, with the other primitives doing as expected. It's neat how you then automatically get all current and future range algorithms for free. One problem I found trying to use map like bind in Haskell is that it does the exact opposite of what I want. The result is not Option!int, but MapResult!(__lambda3, Option!int).
Jun 19 2014
prev sibling next sibling parent "Bienlein" <jeti789 web.de> writes:
On Wednesday, 18 June 2014 at 15:57:40 UTC, Etienne wrote:
 On 2014-06-18 11:55 AM, bearophile wrote:
 Etienne:

 writeln(obj.member?.nested?.val);

What about an approach like Scala instead? Bye, bearophile

You mean like this? http://stackoverflow.com/questions/1163393/best-scala-imitation-of-groovys-safe-dereference-operator def ?[A](block: => A) = try { block } catch { case e: NullPointerException if e.getStackTrace()(2).getMethodName == "$qmark" => null case e => throw e } val a = ?(b.c.d.e)

I think he means to use the Option class instead of returning null. Also Rust does it that way.
Jun 20 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d
wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion? T -- Once the bikeshed is up for painting, the rainbow won't suffice. -- Andrei Alexandrescu
Jun 21 2014
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 21 June 2014 at 22:40:32 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu 
 via Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion? T

maybe is fine. Last time I checked, it isn't only a monad in haskell, but also vastly used by these peoples who speak english.
Jun 21 2014
prev sibling parent "Yota" <yotaxp thatGoogleMailThing.com> writes:
On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu 
wrote:
 On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:
 On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu 
 via Digitalmars-d wrote:
 On 6/19/14, 1:29 PM, Etienne wrote:
 writeln(currAssignment.safeDeref.typeInfo.ident.or("meh"));

"or" is really nice and terse. I think we should add that to std. safeDeref isn't the best choice of name.

What's your suggestion?

That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- Andrei

C# calls the ?? operator the "null-coalescing" operator. I have absolutely no idea why, but that does give precedence for calling it "coalesce". Then again, who wants to memorize how that's spelled? They also have "GetValueOrDefault()" on Nullable<>, but that is kinda long. How about just "orDefault" or "ifNull"?
Jun 24 2014