www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Discussion Thread: DIP 1034--Add a Bottom Type (reboot)--Community

reply Mike Parker <aldacron gmail.com> writes:
This is the discussion thread for the first round of Community 
Review of DIP 1034, "Add a Bottom Type (reboot)":

https://github.com/dlang/DIPs/blob/15081980cd393e21218da6836321ed37ebc48dd3/DIPs/DIP1034.md

The review period will end at 11:59 PM ET on May 20, or when I 
make a post declaring it complete. Discussion in this thread may 
continue beyond that point.

Here in the discussion thread, you are free to discuss anything 
and everything related to the DIP. Express your support or 
opposition, debate alternatives, argue the merits, etc.

However, if you have any specific feedback on how to improve the 
proposal itself, then please post it in the feedback thread. The 
feedback thread will be the source for the review summary I write 
at the end of this review round. I will post a link to that 
thread immediately following this post. Just be sure to read and 
understand the Reviewer Guidelines before posting there:

https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

And my blog post on the difference between the Discussion and 
Feedback threads:

https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

Please stay on topic here. I will delete posts that are 
completely off-topic.
May 06
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the feedback 
 thread. The feedback thread will be the source for the review 
 summary I write at the end of this review round. I will post a 
 link to that thread immediately following this post.
The feedback thread is located here: https://forum.dlang.org/post/arcpszmdarekxtnsnwfl forum.dlang.org
May 06
prev sibling next sibling parent reply Panke <tobias pankrath.net> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1034, "Add a Bottom Type (reboot)":

 [...]
I think this is one the best written DIPs I've seen in while independent from the changes it discusses.
May 06
parent jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 6 May 2020 at 14:21:43 UTC, Panke wrote:
 [snip]

 I think this is one the best written DIPs I've seen in while 
 independent from the changes it discusses.
I had the same thought as well.
May 06
prev sibling next sibling parent Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1034, "Add a Bottom Type (reboot)":

 [...]
Love it! There was no point in DIP-1017 but this one makes an excellent case for the bottom type and explains it well enough that even dummies like me understand it.
May 06
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1034, "Add a Bottom Type (reboot)":

 https://github.com/dlang/DIPs/blob/15081980cd393e21218da6836321ed37ebc48dd3/DIPs/DIP1034.md
