digitalmars.D - Adding the ?. null verification
- Etienne (16/16) Jun 18 2014 I find myself often repeating this boilerplate:
- Kapps (5/23) Jun 18 2014 C# is getting the same syntax, and I remember there being some
- Etienne (5/9) Jun 18 2014 I've seen too many bugs where you end up dereferencing a null pointer,
- Jacob Carlborg (4/8) Jun 19 2014 And "a ??= b", assigne "b" to "a", only if "a" is null.
- Craig Dillabaugh (3/11) Jun 19 2014 Is this any better than?
- Jacob Carlborg (9/11) Jun 20 2014 I would say it's about the same as "a ?? b" is better than "a ? a : b".
- bearophile (4/5) Jun 18 2014 What about an approach like Scala instead?
- Etienne (10/15) Jun 18 2014 You mean like this?
- Bienlein (3/21) Jun 20 2014 I think he means to use the Option class instead of returning
- Lionello Lunesu (4/20) Jun 18 2014 I want non-null in the type system! To put a nullable reference into a
- Etienne (28/31) Jun 18 2014 That would be great but useless for my situation involving some complex
- Ary Borenszweig (3/34) Jun 18 2014 But if you don't add the bool checks you might get segmentation fault.
- Etienne (2/4) Jun 18 2014 In D all pointers are zero-filled on initialization so it counts as a
- Lionello Lunesu (4/35) Jun 18 2014 And all of those references can be null at any time? Non-null would
- Frustrated (18/36) Jun 18 2014 Would be great! I think though, one could simplify it, you don't
- deadalnix (5/23) Jun 18 2014 Use a maybe monad :
- Etienne (7/11) Jun 18 2014 There seems to be an implementation here:
- deadalnix (2/15) Jun 18 2014 With opDispatch. That is how it is supposed to work.
- Andrei Alexandrescu (2/14) Jun 18 2014 opDispatch?
- Andrei Alexandrescu (3/5) Jun 19 2014 Yah, I keep on thinking we should explore the maybe monad more
- Meta (10/15) Jun 19 2014 The other night for fun I tried implementing an Option type as a
- Mattcoder (4/6) Jun 18 2014 If one of these: member, nested or val == null, what will happen
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (9/15) Jun 18 2014 The expression needs to have exactly one type, and because all of
- Mattcoder (3/6) Jun 18 2014 I got it! Thanks.
- H. S. Teoh via Digitalmars-d (95/113) Jun 18 2014 Here's a first stab at a library solution:
- Etienne (3/9) Jun 18 2014 Very clever, this should go straight to std.typecons. I'm definitely
- H. S. Teoh via Digitalmars-d (7/21) Jun 18 2014 Feel free to copy it and do whatever you want with it. It's public
- bearophile (5/8) Jun 18 2014 Better with UFCS?
- Dmitry Olshansky (5/12) Jun 18 2014 And most importantly make a pull request!
- Meta (3/107) Jun 18 2014 That's nifty. opDispatch can do some cool stuff.
- Timon Gehr (17/21) Jun 19 2014 Nitpick: Please do not call it a 'Maybe monad'.
- Etienne (1/5) Jun 19 2014 Yes, it's more like a failsafe than a maybe. failsafe(c).left.right ...
- bearophile (5/7) Jun 19 2014 I suggest to not call it Maybe/maybe to not confuse it with the
- H. S. Teoh via Digitalmars-d (134/160) Jun 19 2014 Here's a slightly improved version that collapses nested wrappers into a
- Yota (5/33) Jun 19 2014 Won't opDispatch here destroy any hope for statement completion
- Tofu Ninja (5/8) Jun 19 2014 Yeah.... D is already really bad for auto completion, opDispatch
- H. S. Teoh via Digitalmars-d (13/23) Jun 19 2014 In this case, the signature constraint on opDispatch could be used for
- Tobias Pankrath (11/25) Jun 19 2014 In this case auto completion could work flawless because of
- Etienne (2/12) Jun 19 2014 Nice, that's a great trick (recognizing alias this t). Mono-D doesn't
- Timon Gehr (3/6) Jun 19 2014 auto opDispatch(string field)()
- Jacob Carlborg (5/6) Jun 20 2014 I thought of adding a field to indicate if a value if present or not. If...
- H. S. Teoh via Digitalmars-d (21/33) Jun 18 2014 [...]
- logicchains (5/14) Jun 18 2014 Somebody should blog on this or put it on the front page or
- =?UTF-8?B?Ik5vcmRsw7Z3Ig==?= (2/5) Jun 19 2014 I agree. This is supercool!
- Adam D. Ruppe (11/11) Jun 18 2014 Fun fact btw: if you are using methods via ufcs you can check for
- "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= (8/9) Jun 18 2014 Optional chaining in swift is meant to be used more like this:
- Nick Treleaven (4/15) Jun 19 2014 Perhaps Maybe could implement opCast!bool, then we could do:
- Etienne (9/11) Jun 19 2014 That would be amazing, but maybe is used in haskell for a different
- H. S. Teoh via Digitalmars-d (12/19) Jun 19 2014 [...]
- Etienne (3/11) Jun 19 2014 ValueType!T / valueType(t) maybe? Its only purpose is to treat ref types...
- Nick Treleaven (2/5) Jun 19 2014 fallback? It could have an optional argument to override init.
- Tofu Ninja (3/4) Jun 19 2014 I like that, makes it obvious what it does. The optional override
- H. S. Teoh via Digitalmars-d (34/39) Jun 19 2014 I've thought about adding an optional argument to override init, but
- Etienne (22/27) Jun 19 2014 meh, this works:
- H. S. Teoh via Digitalmars-d (10/47) Jun 19 2014 This assumes that t.init is not a possible valid field value. But in
- Etienne (36/42) Jun 19 2014 True, you need to mark failure and drag it to the end. Here's another
- Etienne (2/3) Jun 19 2014 (t is null || fail) ? true : false
- H. S. Teoh via Digitalmars-d (143/158) Jun 19 2014 The trouble with this is that you pay for the storage cost of .fail even
- H. S. Teoh via Digitalmars-d (9/24) Jun 19 2014 [...]
- Etienne (3/7) Jun 20 2014 Meh, I don't mind specifying that condition manually after all... having...
- H. S. Teoh via Digitalmars-d (8/21) Jun 20 2014 True. Actually, I did my disassembly test again, and now I can't seem to
- Etienne (2/7) Jun 20 2014 Try marking the or method as const, and the bool as immutable maybe?
- H. S. Teoh via Digitalmars-d (10/13) Jun 19 2014 [...]
- Etienne (2/13) Jun 20 2014 Oh I just saw this. Good, so I can keep my .or() method ! :)
- H. S. Teoh via Digitalmars-d (33/56) Jun 19 2014 [...]
- Andrei Alexandrescu (4/5) Jun 21 2014 "or" is really nice and terse. I think we should add that to std.
- H. S. Teoh via Digitalmars-d (7/12) Jun 21 2014 [...]
- deadalnix (4/15) Jun 21 2014 maybe is fine. Last time I checked, it isn't only a monad in
- David Gileadi (2/19) Jun 23 2014 I like "maybe" too. "nullSafe" might also be a reasonable bikeshed color...
- Andrei Alexandrescu (3/11) Jun 22 2014 That we add "or" to std and find a name for safeDeref that doesn't use
- H. S. Teoh via Digitalmars-d (6/19) Jun 22 2014 Yes I got that. I was asking what you thought was a better name for
- Andrei Alexandrescu (3/19) Jun 22 2014 I'd gotten your point too, and I jokingly answered in the subtext that I...
- Yota (7/21) Jun 24 2014 C# calls the ?? operator the "null-coalescing" operator. I have
- Jacob Carlborg (5/23) Jun 24 2014 In CoffeeScript it's called The Existential Operator. It's also known as...
- H. S. Teoh via Digitalmars-d (9/32) Jun 24 2014 [...]
- Meta (7/11) Jun 24 2014 What about ifExists?
- Meta (4/4) Oct 21 2014 Did you ever get around to making a pull request for this? It'd
- H. S. Teoh via Digitalmars-d (8/12) Oct 21 2014 No, I've been too busy to work on this. Feel free to make a PR on my
- Ary Borenszweig (6/28) Jun 24 2014 And in Ruby it's just "||=". How more intuitive can it get?
- Jacob Carlborg (4/9) Jun 25 2014 Yeah, but that behaves a bit different in D.
- Vladimir Panteleev (26/27) Jun 18 2014 You can implement something similar to this using UFCS:
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
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?discussion about it here. It's somewhat useful I suppose, though (a if a is not null, else b).
Jun 18 2014
discussion about it here. It's somewhat useful I suppose, though I think 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
On 2014-06-18 17:46, Kapps wrote:discussion about it here. It's somewhat useful I suppose, though I think null, else b).And "a ??= b", assigne "b" to "a", only if "a" is null. -- /Jacob Carlborg
Jun 19 2014
On Thursday, 19 June 2014 at 10:10:30 UTC, Jacob Carlborg wrote:On 2014-06-18 17:46, Kapps wrote:Is this any better than? if(!a) a = b;discussion about it here. It's somewhat useful I suppose, though I think a is not null, else b).And "a ??= b", assigne "b" to "a", only if "a" is null.
Jun 19 2014
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
Etienne:writeln(obj.member?.nested?.val);What about an approach like Scala instead? Bye, bearophile
Jun 18 2014
On 2014-06-18 11:55 AM, bearophile wrote:Etienne: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)writeln(obj.member?.nested?.val);What about an approach like Scala instead? Bye, bearophile
Jun 18 2014
On Wednesday, 18 June 2014 at 15:57:40 UTC, Etienne wrote:On 2014-06-18 11:55 AM, bearophile wrote:I think he means to use the Option class instead of returning null. Also Rust does it that way.Etienne: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)writeln(obj.member?.nested?.val);What about an approach like Scala instead? Bye, bearophile
Jun 20 2014
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
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
On 6/18/14, 1:17 PM, Etienne wrote:But if you don't add the bool checks you might get segmentation fault. Or am I missing something?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
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
On 19/06/14 00:17, Etienne wrote: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.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
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
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
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
On Wednesday, 18 June 2014 at 19:26:21 UTC, Etienne wrote:With opDispatch. That is how it is supposed to work.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
On 6/18/14, 12:26 PM, Etienne wrote:opDispatch?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
On 6/18/14, 10:39 AM, deadalnix wrote:Use a maybe monad : Maybe(obj).memeber.nested.valYah, I keep on thinking we should explore the maybe monad more thoroughly as a library in D. -- Andrei
Jun 19 2014
On Thursday, 19 June 2014 at 21:34:07 UTC, Andrei Alexandrescu wrote:On 6/18/14, 10:39 AM, deadalnix wrote: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).Use a maybe monad : Maybe(obj).memeber.nested.valYah, I keep on thinking we should explore the maybe monad more thoroughly as a library in D. -- Andrei
Jun 19 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.
Jun 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: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).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?
Jun 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.
Jun 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: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!On Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne 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).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?
Jun 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 ;)
Jun 18 2014
On Wed, Jun 18, 2014 at 03:46:40PM -0400, Etienne via Digitalmars-d wrote: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.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
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
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
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. :) TThat's nifty. opDispatch can do some cool stuff.
Jun 18 2014
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
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 operatorYes, it's more like a failsafe than a maybe. failsafe(c).left.right ...
Jun 19 2014
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
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 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)"); } }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.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
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: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.On Wednesday, 18 June 2014 at 17:56:46 UTC, Mattcoder wrote:Here's a first stab at a library solution: ... TOn Wednesday, 18 June 2014 at 15:42:04 UTC, Etienne 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).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?
Jun 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.
Jun 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: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)))) ...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.From the signature constraint, it should be obvious that "field" must beamong 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
Jun 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)))) ...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.From the signature constraint, it should be obvious that "field" must beamong 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?
Jun 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
Jun 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
Jun 19 2014
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
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:[...][...] 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.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:
Jun 18 2014
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
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
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
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
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:Perhaps Maybe could implement opCast!bool, then we could do: if (auto v = ...)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 19 2014
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
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:[...] 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.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:
Jun 19 2014
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? TValueType!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
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
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
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: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)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
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. :-( Tmeh, 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
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: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)!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. :-( Tmeh, 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; } } ..T -- Gone Chopin. Bach in a minuet.
Jun 19 2014
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
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
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: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 WildeThis 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;
Jun 19 2014
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
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. :-( TMeh, 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
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: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.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. :-( TMeh, 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
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 TTry marking the or method as const, and the bool as immutable maybe?
Jun 20 2014
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
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: [...]Oh I just saw this. Good, so I can keep my .or() method ! :)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
Jun 20 2014
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:[...][...] 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? -- YHLmeh, 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)!
Jun 19 2014
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
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:[...] What's your suggestion? T -- Once the bikeshed is up for painting, the rainbow won't suffice. -- Andrei Alexandrescuwriteln(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.
Jun 21 2014
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: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.On 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion? Twriteln(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.
Jun 21 2014
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:I like "maybe" too. "nullSafe" might also be a reasonable bikeshed color.On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote: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.On 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion? Twriteln(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.
Jun 23 2014
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:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 22 2014
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:Yes I got that. I was asking what you thought was a better name for safeDeref. T -- Fact is stranger than fiction.On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 22 2014
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:I'd gotten your point too, and I jokingly answered in the subtext that I don't have a better name. -- AndreiOn 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:Yes I got that. I was asking what you thought was a better name for safeDeref.On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 22 2014
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: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"?On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 24 2014
On 2014-06-24 18:52, Yota wrote:On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator. -- /Jacob CarlborgOn 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:absolutely no idea why, but that does give precedence for calling it "coalesce".On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 24 2014
On Tue, Jun 24, 2014 at 09:43:21PM +0200, Jacob Carlborg via Digitalmars-d wrote:On 2014-06-24 18:52, Yota wrote:[...] 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?"On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator.On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:absolutely no idea why, but that does give precedence for calling it "coalesce".On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 24 2014
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
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
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
On 6/24/14, 4:43 PM, Jacob Carlborg wrote:On 2014-06-24 18:52, Yota 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 || 1On Sunday, 22 June 2014 at 08:15:45 UTC, Andrei Alexandrescu wrote:In CoffeeScript it's called The Existential Operator. It's also known as The Elvis Operator.On 6/21/14, 3:38 PM, H. S. Teoh via Digitalmars-d wrote:absolutely no idea why, but that does give precedence for calling it "coalesce".On Sat, Jun 21, 2014 at 03:26:45PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:That we add "or" to std and find a name for safeDeref that doesn't use the word "safe" in a confusing manner. -- AndreiOn 6/19/14, 1:29 PM, Etienne wrote:[...] What's your suggestion?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.
Jun 24 2014
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 || 1Yeah, but that behaves a bit different in D. -- /Jacob Carlborg
Jun 25 2014
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