www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What is the rationale behind enabling Nullable!T to be used as a

reply Adnan <relay.public.adnan outlook.com> writes:
Nullable!T, as I understand is somewhat similar to Rust's 
Option<T>. It's meant to be an alternative to sentinel value 
based error checking.

In regular Rust code if you do something like

let x: Option<i32> = None;
println!("{}", x + 4);

It should throw compile error, since the `x` in `x + 4` is not 
checked for null. OpAdd<rhs = i32> is not implemented for 
Option<i32> for that reason.

Of course, you can get the value using the unwrap() method, which 
would create runtime crash for null option. However when 
typing-out "unwrap()" you understand what you are signing up for.


Back to D.

const Nullable!int a;
assert(a.isNull);
writeln(a + 4); // compiles with 0 warnings

Why is opBinary implemented for Nullable!T? Doesn't it defeat its 
purpose?
Feb 14
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 What is the rationale behind enabling Nullable!T to be used as 
 a regular T?
The rationale is we have a few more months to wait before the deprecation period for that functionality runs out and we can remove it. :)
 Back to D.

 const Nullable!int a;
 assert(a.isNull);
 writeln(a + 4); // compiles with 0 warnings

 Why is opBinary implemented for Nullable!T? Doesn't it defeat 
 its purpose?
If you switch to 2.090.1, you will get warnings there. If you run with -de, the warnings will be errors. --- The original reason was I believe that Nullable was never really intended to be Optional, it was intended to give types a "null" state like objects and pointers. Well, objects and pointers crash when you access them if they're null, so... (No, it's not a very good reason.)
Feb 14
prev sibling next sibling parent reply DanielG <simpletangent gmail.com> writes:
There's also the 'optional' package if you're looking for 
something more functional-program-ey:

https://code.dlang.org/packages/optional
Feb 14
parent reply Adnan <relay.public.adnan outlook.com> writes:
On Friday, 14 February 2020 at 10:35:52 UTC, DanielG wrote:
 There's also the 'optional' package if you're looking for 
 something more functional-program-ey:

 https://code.dlang.org/packages/optional