In one of the examples, it is asserted that there is no way to use `assert(0)` in a switch-like higher-order function that uses lambdas for its cases (such as sumtype's `match`).
 In a switch statement, it is possible to add a case default: 
 assert(0);. This is not possible when using lambda-handlers to 
 simulate a switch, like the sumtype package does:
This is not strictly true. It can be done by specifying the return type of the `assert(0)` lambda explicitly, rather than relying on type inference: int round(Var v) { return v.match!( (int x) => return x, (double x) => return cast(int) x, function int (other) { assert(0); } // compiles ); }
May 06
prev sibling next sibling parent reply Meta <jared771 gmail.com> writes:
noreturn x0; // compile error, must have bottom value

noreturn[1] x4; // compile error, init value is [assert(0)]

struct S {int x; noreturn y;} // definition is fine
S x5; // compile error, must have bottom value

enum E : noreturn {x = assert(0), y = assert(0)}
E e; // compile error, must have bottom value

Why are these defined to cause a compile error?
May 06
parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 6 May 2020 at 16:22:48 UTC, Meta wrote:
 Why are these defined to cause a compile error?
My reasoning is that the following are all equivalent:
 noreturn x;
 noreturn x = noreturn.init;
 noreturn x = assert(0);
 auto x = assert(0);
 auto x = () {assert(0);} ();
The bottom one gives a compile error today.
 Error: `assert(0)` failed
I don't intend to change that. (This comparison with errors during CTFE is also mentioned in "Interaction with other language features" by the way).
May 06
parent reply Meta <jared771 gmail.com> writes:
On Wednesday, 6 May 2020 at 16:39:30 UTC, Dennis wrote:
 On Wednesday, 6 May 2020 at 16:22:48 UTC, Meta wrote:
 Why are these defined to cause a compile error?
My reasoning is that the following are all equivalent:
 noreturn x;
 noreturn x = noreturn.init;
 noreturn x = assert(0);
 auto x = assert(0);
 auto x = () {assert(0);} ();
The bottom one gives a compile error today.
 Error: `assert(0)` failed
I don't intend to change that. (This comparison with errors during CTFE is also mentioned in "Interaction with other language features" by the way).
The problem is that these require special cases in generic code. If these common cases cause compile errors, then every template will either have to have a `if (!is(T == noreturn))`, or allow itself to fail (possibly deep inside a stack of instantiated templates). std.algorithm.group, for example, returns a Group struct, defined as follows: struct Group(alias pred, R) if (isInputRange!R) { import std.typecons : Rebindable, tuple, Tuple; private alias E = ElementType!R; static if ((is(E == class) || is(E == interface)) && (is(E == const) || is(E == immutable))) { private alias MutableE = Rebindable!E; } else static if (is(E : Unqual!E)) { private alias MutableE = Unqual!E; } else { private alias MutableE = E; } private R _input; private Tuple!(MutableE, uint) _current; ... } But if R is noreturn[], then this becomes: struct Group(alias pred, R) if (isInputRange!R) { private noreturn[] _input; private Tuple!(noreturn, uint) _current; } And because Tuple is itself a struct, and Tuple!(noreturn, uint) has a field of type noreturn, the following code will fail to compile: [].group() With a confusing error message inside Tuple. Maybe this is not a realistic scenario, but I think it should at least be considered that this will cause code that previously seemed fairly innocuous to now fail to compile, for a reason that may not be obvious.
May 06
parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 6 May 2020 at 17:03:56 UTC, Meta wrote:
 Maybe this is not a realistic scenario, but I think it should 
 at least be considered that this will cause code that 
 previously seemed fairly innocuous to now fail to compile, for 
 a reason that may not be obvious.
It doesn't compile today, as it would be instantiated with a void[]: ``` import std; void main() {writeln([].group());} ```
 instance std.algorithm.iteration.Group!("a == b", void[]) does 
 not match template declaration ...
You do raise a very good point though, I don't want to introduce a lot of `static if (is(T == noreturn[]))` to make range algorithms work with []. I'm considering changing it so that local noreturn variables give an error on usage instead of initialization. I need think about the implications of that though. I'll try to make a prototype implementation before final review and see if I can make [] work with range functions in Phobos.
May 06
parent reply Johannes Loher <johannes.loher fg4f.de> writes:
Am 06.05.20 um 21:07 schrieb Dennis:
 I'm considering changing it so that local noreturn variables give an
 error on usage instead of initialization.
Thats exactly how it should be done. From my point of view, there is no reason why declaring a variable of type noreturn should assert(0). Only accessing it should do that, at least for local variables. For example consider the following code: auto fun(alias f)() { alias Type = ReturnType!f; Type ret; ret = f(); /* do something with ret */ } noreturn throwingFunction() { throw new Exception("foobar"); } noreturn nonReturningFunction() { for(;;){} } void main() { try { fun!(nonReturningFunction)(); // should throw Exception("foobar") } catch(Exception e) { // silently ignore it } fun!(nonReturningFunction)(); // should loop forever } I am not 100% sure about the implications for global variables, but if global variables of type noreturn are initialized explicitly, it will be a compile error because it will either be an infinite loop at compile time or throw an exception at compile time (I guess there are also some other situations, like failed asserts in betterC, but they also are compile errors). If the variable is not initialized explicitly, I don't see any issue with it compiling. However, as soon as the variable is accessed at runtime, it must assert(0). H.S. Theo and me had a long discussion about this stuff in one of the review threads for DIP1017 and if I remember correctly we basically agreed that this should be the behavior. The main reasoning was that this way we can avoid special cases in generic code and there is no reason not to do it that way, it's just natural. It would be a weird special case to disallow declaring variables of any type (you apply the same reasoning to void in the DIP for making void a proper unit type and I wholeheartedly agree!). However I don't remember if we considered global variables. Aside from that: I really love this DIP, I was looking forward to it since I noticed you were working on it! It (and also the void DIP) would make D's type system feel much more natural. Thanks for your work!
May 06
parent reply Johannes Loher <johannes.loher fg4f.de> writes:
Am 06.05.20 um 23:07 schrieb Johannes Loher:
 ...
After writing this, I just realized another thing: At the moment, it is required that the main function is declared using one of the following forms: void main() { ... } void main(string[] args) { ... } int main() { ... } int main(string[] args) { ... } (https://dlang.org/spec/function.html#main) With this DIP, might it make sense to also allow noreturn as a return type for main, just for consistency?
May 06
parent Dennis <dkorpel gmail.com> writes:
On Wednesday, 6 May 2020 at 21:44:17 UTC, Johannes Loher wrote:
 With this DIP, might it make sense to also allow noreturn as a 
 return type for main, just for consistency?
I think so.
May 07
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/6/2020 4:04 AM, Mike Parker wrote:
 This is the discussion thread for the first round of Community Review of DIP 
 1034, "Add a Bottom Type (reboot)":
 
 https://github.com/dlang/DIPs/blob/15081980cd393e21218da6836321ed37ebc4
dd3/DIPs/DIP1034.md 
Thank you, Dennis, for a well-written DIP, and fixing #DIP1017. Maybe you can take a stab at improving the D spec prose, too? * Mangling: prefer to stick with alpha_numeric characters for mangling. It doesn't need to be short, as I expect it to be rare * Mention the conversion level, should be "convert". * For comparisons, I'd use a more popular language than zig.
May 06
parent reply Dennis <dkorpel gmail.com> writes:
On Wednesday, 6 May 2020 at 22:26:13 UTC, Walter Bright wrote:
 Thank you, Dennis, for a well-written DIP, and fixing #DIP1017. 
 Maybe you can take a stab at improving the D spec prose, too?
Glad to hear that. I have some other things (such as dmd bugs and prototype DIP implementations) I like to work on before looking at the D specification, but maybe I can find some time for it.
 * Mangling: prefer to stick with alpha_numeric characters for 
 mangling. It doesn't need to be short, as I expect it to be rare
Agreed.
 * Mention the conversion level, should be "convert".
Will do.
 * For comparisons, I'd use a more popular language than zig.
While Zig is young, from what I've seen it's gaining traction. More importantly, it has overlapping goals with D (systems programming language, fix C's mistakes). That makes their design decisions more interesting to me than e.g. TypeScript or Python even though those are more popular. The only other systems programming language that I know of that has a bottom type is Rust, which I already included in the DIP. Do you have a particular language you'd like to see compared? For reference: https://en.wikipedia.org/wiki/Bottom_type#In_programming_languages
May 07
next sibling parent wolframw <wolframw protonmail.com> writes:
On Thursday, 7 May 2020 at 13:45:41 UTC, Dennis wrote:
 Do you have a particular language you'd like to see compared?
Even though it's not a systems programming language, Scala could be a good example. Scala has a notoriously strict and sophisticated type system that is very much inspired by type theory: There is a top type called "Any" (which is a supertype of every other type), a bottom type called "Nothing" (which is a subtype of every other type), and void type is referred to as "Unit". (see [1] for a diagram of Scala's type system) In Scala, "Nothing" is a non-instantiable type that is used for anything that doesn't return. Conceptually it appears to be very similar to what is envisioned in the DIP. Here are a few "Nothing" examples: val foo = throw new Exception(); // compiles; `foo` will have type `Nothing` // `foo` will never be assigned to def bar(n: Int): String = { n match { case 1 => "one" case 2 => "two" // note that even though the return type is `String`, the following is // valid because `Nothing` is a subtype of `String` (and every other type) case _ => throw new Exception() // `case _` is equivalent to `default` in D // alternatively, `assert(false)` would give a similar result // `case _ => Nothing` would NOT work because `Nothing` is non-instantiable } } val baz = () => throw new Exception() // type of baz is Function0[Nothing] // meaning: a nullary function that returns `Nothing` (i.e. doesn't return at all) As one would expect, the Scala standard library uses "Nothing" as the return type of its exit and error functions: see [2] (notice the hack in exit: Since Java's System.exit returns void -- or "Unit" in Scala lingo -- the exit function throws, just so its return type can be "Nothing") [1] https://docs.scala-lang.org/resources/images/tour/unified-types-diagram.svg [2] https://github.com/scala/scala/blob/2.13.x/src/library/scala/sys/package.scala#L22
May 07
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/7/2020 6:45 AM, Dennis wrote:
 * For comparisons, I'd use a more popular language than zig.
While Zig is young, from what I've seen it's gaining traction. More importantly, it has overlapping goals with D (systems programming language, fix C's mistakes). That makes their design decisions more interesting to me than e.g. TypeScript or Python even though those are more popular. The only other systems programming language that I know of that has a bottom type is Rust, which I already included in the DIP. Do you have a particular language you'd like to see compared? For reference: https://en.wikipedia.org/wiki/Bottom_type#In_programming_languages
Actually, I think that link would be enough.
May 07
prev sibling next sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1034, "Add a Bottom Type (reboot)":
Thanks for this Dennis! You're doing the Lord's work :)
May 13
prev sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 6 May 2020 at 11:04:06 UTC, Mike Parker wrote:
 [..]
I think it's good to consider how UFCS would interact with the bottom type. I just saw that Dart decided that it should not be possible to have extensions (which are similar to UFCS in D) on Never (more precisely that extensions would not apply to Never): https://github.com/dart-lang/language/pull/1003/files#diff-dcbb29985b096645570ae14645f65d53R713
 For the purposes of extension method resolution, the type 
 `Never` is considered to implement all members, and hence no 
 extension may apply to an expression of type `Never`.
Jun 05