digitalmars.D.learn - Open question: what code pattern you use usually for null safety
- ddcovery (38/38) Jan 14 2021 I know there is other threads about null safety and the
- Adam D. Ruppe (14/16) Jan 14 2021 I'm almost never in this situation except for reading things like
- ddcovery (21/37) Jan 14 2021 Yes, this is the usual situation (Personally, I use "DTO"
- mw (4/21) Jan 14 2021 Reason: easy to read and reason about, esp for non-authors of
- Steven Schveighoffer (19/73) Jan 14 2021 You could kinda automate it like:
- ddcovery (18/39) Jan 14 2021 I'm seeing "opDispatch" everywhere last days :-). It's really
- Steven Schveighoffer (17/54) Jan 15 2021 This doesn't work, if person, person.father, or person.father.father is
- Steven Schveighoffer (6/9) Jan 15 2021 And now reading the other thread about this above, it looks like this
- Imperatorn (3/12) Jan 15 2021 That could be useful actually
- ddcovery (18/27) Jan 15 2021 Yes, the Optional/Some/None pattern is the "functional"
- ddcovery (82/142) Jan 15 2021 I don't know if I can add this to Dlang IDE and then share a
- Dennis (22/24) Jan 14 2021 Usually I don't deal with null because my functions get primitive
- ddcovery (8/33) Jan 14 2021 I agree: using null safety is a sign of something wrong in the
- ddcovery (8/12) Jan 15 2021 I'm writing a "personal" article/study about "null safety"
- Dukc (6/11) Jan 15 2021 Probably the incremental check solution. A helper function if I
- Basile B. (14/21) Jan 15 2021 I have a opDispatch solution here [1], probably very similar to
I know there is other threads about null safety and the "possible" ways to support this in D and so on. This is only an open question to know what code patterns you usually use to solve this situation in D: if(person.father.father.name == "Peter") doSomething(); if(person.father.age > 80 ) doSomething(); knowing that *person*, or its *father* property can be null i.e.: the incremental null check solution if( person !is null && person.father !is null && person.father.father !is null && person.father.father.name == "Peter" ) { doSomething(); } or the "monad" way [person]. filter!"a !is null".map!"a.father". filter!"a !is null".map!"a.father". filter!"a !is null".map!"a.name". each!( (name) { if(name == "Peter") doSomething(); }); or, may be, you have some helper function/structs/templates if( person.d!"father".d!"father".d!"name".get == "Peter" ){ doSomething() } if( person.d!"father".d!"age".get(0) > 80 ){ doSomething() } or an "xml path" like template if( person.get!"father.father.name" == "Peter" ) if( person.get!"father.father.name.length"(0) == 5 ) if( person.get!"father.father.age"(0) > 80 ) If it's not a bother, I'd like to know how you usually approach it Thanks!!!
Jan 14 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:This is only an open question to know what code patterns you usually use to solve this situation in D:I'm almost never in this situation except for reading things like xml or json data that may be missing. So I just special cased those. My json lib doesn't return null per se, it returns var(null) which is allowed to just return more harmless nulls. Thus you write `person.father.father.name.get!string` and it will be empty if anything was null in the chain. With dom, you can optionSelector("person > father > father > name").innerText and again if the selector returned null, all its methods also just return empty strings or whatever. So the library handles these special cases and then I don't worry about nested nulls anywhere else since I consider it bad style to even be in that situation in the first place.
Jan 14 2021
On Thursday, 14 January 2021 at 19:24:54 UTC, Adam D. Ruppe wrote:On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:Yes, this is the usual situation (Personally, I use "DTO" structured objects... that are serialized/unserialized to JSON)This is only an open question to know what code patterns you usually use to solve this situation in D:I'm almost never in this situation except for reading things like xml or json data that may be missing.So I just special cased those. My json lib doesn't return null per se, it returns var(null) which is allowed to just return more harmless nulls. Thus you write `person.father.father.name.get!string` and it will be empty if anything was null in the chain. With dom, you can optionSelector("person > father > father > name").innerText and again if the selector returned null, all its methods also just return empty strings or whatever.Selectors are a good option to navigate on dom/json, but on structured objects too. The good think with "templates" in D is that this "path/selector" can be compiled internally to a map/filter combination completly "null" free... I was experimenting last days with this and I think that a functional orientation (using a MayBe monad implemented as Range ) is the best way to begin.So the library handles these special cases and then I don't worry about nested nulls anywhere else since I consider it bad style to even be in that situation in the first place.I agree: it is a bad style. Personally I allways use MayBe monads in my "DTO"s (that is the effect of having worked with scala :-). The only "cons" with Nullable!T (the "standard" D MayBe equivalent) is that it is not "compatible" with Range libraries (it is not a Range: you use "apply" instead "map", you have not "filter", you can't "chain" a range and Nullable, you can't "join" a range of Nullables like a Range of ranges). This is the reason I'm "experimenting" with my own "MayBe" InputRange that can be created in the form of "Some" or "None" (it's inmutable contrary to what happens with Nullable) and is compatible with all std.algorithm (and array) library.
Jan 14 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:I know there is other threads about null safety and the "possible" ways to support this in D and so on. This is only an open question to know what code patterns you usually use to solve this situation in D: if(person.father.father.name == "Peter") doSomething(); if(person.father.age > 80 ) doSomething(); knowing that *person*, or its *father* property can be null i.e.: the incremental null check solutionI just use this most simple one:if( person !is null && person.father !is null && person.father.father !is null && person.father.father.name == "Peter" ) { doSomething(); }Reason: easy to read and reason about, esp for non-authors of this piece of the code.
Jan 14 2021
On 1/14/21 1:24 PM, ddcovery wrote:I know there is other threads about null safety and the "possible" ways to support this in D and so on. This is only an open question to know what code patterns you usually use to solve this situation in D: if(person.father.father.name == "Peter") doSomething(); if(person.father.age > 80 ) doSomething(); knowing that *person*, or its *father* property can be null i.e.: the incremental null check solution if( person !is null && person.father !is null && person.father.father !is null && person.father.father.name == "Peter" ) { doSomething(); } or the "monad" way [person]. filter!"a !is null".map!"a.father". filter!"a !is null".map!"a.father". filter!"a !is null".map!"a.name". each!( (name) { if(name == "Peter") doSomething(); }); or, may be, you have some helper function/structs/templates if( person.d!"father".d!"father".d!"name".get == "Peter" ){ doSomething() } if( person.d!"father".d!"age".get(0) > 80 ){ doSomething() } or an "xml path" like template if( person.get!"father.father.name" == "Peter" ) if( person.get!"father.father.name.length"(0) == 5 ) if( person.get!"father.father.age"(0) > 80 ) If it's not a bother, I'd like to know how you usually approach it Thanks!!!You could kinda automate it like: struct NullCheck(T) { private T* _val; auto opDispatch(string mem)() if (__traits(hasMember, T, mem)) { alias Ret = typeof(() { return __traits(getMember, *_val, mem); }()); if(_val is null) return NullCheck!(Ret)(null); else return NullCheck!(Ret)(__trats(getMember, *_val, mem)); } bool opCast(V: bool)() { return _val !is null; } } auto nullCheck(T)(T *val) { return AutoNullCheck!T(val);} // usage if(nullCheck(person).father.father && person.father.father.name == "Peter") Probably doesn't work for many circumstances, and I'm sure I messed something up. -Steve
Jan 14 2021
On Thursday, 14 January 2021 at 20:23:08 UTC, Steven Schveighoffer wrote:You could kinda automate it like: struct NullCheck(T) { private T* _val; auto opDispatch(string mem)() if (__traits(hasMember, T, mem)) { alias Ret = typeof(() { return __traits(getMember, *_val, mem); }()); if(_val is null) return NullCheck!(Ret)(null); else return NullCheck!(Ret)(__trats(getMember, *_val, mem)); } bool opCast(V: bool)() { return _val !is null; } } auto nullCheck(T)(T *val) { return AutoNullCheck!T(val);} // usage if(nullCheck(person).father.father && person.father.father.name == "Peter") Probably doesn't work for many circumstances, and I'm sure I messed something up. -SteveI'm seeing "opDispatch" everywhere last days :-). It's really powerful!!! If we define an special T _(){ return _val; } method, then you can write if( nullCheck(person).father.father.name._ == "Peter") And renaming if( ns(person).father.father.name._ == "Peter" ) And adding some extra check like ** isAssignable!(Ret, typeof(null) )** we can add special treatment for not nullable types if( ns(person).father.father.age._(0) == 92 ){ ... } assert( ns(person).father.father.father.age._ == int.init ); If for some strange reason I ever need null safety, I think this is the most beautiful solution or at least a great lesson on templates. Thanks a lot for the lesson, Steve
Jan 14 2021
On 1/14/21 7:27 PM, ddcovery wrote:On Thursday, 14 January 2021 at 20:23:08 UTC, Steven Schveighoffer wrote:This doesn't work, if person, person.father, or person.father.father is null, because now you are dereferencing null again. But something like this might work: NullCheck(T) { ... // opdispatch and stuff bool opEquals(auto ref T other) { return _val is null ? false : *_val == other; } } Something similar to BlackHole or WhiteHole. Essentially there's a default action for null for all types/fields/methods, and everything else is passed through. Swift has stuff like this built-in. But D might look better because you wouldn't need a chain of question marks. -SteveYou could kinda automate it like: struct NullCheck(T) { private T* _val; auto opDispatch(string mem)() if (__traits(hasMember, T, mem)) { alias Ret = typeof(() { return __traits(getMember, *_val, mem); }()); if(_val is null) return NullCheck!(Ret)(null); else return NullCheck!(Ret)(__trats(getMember, *_val, mem)); } bool opCast(V: bool)() { return _val !is null; } } auto nullCheck(T)(T *val) { return AutoNullCheck!T(val);} // usage if(nullCheck(person).father.father && person.father.father.name == "Peter") Probably doesn't work for many circumstances, and I'm sure I messed something up. -SteveI'm seeing "opDispatch" everywhere last days :-). It's really powerful!!! If we define an special T _(){ return _val; } method, then you can write if( nullCheck(person).father.father.name._ == "Peter") And renaming if( ns(person).father.father.name._ == "Peter" )
Jan 15 2021
On 1/15/21 9:19 AM, Steven Schveighoffer wrote:Something similar to BlackHole or WhiteHole. Essentially there's a default action for null for all types/fields/methods, and everything else is passed through.And now reading the other thread about this above, it looks like this type is already written: https://code.dlang.org/packages/optional I'd say use that. -Steve
Jan 15 2021
On Friday, 15 January 2021 at 14:25:09 UTC, Steven Schveighoffer wrote:On 1/15/21 9:19 AM, Steven Schveighoffer wrote:That could be useful actuallySomething similar to BlackHole or WhiteHole. Essentially there's a default action for null for all types/fields/methods, and everything else is passed through.And now reading the other thread about this above, it looks like this type is already written: https://code.dlang.org/packages/optional I'd say use that. -Steve
Jan 15 2021
On Friday, 15 January 2021 at 14:25:09 UTC, Steven Schveighoffer wrote:On 1/15/21 9:19 AM, Steven Schveighoffer wrote:Yes, the Optional/Some/None pattern is the "functional" orientation for avoiding the use of "null". Swift uses a similar pattern (and scala too) and supports the "null safety operators" ?. and ?? (it doesn't work on "null" but on optional/nil). The more I think about it, the more fervent defender of the use of ?. and ?? I am. The misinterpretation about "null safety" is we talk about "null" reference safety, but this pattern can be used with "optional" to... D has not optional/none/some native implementation and this is the reason we think about "?." as a "bad pattern" because we imagine it is for "null" values exclusively. But like other operators, they could be overloaded and adapted to each library. Well, I'm digressing: good night!!!Something similar to BlackHole or WhiteHole. Essentially there's a default action for null for all types/fields/methods, and everything else is passed through.And now reading the other thread about this above, it looks like this type is already written: https://code.dlang.org/packages/optional I'd say use that. -Steve
Jan 15 2021
On Friday, 15 January 2021 at 14:19:35 UTC, Steven Schveighoffer wrote:On 1/14/21 7:27 PM, ddcovery wrote:I don't know if I can add this to Dlang IDE and then share a link... links that I generate doesn't work... * I have adapted the "onDispatch" and the factory method to manage nullable and not nullable values * The unwrapper "T _()" method returns Nullable!T for nullable * I removed the T* when testing changes (I discovered after 1000 changes that template errors are not well informed by the compiler... I losted a lot to discover a missing import)... I will try to restore. import std.typecons; import std.traits; void main() { Person person = new Person("Andres", 10, new Person("Peter", 40, null)); // null reference assert(ns(person).father.father._ is null); // null reference assert(ns(person).father.father.name._ is null); // reference value assert(ns(person).father.name._ == "Peter"); // Nullable!int assert(ns(person).father.father.age._.isNull); assert(ns(person).father.father.age._.get(0) == 0); assert(ns(11)._.get == 11); } struct NullSafety(T) { private T _val; private bool _isEmpty; auto opDispatch(string name)() if (__traits(hasMember, T, name)) { alias Ret = typeof((() => __traits(getMember, _val, name))()); if (_val is null) { static if (isAssignable!(Ret, typeof(null))) return NullSafety!(Ret)(null, true); else return NullSafety!(Ret)(Ret.init, true); } else { return NullSafety!(Ret)(__traits(getMember, _val, name), false); } } static if (isAssignable!(T, typeof(null))) // Reference types unwrapper T _() { return _val; } else // value types unwrapper Nullable!T _() { return _isEmpty ? Nullable!T() : Nullable!T(_val); } } auto ns(T)(T val) { static if (isAssignable!(T, typeof(null))) return NullSafety!T(val, val is null); else return NullSafety!T(val, false); } class Person { public string name; public Person father; public int age; this(string name, int age, Person father) { this.name = name; this.father = father; this.age = age; } }On Thursday, 14 January 2021 at 20:23:08 UTC, Steven Schveighoffer wrote:This doesn't work, if person, person.father, or person.father.father is null, because now you are dereferencing null again. But something like this might work: NullCheck(T) { ... // opdispatch and stuff bool opEquals(auto ref T other) { return _val is null ? false : *_val == other; } } Something similar to BlackHole or WhiteHole. Essentially there's a default action for null for all types/fields/methods, and everything else is passed through. Swift has stuff like this built-in. But D might look better because you wouldn't need a chain of question marks. -SteveYou could kinda automate it like: struct NullCheck(T) { private T* _val; auto opDispatch(string mem)() if (__traits(hasMember, T, mem)) { alias Ret = typeof(() { return __traits(getMember, *_val, mem); }()); if(_val is null) return NullCheck!(Ret)(null); else return NullCheck!(Ret)(__trats(getMember, *_val, mem)); } bool opCast(V: bool)() { return _val !is null; } } auto nullCheck(T)(T *val) { return AutoNullCheck!T(val);} // usage if(nullCheck(person).father.father && person.father.father.name == "Peter") Probably doesn't work for many circumstances, and I'm sure I messed something up. -SteveI'm seeing "opDispatch" everywhere last days :-). It's really powerful!!! If we define an special T _(){ return _val; } method, then you can write if( nullCheck(person).father.father.name._ == "Peter") And renaming if( ns(person).father.father.name._ == "Peter" )
Jan 15 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:If it's not a bother, I'd like to know how you usually approach itUsually I don't deal with null because my functions get primitive types, slices, or structs. `ref` parameters can be used to replace pointers that may not be null. When something is nullable by design, I usually do this: ``` if (!person) { return; // early return if possible } if (auto f0 = person.father) { if (auto f1 = f0.father) { if (f1.name == "Peter") { doSomething(); } } } ``` It doesn't matter whether you're working with a class, pointer, or struct with opCast, this works. When access patterns get complex the nesting may get very deep. Only if you can't avoid this I would consider using fancy helper functions, otherwise just use an if-statement or the && operator.
Jan 14 2021
On Thursday, 14 January 2021 at 20:35:49 UTC, Dennis wrote:On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:I agree: using null safety is a sign of something wrong in the design (the need of dealing with nulls)... but if eventually you need it, simple **if** or **&&** should be enough. Curiously, languages like Dart (and its flutter framework) performs extensive use of null safety (null is everywhere!!!) and it seems that every "modern" language must deal with it. Any case, I'm learning a lot: thank you Dennis for sharing!!!If it's not a bother, I'd like to know how you usually approach itUsually I don't deal with null because my functions get primitive types, slices, or structs. `ref` parameters can be used to replace pointers that may not be null. When something is nullable by design, I usually do this: ``` if (!person) { return; // early return if possible } if (auto f0 = person.father) { if (auto f1 = f0.father) { if (f1.name == "Peter") { doSomething(); } } } ``` It doesn't matter whether you're working with a class, pointer, or struct with opCast, this works. When access patterns get complex the nesting may get very deep. Only if you can't avoid this I would consider using fancy helper functions, otherwise just use an if-statement or the && operator.
Jan 14 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:I know there is other threads about null safety and the "possible" ways to support this in D and so on. This is only an open question to know what code patterns you usually use to solve this situation in DI'm writing a "personal" article/study about "null safety" anti-pattern in form of github project (to include some examples) I really thank you for your answers here that I will use (and mention with your permission) in this small article. The actual version can be found here https://github.com/ddcovery/d_null_safety/blob/main/README.md It is under construction :-).
Jan 15 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:This is only an open question to know what code patterns you usually use to solve this situation in D: if(person.father.father.name == "Peter") doSomething(); if(person.father.age > 80 ) doSomething(); knowing that *person*, or its *father* property can be nullProbably the incremental check solution. A helper function if I find myself doing that more than two or three times. On the other hand, I don't have to do this that often. I usually design the functions to either except non-null values, or to return early in case of null.
Jan 15 2021
On Thursday, 14 January 2021 at 18:24:44 UTC, ddcovery wrote:I know there is other threads about null safety and the "possible" ways to support this in D and so on. [...] If it's not a bother, I'd like to know how you usually approach it [...] Thanks!!!I have a opDispatch solution here [1], probably very similar to the other opDispatch solution mentioned. It is used in d-scanner since several years, e.g here [2]. I'd like to have this as a first class operator because as usual in D, you can do great things with templates but then completion is totally unable to deal with them. Also There's a great difference between using the template to do refacts and using it to write new code. Very frustrating to write `safeAcess(stuff). ` and no completion popup appears. [1]: https://gitlab.com/basile.b/iz/-/blob/master/import/iz/sugar.d#L1655 [2]: https://github.com/dlang-community/D-Scanner/blob/2963358eb4a24064b0893493684d4075361297eb/src/dscanner/analysis/assert_without_msg.d#L42
Jan 15 2021