Not sure if this is any better /+dub.sdl: dependency "optional" version="~>1.0.0" +/ import optional; void main(const string[] args) { static import std; const auto n = no!int(); std.writeln(n + 4); // [] }
Feb 14
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Friday, 14 February 2020 at 12:40:54 UTC, Adnan wrote:
 On Friday, 14 February 2020 at 10:35:52 UTC, DanielG wrote:
 There's also the 'optional' package if you're looking for 
 something more functional-program-ey:

 https://code.dlang.org/packages/optional
Not sure if this is any better /+dub.sdl: dependency "optional" version="~>1.0.0" +/ import optional; void main(const string[] args) { static import std; const auto n = no!int(); std.writeln(n + 4); // [] }
It's by design. optional presents a range interface of length 0 or 1. See the docs for more info: https://github.com/aliak00/optional/#summary
Feb 14
prev sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 14 February 2020 at 12:40:54 UTC, Adnan wrote:
 On Friday, 14 February 2020 at 10:35:52 UTC, DanielG wrote:
 There's also the 'optional' package if you're looking for 
 something more functional-program-ey:

 https://code.dlang.org/packages/optional
Not sure if this is any better /+dub.sdl: dependency "optional" version="~>1.0.0" +/ import optional; void main(const string[] args) { static import std; const auto n = no!int(); std.writeln(n + 4); // [] }
With std.typecons post deprecation: import std.typecons; void main() { static import std; const auto n = Nullable!int(); std.writeln(n.apply!(i => i + 4)); // Nullable!int() }
Feb 14
prev sibling next sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 Nullable!T, as I understand is somewhat similar to Rust's 
 Option<T>. It's meant to be an alternative to sentinel value 
 based error checking.

 In regular Rust code if you do something like

 let x: Option<i32> = None;
 println!("{}", x + 4);

 It should throw compile error, since the `x` in `x + 4` is not 
 checked for null. OpAdd<rhs = i32> is not implemented for 
 Option<i32> for that reason.
The two ends of the design spectrum (as I see it) are: 1. `Optional(T) x` as a range of [0,1] elements of type `T`. For all possible operations `op` on type `T` there exists an operation `Option(T).op` and the effect of executing is equivalent to `x.map!fun`. This is what aliak's optional package provides. 2. `Optional(T) x` behaves like a null-able pointer. The compiler statically prevents dereferencing if it can't prove that x is non-null. Such as scheme (although limited to just pointers for now) is work in progress: https://github.com/dlang/dmd/blob/master/changelog/ob.md
Feb 14
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Friday, 14 February 2020 at 14:43:22 UTC, Petar Kirov 
[ZombineDev] wrote:
 The two ends of the design spectrum (as I see it) are:
 1. `Optional(T) x` as a range of [0,1] elements of type `T`. 
 For all possible operations `op` on type `T` there exists an 
 operation `Option(T).op` and the effect of executing is 
 equivalent to `x.map!fun`.
Edit: `x.map!op`.
Feb 14
prev sibling parent reply Adnan <relay.public.adnan outlook.com> writes:
On Friday, 14 February 2020 at 14:43:22 UTC, Petar Kirov 
[ZombineDev] wrote:
 On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 The two ends of the design spectrum (as I see it) are:
 1. `Optional(T) x` as a range of [0,1] elements of type `T`. 
 For all possible operations `op` on type `T` there exists an 
 operation `Option(T).op` and the effect of executing is 
 equivalent to `x.map!fun`. This is what aliak's optional 
 package provides.
Very interesting. I always thought that Option<T> is a type-guard for enforced null checks (I could be wrong here). But seems to me that this design circles back to square 1: having the callee remember to check if the length of the range is 0 or 1. Which is essentially similar to check sentinel values (i.e. check if the binarysearch returns -1 as index). What languages do this? What does Aliak's package provide that's fundamentally different to just returning a T[]? Empty T[] would mean `None` and a T[] with 1 item means `Some(T)`?
 2. `Optional(T) x` behaves like a null-able pointer. The 
 compiler statically prevents dereferencing if it can't prove 
 that x is non-null. Such as scheme
Nim's optional works this way too: import options let v = none(int) Scala: val a: Option[Int] = None println(a + 4) // compile error Even C++: #include <iostream> #include <optional> auto main() -> int { std::optional<int> a; std::cout << a + 4 << std::endl; // compile error }
(although limited to just
 pointers for now) is work in progress:
 https://github.com/dlang/dmd/blob/master/changelog/ob.md
Nice, any chance it's going to work with non-ptrs too?
Feb 14
next sibling parent Eugene Wissner <belka caraus.de> writes:
On Friday, 14 February 2020 at 17:04:28 UTC, Adnan wrote:
 On Friday, 14 February 2020 at 14:43:22 UTC, Petar Kirov 
 [ZombineDev] wrote:
 On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 The two ends of the design spectrum (as I see it) are:
 1. `Optional(T) x` as a range of [0,1] elements of type `T`. 
 For all possible operations `op` on type `T` there exists an 
 operation `Option(T).op` and the effect of executing is 
 equivalent to `x.map!fun`. This is what aliak's optional 
 package provides.
Very interesting. I always thought that Option<T> is a type-guard for enforced null checks (I could be wrong here). But seems to me that this design circles back to square 1: having the callee remember to check if the length of the range is 0 or 1. Which is essentially similar to check sentinel values (i.e. check if the binarysearch returns -1 as index). What languages do this?
It seems to be an attempt to implement a functor in idiomatic D. In Haskell you have lists and you can map over lists. For example: fmap (+1) [1, 2, 3] will add 1 to each element of the list [1, 2, 3]. But you can map over many other different types, such as Maybe fmap (+1) Nothing will return Nothing. fmap (+1) (Just 1) will return (Just 2). You can map over Eithers, e.g. for either string or integer: fmap (+1) (Left "some type") will return 'Left "some type"'. fmap (+1) (Right 5) will return 'Right 6'. Haskell has even an "empty" function which returns "Nothing" for Maybe, or an empty list for lists. But maybes have nothing to do with lists in Haskell. Functors allow to write concise, type safe code without all that "if-noise" if (a.isNothing) { do some stuff } else { I don't care } Functors is a very generic concept, appliable to many data types and there is a bunch of functions that work on functors. Since in D all kinds of algorithms are implemented for ranges, Optional seems to try to use it and say: Well let us imagine we have a range which can be empty or contain at most one element. It is pretty the same as Maybe/Option/Optional, but many algorithms that work on ranges (such as map) can be applied to this type. Not sure if it is an answer, but I think it is the actual background.
Feb 14
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Friday, 14 February 2020 at 17:04:28 UTC, Adnan wrote:
 On Friday, 14 February 2020 at 14:43:22 UTC, Petar Kirov 
 [ZombineDev] wrote:
 On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 The two ends of the design spectrum (as I see it) are:
 1. `Optional(T) x` as a range of [0,1] elements of type `T`. 
 For all possible operations `op` on type `T` there exists an 
 operation `Option(T).op` and the effect of executing is 
 equivalent to `x.map!fun`. This is what aliak's optional 
 package provides.
Very interesting. I always thought that Option<T> is a type-guard for enforced null checks (I could be wrong here). But seems to me that this design circles back to square 1: having the callee remember to check if the length of the range is 0 or 1. Which is essentially similar to check sentinel values (i.e. check if the binarysearch returns -1 as index). What languages do this? What does Aliak's package provide that's fundamentally different to just returning a T[]? Empty T[] would mean `None` and a T[] with 1 item means `Some(T)`?
Aliak's package offers several features in addition to the range interface that make it more convenient to work with than a simple array. You can see a list in the README: https://github.com/aliak00/optional/blob/v1.0.0/README.md#features
Feb 14
prev sibling parent aliak <something something.com> writes:
On Friday, 14 February 2020 at 17:04:28 UTC, Adnan wrote:
 On Friday, 14 February 2020 at 14:43:22 UTC, Petar Kirov 
 [ZombineDev] wrote:
 On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 The two ends of the design spectrum (as I see it) are:
 1. `Optional(T) x` as a range of [0,1] elements of type `T`. 
 For all possible operations `op` on type `T` there exists an 
 operation `Option(T).op` and the effect of executing is 
 equivalent to `x.map!fun`. This is what aliak's optional 
 package provides.
Very interesting. I always thought that Option<T> is a type-guard for enforced null checks (I could be wrong here). But seems to me that this design circles back to square 1: having the callee remember to check if the length of the range is 0 or 1. Which is essentially similar to check sentinel values (i.e. check if the binarysearch returns -1 as index). What languages do this?
Ya! If you take scala for e.g. Optional!T is an attempt to model a lot of what scala does. I.e. it's a range. If you want to get to the element you have to check if it exists first, else you manipulate it via functional primitives like map. You need to do the same in scala, kotlin, swift for e.g. I used to have an unwrap function in there as well, and you could do: if (auto v = optional.unwrap) { // you get a pointer to T here and in D you can access members of a pointer // just like it wasn't a pointer. } But that proved to be hard to keep safe. I've never been a 100% on the whole operator overloading though. The difference between how it's implemented here and other places is that it's a noop if the optional is empty. In swift you can call functions on optionals even if they're empty, so that's what the oc utility in optional is for, and the opBinary was a way to add that same functionality for D operators and array access. Basically, operating on an optional is ok and always results in an optional. And it must always maintain it's emptiness if any of the operands are empty.
 What does Aliak's package provide that's fundamentally 
 different to just returning a T[]? Empty T[] would mean `None` 
 and a T[] with 1 item means `Some(T)`?
I've tried to outline as much as possible in the readme. In a nutshell though: intent, nogc, safe access, no need to worry about having more than one element - I may be forgetting some things. Cheers, - ali
Feb 14
prev sibling parent Elronnd <elronnd elronnd.net> writes:
On Friday, 14 February 2020 at 08:54:41 UTC, Adnan wrote:
 Nullable!T, as I understand is somewhat similar to Rust's 
 Option<T>. It's meant to be an alternative to sentinel value 
 based error checking.
Not exactly, as others have mentioned. But there is https://github.com/moon-chilled/tenbots-opensource/blob/master/maybe.d (which originally comes from https://github.com/dkhasel/maybe-d, but I cleaned it up a bit). At one point, I wrote a wrapper which would let you write a function with a return type of Maybe!T, but which actually returned objects of type T. But that was a giant hack and not at all worth it.
Feb 14