digitalmars.D - DIP 1017--Add Bottom Type--Final Review
- Mike Parker (12/12) Jan 15 2019 DIP 1017, "Add Bottom Type", is now ready for Final Review. This
- ixid (4/16) Jan 15 2019 Sorry to bikeshed but any name other than 'bottom' would be
- Meta (4/7) Jan 15 2019 "never" is used in Rust, i.e., the never type. Scala and a few
- aliak (25/32) Jan 15 2019 +1 for not using "bottom". It's really confusing to anyone who is
- Walter Bright (21/24) Jan 15 2019 Fortunately, "bottom type" is very google-able, and this comes up:
- Neia Neutuladh (9/27) Jan 15 2019 In this case, people will ask: "what does bottom_t mean?"
- aliak (15/43) Jan 15 2019 How is it new jargon when other programming languages that are in
- NaN (3/15) Jan 16 2019 Yeah shame you skipped that discussion with enum and nothrow.
- Dukc (8/18) Jan 16 2019 Since Rust already uses `never`, it won't be inventing new
- Walter Bright (4/8) Jan 16 2019 Exactly why it was done that way. It was a way to avoid needing a keywor...
- Olivier FAURE (13/19) Jan 16 2019 Agreed, the type name is bikeshedding.
- Mike Parker (16/22) Jan 16 2019 I guess I need to edit the procedure document to clarify this.
- Mike Parker (5/6) Jan 16 2019 "as a referendum"
- Walter Bright (43/46) Jan 16 2019 That indeed is the correct question.
- Dukc (4/10) Jan 16 2019 Isn't `void` in fact analogous to the TOP type? With a bit
- Walter Bright (5/7) Jan 16 2019 I think H.S. Teoh's reply helps with that. 'void' is symptomatic of poor...
- Nicholas Wilson (9/19) Jan 16 2019 Its not that we're not interested, its that the DIP needs to show
- Johannes Loher (3/6) Jan 16 2019 I don't think this is true. At least I am very interested in giving D's
- Walter Bright (2/4) Jan 16 2019 Good to hear!
- H. S. Teoh (10/13) Jan 17 2019 Actually, I think most of the negative reactions come not from the
- rikki cattermole (4/15) Jan 17 2019 As someone who will be voting no because I do not feel the feedback has
- luckoverthere (14/35) Jan 16 2019 So you want to add something you don't fully understand and
- Walter Bright (4/14) Jan 16 2019 A fair point, though the rudeness is disappointing. I'd appreciate help ...
- Neia Neutuladh (10/23) Jan 16 2019 D doesn't have a top type. Object is the top of the hierarchy of non-COM...
- H. S. Teoh (71/77) Jan 16 2019 [...]
- Jonathan Marler (8/17) Jan 16 2019 A good summary of the issues with void. Maybe adding a bottom
- Meta (36/55) Jan 16 2019 D has a couple of different unit types which all behave
- Johannes Loher (12/61) Jan 16 2019 I have already resorted to using `struct Unit {}` as a proper unit type
- H. S. Teoh (15/31) Jan 17 2019 Unfortunately, you get a compile error for this declaration.
- H. S. Teoh (26/57) Jan 17 2019 Further thoughts on empty enums in D: according to the spec, an enum
- Jonathan Marler (9/38) Jan 17 2019 The compiler could "decay" these various "unit types" to the same
- Jonathan Marler (9/38) Jan 17 2019 The compiler could "decay" these various "unit types" to the same
- Johannes Loher (7/14) Jan 16 2019 I totally agree with this. I would love to see void becomming a real
- Dukc (22/28) Jan 17 2019 I think `void` can still be saved. I'm not sure about the meaning
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (17/32) Jan 17 2019 Consider:
- H. S. Teoh (37/59) Jan 17 2019 I think you got the conversion direction mixed up. If typeof(assert(0))
- Dukc (2/16) Jan 17 2019 Oh yes. Stratch that.
- Olivier FAURE (7/18) Jan 17 2019 Noob question, but wouldn't a top type also be a unit type?
- H. S. Teoh (12/13) Jan 17 2019 No! A unit type is one inhabited by a single value. A top type is a type
- Olivier FAURE (15/22) Jan 17 2019 I think I'm confused.
- Paul Backus (9/32) Jan 17 2019 It's the other way around--there are things you can do in `foo`
- Neia Neutuladh (10/25) Jan 17 2019 bar knows nothing about the value passed in. It's pretty much equivalent...
- H. S. Teoh (72/92) Jan 17 2019 For one thing, Top would be the supertype of every other type, so you
- Olivier FAURE (5/17) Jan 17 2019 All these sound like things that would make sense with void?
- Olivier FAURE (8/11) Jan 17 2019 To expand on this, I'm understanding supertyping (at least in D
- H. S. Teoh (75/87) Jan 17 2019 Think of Top as the analogue of D's Object class. Every class inherits
- Johannes Loher (28/53) Jan 17 2019 I don't actually think declaring a variable of the Bottom type is
- Neia Neutuladh (7/10) Jan 17 2019 Which runs into default-initialization, so that's not awesome. But the
- Johannes Loher (5/20) Jan 17 2019 Both default initialization and void initialization of variables of type
- Johannes Loher (16/31) Jan 17 2019 By the way, I think it is debatable whether variables of type Tbottom
- Neia Neutuladh (6/16) Jan 17 2019 C and C++ don't care if you use something before initializing it. Most
- H. S. Teoh (31/45) Jan 17 2019 It looks that way, but actually it totally makes sense. It only looks
- Neia Neutuladh (16/22) Jan 17 2019 This is also an asymmetry: the following two functions abort at differen...
- H. S. Teoh (75/92) Jan 17 2019 That's an interesting take on Bottom. I think that makes sense. In D,
- Johannes Loher (17/31) Jan 17 2019 I didn't actually think this far, but you are right! We'd still somehow
- H. S. Teoh (39/56) Jan 17 2019 Hmm that's a good question. I was going to suggest that the very act of
- Johannes Loher (59/97) Jan 17 2019 What we can do is simply replace this by
- H. S. Teoh (39/134) Jan 17 2019 Hmm you're right, in this case, we're not actually declaring b as a
- Johannes Loher (14/16) Jan 17 2019 Indeed an interesting question. Since `Tbottom` is a type like any
- H. S. Teoh (33/51) Jan 17 2019 Actually, since there can be no instances of Tbottom, that in theory
- Johannes Loher (8/18) Jan 17 2019 This really made me laugh :D It should be consistent with
- H. S. Teoh (9/16) Jan 17 2019 [...]
- Olivier FAURE (23/33) Jan 21 2019 I think this solution would also create its own corner cases.
- Q. Schroll (3/11) Jan 17 2019 While unrelated to the DIP discussion, I will insist that x >=
- H. S. Teoh (5/19) Jan 17 2019 `NaN >= 0.0` evaluates to false, so the assert will still trigger.
- Walter Bright (2/3) Jan 17 2019 I'm loving this discussion!
- Nicholas Wilson (3/6) Jan 17 2019 This is draft-review level discussion, not final review
- =?UTF-8?Q?Tobias=20M=C3=BCller?= (3/14) Jan 20 2019 That could be useful in generic code that is instatiated with 'Bottom'
- Johannes Loher (48/60) Jan 17 2019 I believe this view of things results directly from the fact that there
- Nicholas Wilson (43/74) Jan 16 2019 I shows, the rationale for this DIP as it stands is wafer thin.
- H. S. Teoh (26/56) Jan 16 2019 Actually, there *are* some cases where people might try to write
- Walter Bright (7/10) Jan 16 2019 The idea originally came up here:
- Walter Bright (15/17) Jan 17 2019 Right. The language allowing one to write silly things is one thing, but...
- Meta (4/7) Jan 16 2019 IMO, this tone is uncalled for. Let's all try to be civil and
- Nicholas Wilson (9/11) Jan 17 2019 That is a criticism of the DIP and the way the DIP has been
- Olivier FAURE (9/15) Jan 17 2019 Agreed.
- Johannes Loher (13/19) Jan 16 2019 I don't completely agree with this statement. If possible, as language
- Johannes Loher (4/10) Jan 16 2019 In a later post, H. S. Theo describes some of the problems with void in
- Walter Bright (2/5) Jan 17 2019 Too much water under that bridge.
- Mike Franklin (9/16) Jan 18 2019 I suppose `bottom`, `top`, and `unit` types can be added to the
- H. S. Teoh (9/24) Jan 19 2019 [...]
- Johannes Loher (13/18) Jan 16 2019 I do actually think there is a lot of value in a bottom type. The
- Chris M. (3/8) Jan 17 2019 Get Bartosz in here :#)
- Johan Engelen (47/48) Jan 15 2019 I know we are no longer supposed to discuss the proposal's
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (9/13) Jan 15 2019 Because `void` is not a bottom type - a bottom type has no
- Kagamin (4/7) Jan 15 2019 For example llvm ir has its own type system that is checked, and
- Johan Engelen (18/30) Jan 15 2019 Although I am not a mathematician, this argumentation feels
- Timon Gehr (35/62) Jan 15 2019 But procedures are not outside the reach of mathematics. A procedure is
- Q. Schroll (15/21) Jan 16 2019 The void type is one of *three* type-theoretically different
- Timon Gehr (9/29) Jan 20 2019 No, you cannot have _variables_ of this type. There is nothing like this...
- Guillaume Piolat (3/6) Jan 15 2019 +1 for all this.
- Neia Neutuladh (14/35) Jan 15 2019 C and C++ name mangling doesn't include return types for free functions,...
- Meta (50/86) Jan 15 2019 With all due respect to all involved in this conversation, this
- Johan Engelen (32/68) Jan 15 2019 Indeed the whole D community could use more theory input, but the
- Nicholas Wilson (10/15) Jan 15 2019 non-standardness can be fixed quite easily
- aliak (16/22) Jan 15 2019 A very practical use case is with error code paths where you end
- Johan Engelen (6/23) Jan 16 2019 This is just another example of using the bottom type to signify
- Paul Backus (39/44) Jan 16 2019 Here's an example I ran into while working on my `expectations`
- luckoverthere (13/58) Jan 16 2019 As far as the DIP goes, it does not appear to make any mention of
- Paul Backus (18/36) Jan 16 2019 `match` is a template function whose return type is inferred from
- luckoverthere (14/59) Jan 16 2019 Also a simpler work around to your problem:
- Paul Backus (7/18) Jan 16 2019 Yes, this would also work. But having to insert a spurious return
- =?UTF-8?Q?Tobias=20M=C3=BCller?= (5/11) Jan 15 2019 AFAIK the reason is, that Rust has no subtyping in the language. In type
- Nicholas Wilson (16/61) Jan 15 2019 To me, bottom is a complex way to signal to the reader and to the
- aliak (5/9) Jan 15 2019 Is it "much" more costly?
- aliak (3/12) Jan 15 2019 Ooh and eventually getting a "top type"... ? Can't have a toptype
- Nicholas Wilson (15/24) Jan 15 2019 Bottom, must be implemented in the compiler. Attributes exist
- aliak (12/39) Jan 15 2019 You'd have to take that in to consideration because you'd have to
- Nicholas Wilson (14/30) Jan 15 2019 No, the logic to handle it is already there `typeof(f()) == int`,
- Neia Neutuladh (19/29) Jan 15 2019 That's a downside. Consider a binding processor for a scripting language...
- Jacob Carlborg (5/10) Jan 15 2019 Not sure it's even worth an attribute. I'm thinking more a pragma.
- Olivier FAURE (19/26) Jan 15 2019 I agree that this proposal should still be in the "Community
- Mike Parker (9/20) Jan 15 2019 "has been addressed" does not mean that the DIP will be revised
- jmh530 (4/14) Jan 15 2019 There's a typo in the abstract that should get fixed at least...
- Nicholas Wilson (19/31) Jan 15 2019 No, absolutely no.
- Atila Neves (4/16) Jan 15 2019 I don't remember in detail the discussion for a bottom type, but
- Jonathan Marler (33/45) Jan 15 2019 I'd like to point out that this proposal is introducing 2 new
- Johannes Loher (3/8) Jan 15 2019 +1
- Jonathan Marler (20/71) Jan 15 2019 To clarify a bit more.
- Neia Neutuladh (16/19) Jan 15 2019 Would this work?
- Johannes Loher (169/240) Jan 15 2019 Similar to other reviewers, I am very disappointed in the lack of
- Neia Neutuladh (20/40) Jan 15 2019 assert(0)++, for instance, is not allowed.
- Meta (6/9) Jan 15 2019 +1
- Johannes Loher (29/30) Jan 15 2019 17.
- Meta (38/48) Jan 15 2019 Looks like the compiler already some some kind of analysis to
- Johannes Loher (19/23) Jan 16 2019 I totally agree with this criticism. What does `Tbottom*` mean
- Dukc (8/24) Jan 16 2019 I think it was me who brought that pointer thing up in last
- Johannes Loher (27/33) Jan 16 2019 No, I actually meant it exactly as I wrote it. The reasoning is
- Dukc (4/12) Jan 16 2019 An interesting find! I cannot immediately see any good argument
- FeepingCreature (3/14) Jan 17 2019 That is amazing. I love it.
- Neia Neutuladh (7/21) Jan 17 2019 Unfortunately, it would mean void.sizeof is (void*).sizeof and
- Basile B. (3/6) Jan 15 2019 What is this new ".alignsize" property ? Is that supposed to be
- Walter Bright (7/7) Jan 15 2019 As a historical note, subroutines used to be grouped into two categories...
- Neia Neutuladh (102/106) Jan 15 2019 There are at least five parts to this proposal:
- Johannes Loher (64/80) Jan 16 2019 This is not completely accurate. Nothing actually is the bottom
- Neia Neutuladh (4/12) Jan 16 2019 I very much agree with void being a proper type. It's an annoying specia...
- Nicholas Wilson (13/40) Jan 16 2019 Thanks for this analysis.
- Dukc (6/10) Jan 16 2019 I agree with this. It's okay to reject the proposed alternatives,
- Jonathan Marler (8/20) Jan 17 2019 The DIP says this:
- Johannes Loher (5/16) Jan 17 2019 I asked the same question earlier. What I _think_ it means is that you
- Tim (45/57) Jan 17 2019 An advantage of the bottom type over an attribute is, that it is
- Neia Neutuladh (5/9) Jan 17 2019 It was unclear to me, however, whether you could implicitly convert a
- Jonathan Marler (17/54) Jan 17 2019 This is another good example. I'd like to point out that the Unit
- H. S. Teoh (24/32) Jan 17 2019 This would introduce an asymmetry with the rest of the pointer types in
- Johannes Loher (6/42) Jan 17 2019 +1 for making Tbottom* have the single value `null` (this totally makes
- Jonathan Marler (13/31) Jan 17 2019 Yes that's kind of the point :) It's a new pointer type that has
- H. S. Teoh (27/61) Jan 17 2019 No, that's backwards. (1) Introducing asymmetry, i.e., special cases,
- Johannes Loher (14/19) Jan 17 2019 I suggested this earlier in this thread, i.e.
- H. S. Teoh (56/80) Jan 17 2019 This is where I wish someone who actually knows type theory would step
- Johannes Loher (11/69) Jan 17 2019 This actually sounds like reasonable way to view it. You can even get
- H. S. Teoh (43/60) Jan 17 2019 [...]
- Johannes Loher (9/58) Jan 17 2019 I actually think this is ok. I don't reall have an issue with it. But I
- Olivier FAURE (22/34) Jan 21 2019 I wonder if there's a body of research in type theory about
- Neia Neutuladh (6/18) Jan 21 2019 That's what structural typing generally is. In essence, with structural
- Olivier FAURE (10/15) Jan 22 2019 I'm not sure. Most discussions I've seen equate structural typing
- Neia Neutuladh (14/19) Jan 22 2019 Because it makes field order significant. It means that these two types
- Olivier FAURE (30/41) Jan 22 2019 See, this is exactly what I was talking about earlier.
- Neia Neutuladh (10/23) Jan 22 2019 In a structural type system, functions take "anything that conforms to t...
- Jonathan Marler (16/81) Jan 17 2019 This is definitely not true in all cases, therefore, is itself
- H. S. Teoh (52/71) Jan 17 2019 [...]
- Paul Backus (38/47) Jan 17 2019 The official term for this is "nominal typing" [1]. In a
- H. S. Teoh (45/76) Jan 18 2019 Thank you. That's the gist of it.
- =?UTF-8?Q?Tobias=20M=C3=BCller?= (10/14) Jan 21 2019 Since a unit type consists of a single value, there exist as much unit
- =?UTF-8?Q?Tobias=20M=C3=BCller?= (11/23) Jan 21 2019 A unit type carrying no value means that the *dynamic value at runtime*
- Neia Neutuladh (11/14) Jan 21 2019 Point of pedantry: bool is an integer type according to the type
- Nicholas Wilson (3/15) Jan 22 2019 Indeed, hopefully we can convince Andrei to reverse the decision
- 12345swordy (3/24) Jan 22 2019 Good luck with that, you going to need it.
- Chris M. (2/16) Jan 22 2019 I'm still salty about DIP1015
- Neia Neutuladh (3/6) Jan 17 2019 Dereferencing TBottom* would have to be rewritten as `assert(false);` to...
- Johannes Loher (3/11) Jan 17 2019 No, dereferencing an instance of type `Tbottom*` is always dereferencing
- Neia Neutuladh (4/15) Jan 17 2019 Oh, I misread that as "assume that TBottom* is null", sorry. Forcibly
- Johannes Loher (19/23) Jan 17 2019 You could also do this with an attribute. Consider the following:
DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.Sorry to bikeshed but any name other than 'bottom' would be preferable, D may be the butt of the joke so to speak if code is full of this. There are plenty of synonyms.
Jan 15 2019
On Tuesday, 15 January 2019 at 10:08:28 UTC, ixid wrote:Sorry to bikeshed but any name other than 'bottom' would be preferable, D may be the butt of the joke so to speak if code is full of this. There are plenty of synonyms."never" is used in Rust, i.e., the never type. Scala and a few other languages use Nothing, and Haskell uses Void (not the same as `void` in C-family languages).
Jan 15 2019
On Tuesday, 15 January 2019 at 21:39:08 UTC, Meta wrote:On Tuesday, 15 January 2019 at 10:08:28 UTC, ixid wrote:+1 for not using "bottom". It's really confusing to anyone who is not a language theory expert. "Never" makes a lot more sense. Never is also used in Swift. Motivation from their proposal for a bottom type [0]: "The best name for the standard library uninhabited type was a point of contention. Many of the names suggested by type theory literature or experience in functional programming circles are wanting: * Void might have been mathematically appropriate, but alas has already been heavily confused with "unit" in C-derived circles. * Names like Nothing, Nil, etc. have the potential to be confused with the nil value of Optional, or with returning Void. * Type theory jargon like Bottom wouldn't be immediately understood by many users. The first revision of this proposal suggested NoReturn, but in discussion, the alternative name Never was suggested, which was strongly preferred by most participants. Never properly implies the temporal aspect--this function returns never --and also generalizes well to other potential applications for an uninhabited type. For instance, if we gained the ability to support typed throws, then () throws<Never> -> Void would also clearly communicate a function that never throws." [0] https://github.com/apple/swift-evolution/blob/master/proposals/0102-noreturn-bottom-type.mdSorry to bikeshed but any name other than 'bottom' would be preferable, D may be the butt of the joke so to speak if code is full of this. There are plenty of synonyms."never" is used in Rust, i.e., the never type. Scala and a few other languages use Nothing, and Haskell uses Void (not the same as `void` in C-family languages).
Jan 15 2019
On 1/15/2019 8:16 PM, aliak wrote:+1 for not using "bottom". It's really confusing to anyone who is not a language theory expert.Fortunately, "bottom type" is very google-able, and this comes up: https://en.wikipedia.org/wiki/Bottom_type"Never" makes a lot more sense.Inventing new jargon for established terms is worse. Established jargon gives the reader a certain level of confidence when established terms are used correctly in that the reader understands the text and that the writer understands the text. The "Bottom" type appears in books on type theory, like "Types and Programming Languages" by Pierce. "Never" does not, leaving the reader wondering what relation it might have to a bottom type. --- It reminds me of when we were coming up with a term for what became "immutable" types. People would say: Q: what does 'readonly' actually mean? A: readonly means immutable Q: ok, I get it Q: what does 'invariant' actually mean? A: invariant means immutable Q: ok, I get it Finally we realized we were being clubbed with a Clue-By-4. Just call it "immutable" and voila, haven't had comprehensibility problems with it since.
Jan 15 2019
On Tue, 15 Jan 2019 22:43:59 -0800, Walter Bright wrote:It reminds me of when we were coming up with a term for what became "immutable" types. People would say: Q: what does 'readonly' actually mean? A: readonly means immutable Q: ok, I get it Q: what does 'invariant' actually mean? A: invariant means immutable Q: ok, I get it Finally we realized we were being clubbed with a Clue-By-4. Just call it "immutable" and voila, haven't had comprehensibility problems with it since.In this case, people will ask: "what does bottom_t mean?" Some people will first look at Wikipedia and see:In type theory, a theory within mathematical logic, the bottom type is the type that has no values. It is also called the zero or empty type, and is sometimes denoted with falsum (⊥).That, like many Wikipedia articles, clarifies nothing. They might read on and see:In C, C++, Java, and Dart, the bottom type is void.Which is wrong, and they'll wonder why the language has both bottom_t and void, and then they'll ask here. And then we'll answer: it means this function doesn't return. Which suggests "NeverReturns" or some variation of that as the name of the type.
Jan 15 2019
On Wednesday, 16 January 2019 at 06:43:59 UTC, Walter Bright wrote:On 1/15/2019 8:16 PM, aliak wrote:How is it new jargon when other programming languages that are in far, far more widespread use than D are already using it? And why is it that none (i.e zero) of them went with bottom? Do you honestly believe more readers will understand "returns bottom" over "returns never"? Honestly?+1 for not using "bottom". It's really confusing to anyone who is not a language theory expert.Fortunately, "bottom type" is very google-able, and this comes up: https://en.wikipedia.org/wiki/Bottom_type"Never" makes a lot more sense.Inventing new jargon for established terms is worse. Established jargon gives the reader a certain level of confidence when established terms are used correctly in that the reader understands the text and that the writer understands the text.The "Bottom" type appears in books on type theory, like "Types and Programming Languages" by Pierce. "Never" does not, leaving the reader wondering what relation it might have to a bottom type.99.9% of programmers do not read those books, and bottom will. s/bottom/never :p--- It reminds me of when we were coming up with a term for what became "immutable" types. People would say: Q: what does 'readonly' actually mean? A: readonly means immutable Q: ok, I get it Q: what does 'invariant' actually mean? A: invariant means immutable Q: ok, I get it Finally we realized we were being clubbed with a Clue-By-4. Just call it "immutable" and voila, haven't had comprehensibility problems with it since.Immutable is obvious without googling. Is bottom? Honestly, this sounds like one of those arguments of convenience -> "I like X so ill argue that it's the proper term" whereas if it were reversed, the argument would be "I like X because it makes more sense than the correct theoretical term that no one will understand" 🤷♂️
Jan 15 2019
On Wednesday, 16 January 2019 at 06:43:59 UTC, Walter Bright wrote:On 1/15/2019 8:16 PM, aliak wrote: It reminds me of when we were coming up with a term for what became "immutable" types. People would say: Q: what does 'readonly' actually mean? A: readonly means immutable Q: ok, I get it Q: what does 'invariant' actually mean? A: invariant means immutable Q: ok, I get it Finally we realized we were being clubbed with a Clue-By-4. Just call it "immutable" and voila, haven't had comprehensibility problems with it since.Yeah shame you skipped that discussion with enum and nothrow.
Jan 16 2019
On Wednesday, 16 January 2019 at 06:43:59 UTC, Walter Bright wrote:Since Rust already uses `never`, it won't be inventing new jargon. We can choose either of these two based on our taste. But also, your DIP needn't make that choice. Since the type is defined with `typeof` instead of it's own keyword, anyone can alias the type however he wants. The terminology can be left to evolve naturally, without needing to dictate a term for the type."Never" makes a lot more sense.Inventing new jargon for established terms is worse. Established jargon gives the reader a certain level of confidence when established terms are used correctly in that the reader understands the text and that the writer understands the text. The "Bottom" type appears in books on type theory, like "Types and Programming Languages" by Pierce. "Never" does not, leaving the reader wondering what relation it might have to a bottom type.
Jan 16 2019
On 1/16/2019 2:18 AM, Dukc wrote:But also, your DIP needn't make that choice. Since the type is defined with `typeof` instead of it's own keyword, anyone can alias the type however he wants. The terminology can be left to evolve naturally, without needing to dictate a term for the type.Exactly why it was done that way. It was a way to avoid needing a keyword, much like `string` is an alias, not a keyword. The name was never the point of the DIP.
Jan 16 2019
On Wednesday, 16 January 2019 at 10:25:21 UTC, Walter Bright wrote:The name was never the point of the DIP.Agreed, the type name is bikeshedding. What is your answer to the rebuttal, common in both this thread and the previous thread, that a bottom type has no practical use justifying the complexity (and probable corner cases) it would add to the type system? In particular, I think this question from Jonathan Marler really needs to be answered before the DIP moves forward:What else does the new bottom type give us? And be sure to distinguish between things that only require "no-return" functions and things that require a bottom type. If they only require "no-return" functions, then they are not rationale for adding a bottom type.I mean, this is clearly the biggest point of contention. If the DIP is forwarded and implemented without addressing it (like it has been so far), then it's basically saying that the DIP process is a formality and the gathered feedback doesn't matter.
Jan 16 2019
On Wednesday, 16 January 2019 at 11:40:36 UTC, Olivier FAURE wrote:On Wednesday, 16 January 2019 at 10:25:21 UTC, Walter Bright wrote:I mean, this is clearly the biggest point of contention. If the DIP is forwarded and implemented without addressing it (like it has been so far), then it's basically saying that the DIP process is a formality and the gathered feedback doesn't matter.I guess I need to edit the procedure document to clarify this. The DIP process is not just a formality and is working as intended. The purpose of the review process is to help get the DIP in shape for the formal assessment by Walter and Andrei, to help ensure it is as thorough as possible. It is no way intended to be a referendum on whether a DIP should be implemented. In the formal assessment, Walter and Andrei will take any unaddressed feedback and objections into account, but the final decision rests with them. Of course, in this case, it's a bit different since Walter is the DIP author. Your feedback is going directly to him. If you happen to persuade him to come around to your point of view, congratulations. But again, please don't view this referendum.
Jan 16 2019
On Wednesday, 16 January 2019 at 12:11:36 UTC, Mike Parker wrote:congratulations. But again, please don't view this referendum."as a referendum" And I should add, if anyone disagrees and wants to reply to me, please open another thread. I don't want to pollute the review thread with a debate on the merits of the DIP process. Thanks!
Jan 16 2019
On 1/16/2019 3:40 AM, Olivier FAURE wrote:What is your answer to the rebuttal, common in both this thread and the previous thread, that a bottom type has no practical use justifying the complexity (and probable corner cases) it would add to the type system?That indeed is the correct question. The thing is, I know little about formal type theory. As far as I can tell, nobody in this community does either, aside from Timon Gehr. There are notions in mathematics and programming that seem like nonsense, but exist as important boundary conditions: * zero * infinity * black hole objects * white hole objects * identity functions * top type (represents every value, like D's "Object" root type) * bottom type (represents no value) Civilization had arithmetic long before zero was discovered. Today, we can't imagine doing math without 0. Infinity came along later, and its usefulness can be seen as an indispensable foundation of calculus. C++ muddled along for decades with an identity function being impossible to write. Although an identity function seems pointless, it went unrecognized for a long time that this was causing all sorts of problems with templates. Back to the bottom type. C's ability to manipulate types is so primitive nobody misses type calculus. But D can do type calculus, and I worry that the lack of a bottom type, far from creating corner cases, causes corner cases and awkward problems analogous to what the lack of an identity function causes. The trouble is, I don't know enough about type theory to know if this is the case or not. I only know analogies to problems other systems have when the boundary cases are not expressible. An example of an awkward corner case caused by lack of a bottom type: noreturn int betty(); How can it return an int yet not return? It makes no sense. And if one was doing type calculus on the return value of betty(), it would show up as 'int' and botch everything up. It'd be like substituting '5' in calculations that need a '0', but have no notion of '0'. The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem. My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out! (I.e. it's not just about functions that don't return. That's just an obvious benefit.)
Jan 16 2019
On Wednesday, 16 January 2019 at 22:32:33 UTC, Walter Bright wrote:The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem.Isn't `void` in fact analogous to the TOP type? With a bit additional features, it could be used as one, as I understand it.
Jan 16 2019
On 1/16/2019 2:59 PM, Dukc wrote:Isn't `void` in fact analogous to the TOP type? With a bit additional features, it could be used as one, as I understand it.I think H.S. Teoh's reply helps with that. 'void' is symptomatic of poor design inherited from C along with my non-CS background. My interest in a bottom type comes from wanting a sounder mathematical basis for types in D, but sadly nobody else seems interested, and this DIP is pretty much DOA.
Jan 16 2019
On Thursday, 17 January 2019 at 01:45:13 UTC, Walter Bright wrote:On 1/16/2019 2:59 PM, Dukc wrote:OTOH, it gets us easy compatibility and familiarity.Isn't `void` in fact analogous to the TOP type? With a bit additional features, it could be used as one, as I understand it.I think H.S. Teoh's reply helps with that. 'void' is symptomatic of poor design inherited from C along with my non-CS background.My interest in a bottom type comes from wanting a sounder mathematical basis for types in D, but sadly nobody else seems interested, and this DIP is pretty much DOA.Its not that we're not interested, its that the DIP needs to show tangible practical benefits (which are low) that clearly outweigh the risks and costs (which are very high), and shows clear benefits relative to the other solutions to the problems (which already exist), which it doesn't. If this DIP is DOA, how has it managed to get all the way to final review? It should either be dropped, or still be in draft.
Jan 16 2019
Am 17.01.19 um 02:45 schrieb Walter Bright:My interest in a bottom type comes from wanting a sounder mathematical basis for types in D, but sadly nobody else seems interested, and this DIP is pretty much DOA.I don't think this is true. At least I am very interested in giving D's types a sounder mathematical basis.
Jan 16 2019
On 1/16/2019 10:38 PM, Johannes Loher wrote:I don't think this is true. At least I am very interested in giving D's types a sounder mathematical basis.Good to hear!
Jan 16 2019
On Wed, Jan 16, 2019 at 05:45:13PM -0800, Walter Bright via Digitalmars-d wrote: [...]My interest in a bottom type comes from wanting a sounder mathematical basis for types in D, but sadly nobody else seems interested, and this DIP is pretty much DOA.Actually, I think most of the negative reactions come not from the technical problems of the DIP itself, but from the (perceived or otherwise) way it was handled, such as how it got to final review stage when it clearly needs more work. It may have sparked less outrage had it stayed in draft stage until the technical problems were ironed out. T -- Be in denial for long enough, and one day you'll deny yourself of things you wish you hadn't.
Jan 17 2019
On 17/01/2019 11:21 PM, H. S. Teoh wrote:On Wed, Jan 16, 2019 at 05:45:13PM -0800, Walter Bright via Digitalmars-d wrote: [...]As someone who will be voting no because I do not feel the feedback has been applied in the DIP, this is correct. While I may agree that getting this passed is a good thing, I do not feel the lack of response is.My interest in a bottom type comes from wanting a sounder mathematical basis for types in D, but sadly nobody else seems interested, and this DIP is pretty much DOA.Actually, I think most of the negative reactions come not from the technical problems of the DIP itself, but from the (perceived or otherwise) way it was handled, such as how it got to final review stage when it clearly needs more work. It may have sparked less outrage had it stayed in draft stage until the technical problems were ironed out.
Jan 17 2019
On Wednesday, 16 January 2019 at 22:32:33 UTC, Walter Bright wrote:An example of an awkward corner case caused by lack of a bottom type: noreturn int betty(); How can it return an int yet not return? It makes no sense. And if one was doing type calculus on the return value of betty(), it would show up as 'int' and botch everything up. It'd be like substituting '5' in calculations that need a '0', but have no notion of '0'. The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem. My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out! (I.e. it's not just about functions that don't return. That's just an obvious benefit.)So you want to add something you don't fully understand and aren't sure if there is even any benefit at all. You just presume there to be a benefit? I don't think you should think of void and void* as the same thing. Pointers are their own type, if all you have is a void* and no other information, you might as well be pointing at nothing. I suggest you figure out what it is you actually want to implement, this is no better than cowboy programming. Hell might even be worse than a pilot wearing a blindfold with someone whispering in his ear that he is going the right direction. Don't implement something you obviously don't know enough about. I can only imagine what kind of disaster this is going to be in comparison to auto-encoding.
Jan 16 2019
On 1/16/2019 3:11 PM, luckoverthere wrote:So you want to add something you don't fully understand and aren't sure if there is even any benefit at all. You just presume there to be a benefit? I don't think you should think of void and void* as the same thing. Pointers are their own type, if all you have is a void* and no other information, you might as well be pointing at nothing. I suggest you figure out what it is you actually want to implement, this is no better than cowboy programming. Hell might even be worse than a pilot wearing a blindfold with someone whispering in his ear that he is going the right direction. Don't implement something you obviously don't know enough about. I can only imagine what kind of disaster this is going to be in comparison to auto-encoding.A fair point, though the rudeness is disappointing. I'd appreciate help with it. I hope this DIP stimulates something more positive than excoriation. Consider it a challenge!
Jan 16 2019
On Wed, 16 Jan 2019 14:32:33 -0800, Walter Bright wrote:* top type (represents every value, like D's "Object" root type)D doesn't have a top type. Object is the top of the hierarchy of non-COM, non-C++ classes. std.variant.Variant is the closest thing D has to a top type.An example of an awkward corner case caused by lack of a bottom type: noreturn int betty(); How can it return an int yet not return? It makes no sense.Any empty type would suffice here. Bottom is just one canonical empty type.My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out!I'm sorry that I missed your post asking for help on this DIP's background research while it was being drafted.(I.e. it's not just about functions that don't return. That's just an obvious benefit.)This seems like a very good way to get the details wrong and eliminate the potential benefits that a different implementation, one better informed by theory or real-world examples from other programming languages, could give.
Jan 16 2019
On Wed, Jan 16, 2019 at 02:32:33PM -0800, Walter Bright via Digitalmars-d wrote: [...]The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem.[...] Actually, the *real* problem with `void` is that it has been overloaded to mean multiple, context-dependent things that are somewhat but not really the same, and aren't really compatible with each other. 1) When specified as a function return type, `void` signifies that the function does not return a meaningful value. In this sense, one might argue that it's a unit type (that requires no representation because it always holds the same value). 2) When `void` is specified as the type of a variable, it is an error, the justification being that you cannot have a variable that holds nothing. However, this is incompatible with being a unit type: if it were *really* a unit type, we'd be allowed to declare as many variables as we want of that type. They would take up zero storage space (because there's no need to store a value that's the only value that can exist in that type), and every variable of this type would vacuously equal every other variable, because they can only hold the same value, so comparisons with == will always be vacuously true. You'd be able to pass it as a function parameter, though that'd be pretty useless since it conveys no information, and hence no code actually needs to be generated for it (there's no need to actually pass anything at the machine code level). None of this is permitted for `void`, thereby making `void` not a unit type. In fact, `void` in this usage seems closer to being a bottom type -- a type that holds no value, and therefore it makes no sense to instantiate a variable of that type. So this isn't really consistent with (1). 3) The pointer type derived from `void`, that is, `void*`, has an ad hoc meaning that is not consistent with either (1) or (2): void* behaves like a top pointer type, i.e., every pointer implicitly casts to it. But that means that the `void` in `void*` does not mean the same thing as the `void` in (1) or (2) above, because if (1) were true, then it makes no sense to be able to construct a pointer to it. You cannot construct a pointer to nothing! The closest thing you could get to constructing a pointer to nothing is null, however, typeof(null) != void* (even though null is implicitly convertible to void* due to void* behaving like a pointer to a top type). Furthermore, if (2) were to hold, then `void*` couldn't possibly point to any other type, because a pointer to a unit type can only have one value (a "magic" pointer value that points to the non-physical storage location of the only value of the unit type). So `void*` is inconsistent with (2). What `void*` actually means is NOT a pointer to `void`, but rather a pointer to a top type that can represent any value. However, even here, the language is not consistent: you cannot dereference a void* without casting it into something else first. If indeed it were a true top type, then dereferencing void* ought to give us a value of the top type (i.e., any value that can be represented in the language). So (3) is not consistent with (1) and (2), and is closer to being a pointer to a top type, but not really because you can never dereference it. So you see, `void` is a rats' nest of special cases and ad hoc monkey-patched semantics that's inconsistent with itself. Sometimes it behaves like a unit type, sometimes it behaves like a bottom type, and sometimes it behaves like a top type, and none of these usages are consistent with each other, nor are they truly consistent with themselves either. It's like working with a number system that has no concept of zero, negative numbers, or infinity, and then patching in a new number (let's call it X) that sometimes behaves like zero, sometimes like a negative number, and sometimes like infinity. Good luck doing calculations involving X. The only reason our brains haven't exploded yet from these inconsistencies is because we have become acclimatized to interpreting `void` in different ways depending on the context. But because of that, trying to make sense of `void` as a consistent entity in itself is an exercise in futility. And now we're proposing to add a Tbottom type to try to fix the missing corner cases in the type system... Just *how* is it going to interact with the existing inconsistent meanings of `void`? I just can't wait to see the glorious chaos that will surely result. :-/ T -- Genius may have its limitations, but stupidity is not thus handicapped. -- Elbert Hubbard
Jan 16 2019
On Thursday, 17 January 2019 at 00:09:50 UTC, H. S. Teoh wrote:On Wed, Jan 16, 2019 at 02:32:33PM -0800, Walter Bright via Digitalmars-d wrote: [...]A good summary of the issues with void. Maybe adding a bottom type enables some new clever semantics, but I would venture to guess that adding a real unit type would be even more helpful than a bottom type. I've had cases where being able to use void as a function parameter or a field would have made some of my templates much cleaner. Maybe we should focus on making void a real unit type before we try to add a bottom type?[...][...] Actually, the *real* problem with `void` is that it has been overloaded to mean multiple, context-dependent things that are somewhat but not really the same, and aren't really compatible with each other. [...]
Jan 16 2019
On Thursday, 17 January 2019 at 00:41:54 UTC, Jonathan Marler wrote:On Thursday, 17 January 2019 at 00:09:50 UTC, H. S. Teoh wrote:D has a couple of different unit types which all behave differently: typeof(null), its sole value being null. It also happens to be the bottom type for all objects, pointers and arrays. void, which actually *does* have a single value - you just can't declare a variable with type void: void main() { auto v = new void[1]; pragma(msg, typeof(v[0])); // Prints "void" import std.stdio; writeln(v[0]); // Error: template std.stdio.writeln cannot deduce function from argument types !()(void) } This may actually be a compiler bug/quirk; I'm not completely sure. Any enum with only 1 element: enum Unit { val } As an aside, the empty enum: enum Empty { } Is an uninhabited type similar to Bottom, but is not implicitly convertible to any other type, unlike Bottom. The empty struct: struct Unit { } Unit u; writeln(u); // Prints Unit() And probably more that I have missed.On Wed, Jan 16, 2019 at 02:32:33PM -0800, Walter Bright via Digitalmars-d wrote: [...]A good summary of the issues with void. Maybe adding a bottom type enables some new clever semantics, but I would venture to guess that adding a real unit type would be even more helpful than a bottom type. I've had cases where being able to use void as a function parameter or a field would have made some of my templates much cleaner. Maybe we should focus on making void a real unit type before we try to add a bottom type?[...][...] Actually, the *real* problem with `void` is that it has been overloaded to mean multiple, context-dependent things that are somewhat but not really the same, and aren't really compatible with each other. [...]
Jan 16 2019
Am 17.01.19 um 03:59 schrieb Meta:D has a couple of different unit types which all behave differently: typeof(null), its sole value being null. It also happens to be the bottom type for all objects, pointers and arrays. void, which actually *does* have a single value - you just can't declare a variable with type void: void main() { auto v = new void[1]; pragma(msg, typeof(v[0])); // Prints "void" import std.stdio; writeln(v[0]); // Error: template std.stdio.writeln cannot deduce function from argument types !()(void) } This may actually be a compiler bug/quirk; I'm not completely sure. Any enum with only 1 element: enum Unit { val }I have already resorted to using `struct Unit {}` as a proper unit type in the past. The problem with this however is that it is only convention. As soon as you have to interface with somebody else's code, you lost, because they all use void and expect you to use void.As an aside, the empty enum: enum Empty { } Is an uninhabited type similar to Bottom, but is not implicitly convertible to any other type, unlike Bottom. The empty struct: struct Unit { } Unit u; writeln(u); // Prints Unit() And probably more that I have missedWhile all of this is true, in my opinion there should be one "canonical" unit type in the sense that it works as a proper unit type (it has only one value, you can declare variables of it) and it is implicitly returned from functions which do not have an explicit return statement (and are not inferred to return Tbottom, if that is added). The candidate for such a type would be void, but there are quite few things we'd need to fix as H. S. Theo explained somewhere in this thread.
Jan 16 2019
On Thu, Jan 17, 2019 at 02:59:09AM +0000, Meta via Digitalmars-d wrote: [...]As an aside, the empty enum: enum Empty { } Is an uninhabited type similar to Bottom, but is not implicitly convertible to any other type, unlike Bottom.Unfortunately, you get a compile error for this declaration. But assuming it were allowed, then it would be possible to declare multiple distinct empty enums that do not interconvert with each other, which would mean that there is not one, but arbitrarily many bottom types of this kind.The empty struct: struct Unit { } Unit u; writeln(u); // Prints Unit()Oddly enough, Unit.sizeof == 1, as a hack for generating distinct addresses when you declare multiple instances of Unit. One would have expected .sizeof == 0 for a unit type (and NaN or an error if you attempted to take .sizeof of an empty enum, if empty enums were allowed). T -- It is widely believed that reinventing the wheel is a waste of time; but I disagree: without wheel reinventers, we would be still be stuck with wooden horse-cart wheels.
Jan 17 2019
On Thu, Jan 17, 2019 at 02:39:18AM -0800, H. S. Teoh via Digitalmars-d wrote:On Thu, Jan 17, 2019 at 02:59:09AM +0000, Meta via Digitalmars-d wrote: [...]Further thoughts on empty enums in D: according to the spec, an enum always has an underlying base type, the default of which is int. So an empty enum as declared above, if we were to hypothetically allow it, would be a subtype of int (a bottom int subtype). Which also means we can declare: enum BottomStr : string { } which, AFAIK, would be treated as something distinct from the bottom int type. So it would represent a different bottom type. You'd need a "true" bottom type to get the direct equivalent of the Bottom of type theory. Of course, we could stipulate that all empty enums are interpreted as Tbottom, which would clear up this mess -- but this is yet another area where the DIP in question failed to address.As an aside, the empty enum: enum Empty { } Is an uninhabited type similar to Bottom, but is not implicitly convertible to any other type, unlike Bottom.Unfortunately, you get a compile error for this declaration. But assuming it were allowed, then it would be possible to declare multiple distinct empty enums that do not interconvert with each other, which would mean that there is not one, but arbitrarily many bottom types of this kind.[...] And furthermore, as somebody has already pointed out, Unit as declared above would be distinct from empty structs of any other name that you might declare. So there'd be multiple incompatible unit types. Which again is a mess, since two functions that don't return values ("procedures") could ostensibly return distinct unit types (let's say one returns void and the other returns Unit, or one returns Unit and the other returns another empty struct of a different name), and you'd have a messy incompatibility situation. T -- Give a man a fish, and he eats once. Teach a man to fish, and he will sit forever.The empty struct: struct Unit { } Unit u; writeln(u); // Prints Unit()Oddly enough, Unit.sizeof == 1, as a hack for generating distinct addresses when you declare multiple instances of Unit. One would have expected .sizeof == 0 for a unit type (and NaN or an error if you attempted to take .sizeof of an empty enum, if empty enums were allowed).
Jan 17 2019
On Thursday, 17 January 2019 at 18:11:10 UTC, H. S. Teoh wrote:On Thu, Jan 17, 2019 at 02:39:18AM -0800, H. S. Teoh via Digitalmars-d wrote:The compiler could "decay" these various "unit types" to the same type (with different names). struct EmptyStruct { } enum EmptyEnum { } static assert( is (EmptyStruct == TUnit) ); static assert( is (EmptyEnum == TUnit) ); At that point they just become different ways of writing the same thing, but should keep the type theory sound.[...]Further thoughts on empty enums in D: according to the spec, an enum always has an underlying base type, the default of which is int. So an empty enum as declared above, if we were to hypothetically allow it, would be a subtype of int (a bottom int subtype). Which also means we can declare: enum BottomStr : string { } which, AFAIK, would be treated as something distinct from the bottom int type. So it would represent a different bottom type. You'd need a "true" bottom type to get the direct equivalent of the Bottom of type theory. Of course, we could stipulate that all empty enums are interpreted as Tbottom, which would clear up this mess -- but this is yet another area where the DIP in question failed to address.[...][...] And furthermore, as somebody has already pointed out, Unit as declared above would be distinct from empty structs of any other name that you might declare. So there'd be multiple incompatible unit types. Which again is a mess, since two functions that don't return values ("procedures") could ostensibly return distinct unit types (let's say one returns void and the other returns Unit, or one returns Unit and the other returns another empty struct of a different name), and you'd have a messy incompatibility situation. T
Jan 17 2019
On Thursday, 17 January 2019 at 18:11:10 UTC, H. S. Teoh wrote:On Thu, Jan 17, 2019 at 02:39:18AM -0800, H. S. Teoh via Digitalmars-d wrote:The compiler could "decay" these various "unit types" to the same type (with different names). struct EmptyStruct { } enum EmptyEnum { } static assert( is (EmptyStruct == TUnit) ); static assert( is (EmptyEnum == TUnit) ); At that point they just become different ways of writing the same thing, but should keep the type theory sound.[...]Further thoughts on empty enums in D: according to the spec, an enum always has an underlying base type, the default of which is int. So an empty enum as declared above, if we were to hypothetically allow it, would be a subtype of int (a bottom int subtype). Which also means we can declare: enum BottomStr : string { } which, AFAIK, would be treated as something distinct from the bottom int type. So it would represent a different bottom type. You'd need a "true" bottom type to get the direct equivalent of the Bottom of type theory. Of course, we could stipulate that all empty enums are interpreted as Tbottom, which would clear up this mess -- but this is yet another area where the DIP in question failed to address.[...][...] And furthermore, as somebody has already pointed out, Unit as declared above would be distinct from empty structs of any other name that you might declare. So there'd be multiple incompatible unit types. Which again is a mess, since two functions that don't return values ("procedures") could ostensibly return distinct unit types (let's say one returns void and the other returns Unit, or one returns Unit and the other returns another empty struct of a different name), and you'd have a messy incompatibility situation. T
Jan 17 2019
Am 17.01.19 um 01:41 schrieb Jonathan Marler:A good summary of the issues with void. Maybe adding a bottom type enables some new clever semantics, but I would venture to guess that adding a real unit type would be even more helpful than a bottom type. I've had cases where being able to use void as a function parameter or a field would have made some of my templates much cleaner. Maybe we should focus on making void a real unit type before we try to add a bottom type?I totally agree with this. I would love to see void becomming a real unit type! I have already tripped over the fact that void is not a proper unit type several times. It makes generic function composition (and other generic constructions) so much more annoying: You always need to treat void as a special case.
Jan 16 2019
On Thursday, 17 January 2019 at 00:09:50 UTC, H. S. Teoh wrote:So you see, `void` is a rats' nest of special cases and ad hoc monkey-patched semantics that's inconsistent with itself. Sometimes it behaves like a unit type, sometimes it behaves like a bottom type, and sometimes it behaves like a top type, and none of these usages are consistent with each other, nor are they truly consistent with themselves either.I think `void` can still be saved. I'm not sure about the meaning of "bottom" and "top" types, but I think that with a few non-breaking changes, it can be made to logically be the polar opposite of typeof(assert(0)): -Where any type would be convertible to `typeof(assert(0))`, no type is convertible to `void`. -Where no type is convertible from `typeof(assert(0))`, any type could be made convertible from `void`, the only possible value of void converting to target type's `.init` value. -Where a pointer to `typeof(assert(0))` can point to nothing, a pointer to `void` can point to anything. -Where `typeof(assert(0))`, being capable of representing any type possible without loss of information, would have infinite size (and thus be impossible), `sizeof(void) == 0`, so it is possible even in systems with no memory at all. -`void` variables should be valid syntax, that can be assigned (no-op at machine level) and compared (lowered to literal `true` by compiler) to each other. -Compiler would be free to decide any non-null address for a void variable. Since dereferencing a pointer to void will only result in void, which never uses any memory, it cannot do any harm.
Jan 17 2019
On Thursday, 17 January 2019 at 10:35:00 UTC, Dukc wrote:I think `void` can still be saved. I'm not sure about the meaning of "bottom" and "top" types, but I think that with a few non-breaking changes, it can be made to logically be the polar opposite of typeof(assert(0)): -Where any type would be convertible to `typeof(assert(0))`, no type is convertible to `void`.Sounds good.-Where no type is convertible from `typeof(assert(0))`, any type could be made convertible from `void`, the only possible value of void converting to target type's `.init` value.Consider: int n = foo(); A refactoring changes foo() from returning int to returning void. Now n is 0, and the type system is not helping me figure out what's wrong. On a more theoretical level, void's set of possible values contains exactly one value, and that value is not int.init, string.init or myStruct.init - it's void.init.-Where a pointer to `typeof(assert(0))` can point to nothing, a pointer to `void` can point to anything.-Where `typeof(assert(0))`, being capable of representing any type possible without loss of information, would have infinite size (and thus be impossible), `sizeof(void) == 0`, so it is possible even in systems with no memory at all.If void.sizeof == 0, then any void[], regardless of element count, would point to a memory region 0 bytes in size. This conflicts with your previous point. I'm not averse to making void a unit type, but there seems to be some corners your design doesn't consider. -- Simen
Jan 17 2019
On Thu, Jan 17, 2019 at 10:35:00AM +0000, Dukc via Digitalmars-d wrote: [...]I think `void` can still be saved. I'm not sure about the meaning of "bottom" and "top" types, but I think that with a few non-breaking changes, it can be made to logically be the polar opposite of typeof(assert(0)): -Where any type would be convertible to `typeof(assert(0))`, no type is convertible to `void`. -Where no type is convertible from `typeof(assert(0))`, any type could be made convertible from `void`, the only possible value of void converting to target type's `.init` value.I think you got the conversion direction mixed up. If typeof(assert(0)) is supposed to be the bottom type, then it must implicitly convert to every other type (as Walter's DIP stipulates), but no other type could implicitly convert to it.-Where a pointer to `typeof(assert(0))` can point to nothing, a pointer to `void` can point to anything.This is inconsistent with void being a unit type. A pointer to a unit type can only ever point to the unique value of that type, it cannot point to anything else. This is where changing void to be a unit type will break every code that uses void*. Basically, void* is a misnomer inherited from C. It really means any*, where 'any' is the universal type (i.e. the top type). But because C has no such concept as a universal type, the void keyword was contextually redefined to mean 'any' when a pointer type is made from it. It's an unfortunate inconsistency analogous to saying that "one" means "one" when it's a return type, but "one" means "infinity" when you make a pointer from it. It doesn't make sense as a whole, even though contextually we've come to learn to read void* as any* even though void without the * means something else. Do not confuse a unit type with the top type. They are very different beasts.-Where `typeof(assert(0))`, being capable of representing any type possible without loss of information, would have infinite size (and thus be impossible), `sizeof(void) == 0`, so it is possible even in systems with no memory at all.I think you're confusing the bottom type with the top type here. :-) The bottom type has *no* values; it's the unique type that cannot represent *anything*. There can be no variables of this type, not because its values are too big, but because it has no values at all. A type that can represent any type is the top type, and it does not require infinite space: std.variant.Variant, for example, can be considered a top type, because it can hold values of any type. It does not require infinite space.-`void` variables should be valid syntax, that can be assigned (no-op at machine level) and compared (lowered to literal `true` by compiler) to each other. -Compiler would be free to decide any non-null address for a void variable. Since dereferencing a pointer to void will only result in void, which never uses any memory, it cannot do any harm.Uhm, a random non-null pointer value can do *lots* of harm, because being a systems language, D lets you cast pointers of one type to a pointer of a different type. Casting a pointer to void to a pointer to int, for example, will let you write to a random memory location. Very dangerous. T -- Democracy: The triumph of popularity over principle. -- C.Bond
Jan 17 2019
On Thursday, 17 January 2019 at 13:15:03 UTC, H. S. Teoh wrote:On Thu, Jan 17, 2019 at 10:35:00AM +0000, Dukc via Digitalmars-d wrote: [...]Oh yes. Stratch that.-Where no type is convertible from `typeof(assert(0))`, any type could be made convertible from `void`, the only possible value of void converting to target type's `.init` value.I think you got the conversion direction mixed up. If typeof(assert(0)) is supposed to be the bottom type, then it must implicitly convert to every other type (as Walter's DIP stipulates), but no other type could implicitly convert to it.
Jan 17 2019
On Thursday, 17 January 2019 at 00:09:50 UTC, H. S. Teoh wrote:So you see, `void` is a rats' nest of special cases and ad hoc monkey-patched semantics that's inconsistent with itself. Sometimes it behaves like a unit type, sometimes it behaves like a bottom type, and sometimes it behaves like a top type, and none of these usages are consistent with each other, nor are they truly consistent with themselves either. It's like working with a number system that has no concept of zero, negative numbers, or infinity, and then patching in a new number (let's call it X) that sometimes behaves like zero, sometimes like a negative number, and sometimes like infinity. Good luck doing calculations involving X.Noob question, but wouldn't a top type also be a unit type? I'm visualizing a top type as the type of an aggregate with zero member, which makes it a supertype of any other aggregate (whereas a bottom type would be the type of an aggregate with infinite members); wouldn't that type then have only one single value?
Jan 17 2019
On Thu, Jan 17, 2019 at 12:02:36PM +0000, Olivier FAURE via Digitalmars-d wrote: [...]Noob question, but wouldn't a top type also be a unit type?No! A unit type is one inhabited by a single value. A top type is a type that can represent *every* value. Very crudely speaking, if types were numbers, then a unit type is 1, a bottom type is 0, and a top type is infinity. Or, to use a set analogy, a unit type corresponds with a singleton set, a bottom type to the empty set (the subtype of every type), and the top type to the universal set (the supertype of every type). T -- Change is inevitable, except from a vending machine.
Jan 17 2019
On Thursday, 17 January 2019 at 12:40:18 UTC, H. S. Teoh wrote:No! A unit type is one inhabited by a single value. A top type is a type that can represent *every* value. Very crudely speaking, if types were numbers, then a unit type is 1, a bottom type is 0, and a top type is infinity. Or, to use a set analogy, a unit type corresponds with a singleton set, a bottom type to the empty set (the subtype of every type), and the top type to the universal set (the supertype of every type).I think I'm confused. Actually, I think I have a pretty good question. Let's imagine we implement two types in D, Unit and Top, so that they match their type theory concepts as best as they can. Given this code: int foo(Unit u) { ... } int bar(Top t) { ... } what could you do in `bar` that you couldn't do in `foo`? (I'd appreciate if you could answer with pseudocode examples, to help communication)
Jan 17 2019
On Thursday, 17 January 2019 at 13:20:20 UTC, Olivier FAURE wrote:On Thursday, 17 January 2019 at 12:40:18 UTC, H. S. Teoh wrote:It's the other way around--there are things you can do in `foo` that you can't do in `bar`. For example, you can compare two instances of Unit using `==` (and in fact such a comparison will always evaluate to true, since all instances of Unit have, by definition, the same value). However, you cannot compare two instances of Top using `==`, because there are types in D that have ` disable opEquals`, and Top is a supertype of those types.No! A unit type is one inhabited by a single value. A top type is a type that can represent *every* value. Very crudely speaking, if types were numbers, then a unit type is 1, a bottom type is 0, and a top type is infinity. Or, to use a set analogy, a unit type corresponds with a singleton set, a bottom type to the empty set (the subtype of every type), and the top type to the universal set (the supertype of every type).I think I'm confused. Actually, I think I have a pretty good question. Let's imagine we implement two types in D, Unit and Top, so that they match their type theory concepts as best as they can. Given this code: int foo(Unit u) { ... } int bar(Top t) { ... } what could you do in `bar` that you couldn't do in `foo`? (I'd appreciate if you could answer with pseudocode examples, to help communication)
Jan 17 2019
On Thu, 17 Jan 2019 13:20:20 +0000, Olivier FAURE wrote:Actually, I think I have a pretty good question. Let's imagine we implement two types in D, Unit and Top, so that they match their type theory concepts as best as they can. Given this code: int foo(Unit u) { ... } int bar(Top t) { ... } what could you do in `bar` that you couldn't do in `foo`?bar knows nothing about the value passed in. It's pretty much equivalent to taking a Variant. Or, if you're only dealing with OOP, it's like taking Object. It could do anything with that value, but it has to figure out what type it's dealing with first, then down-cast it. foo knows the exact value that will be passed to it because there's only one possibility. It can't do anything with that value at runtime that it couldn't do at compile-time, so the whole function could be reduced to a constant (assuming it's pure), or the argument could be omitted. Kind of like taking a CheckedInt where min and max are both 0.
Jan 17 2019
On Thu, Jan 17, 2019 at 01:20:20PM +0000, Olivier FAURE via Digitalmars-d wrote: [...]I think I'm confused. Actually, I think I have a pretty good question. Let's imagine we implement two types in D, Unit and Top, so that they match their type theory concepts as best as they can. Given this code: int foo(Unit u) { ... } int bar(Top t) { ... } what could you do in `bar` that you couldn't do in `foo`? (I'd appreciate if you could answer with pseudocode examples, to help communication)For one thing, Top would be the supertype of every other type, so you could pass in values of any type to bar: bar(123); bar("abc"); bar(null); bar(Unit.init); Whereas the only type that foo will accept is the unique value of Unit: foo(Unit.init); You can't pass anything else to foo, because the only value it accepts is the unique value of Unit, and nothing else. As to what you can *do* inside the functions: since Unit has only one value, every operation on it is vacuous: int foo(Unit u) { assert(u == u); // always true auto v = u; // basically no-op assert(is(typeof(v) == Unit)); ... return 0; // N.B.: cannot return u because Unit // does not convert to int (Unit is not // a subtype of int). } With Top, the value can literally be *any* type, which means there's also very little you can actually do with it, because you cannot assume anything about it -- the only assumptions you can make are those that are true for *all* types in the language. You could take its address, or assign a new value to it (of any type): int bar(Top t) { Top* p = &t; t = 123; t = "abc"; t = null; t = Unit.init; ... return 0; // again, cannot return t, because Top is not // a subtype of int (int is a subtype of // Top, though, but it doesn't go the // other way) } But as to actually operating on the value of t, there's really not much that can be done, since it encompasses too much, and there are very few operations that are common to *all* types. A Top* would be the same as today's void*, in that any pointer implicitly converts to it: int i = 123; float x = 1.0; string s = "abc"; Unit u; Top* ptr; ptr = &i; ptr = &x; ptr = &s; ptr = &u; Dereferencing ptr would give you a value of type Top, but then, barring further language support, you wouldn't know which underlying type it is, so there wouldn't be very much you could meaningfully do with it. This means that you can't meaningfully assign anything via ptr, for example: *ptr = 123; // error: not every type supports opAssign(int) *ptr = "abc"; // error: not every type supports opAssign(string) ... // etc. Which ultimately makes sense, because we could have gotten ptr from taking the address of a float, for example, and it would be UB to assign a string to *ptr. You could cast the Top* to something else, but that gets into the realm of implementation-defined behaviour (it could be just UB), and no longer something under the auspices of type theory. Basically, Top behaves more-or-less like std.variant.Variant, maybe excepting some corner cases. T -- People walk. Computers run.
Jan 17 2019
On Thursday, 17 January 2019 at 17:53:10 UTC, H. S. Teoh wrote:For one thing, Top would be the supertype of every other type, so you could pass in values of any type to bar: bar(123); bar("abc"); bar(null); bar(Unit.init); Which ultimately makes sense, because we could have gotten ptr from taking the address of a float, for example, and it would be UB to assign a string to *ptr. You could cast the Top* to something else, but that gets into the realm of implementation-defined behaviour (it could be just UB), and no longer something under the auspices of type theory.All these sound like things that would make sense with void? For instance, I think it would make sense to call a function `int foobar(void, void)` with as `foobar(1, "abd")`, which is basically the function saying it will ignore these parameters.
Jan 17 2019
On Thursday, 17 January 2019 at 18:38:54 UTC, Olivier FAURE wrote:For instance, I think it would make sense to call a function `int foobar(void, void)` with as `foobar(1, "abd")`, which is basically the function saying it will ignore these parameters.To expand on this, I'm understanding supertyping (at least in D and similar languages) as the *discarding* of type information. Eg, A is the supertype of B if B has more specific information about what values it can hold than A, which means A can accept more different values. In that mindset, void seems like the logical top type, because it describes no information at all about any value it could hold.
Jan 17 2019
On Thu, Jan 17, 2019 at 06:47:33PM +0000, Olivier FAURE via Digitalmars-d wrote:On Thursday, 17 January 2019 at 18:38:54 UTC, Olivier FAURE wrote:Think of Top as the analogue of D's Object class. Every class inherits from Object. Conversely, being handed an Object also gives you very little information about the underlying concrete class. Top would just be Object as applied to *all* types in the language.For instance, I think it would make sense to call a function `int foobar(void, void)` with as `foobar(1, "abd")`, which is basically the function saying it will ignore these parameters.To expand on this, I'm understanding supertyping (at least in D and similar languages) as the *discarding* of type information. Eg, A is the supertype of B if B has more specific information about what values it can hold than A, which means A can accept more different values.In that mindset, void seems like the logical top type, because it describes no information at all about any value it could hold.As I've said, `void` is an overloaded keyword that has different meanings in different contexts. In the case of `void*`, it certainly behaves like a Top type -- void* can point to literally *anything*, but it also tells you basically nothing about the type it's pointing to. However, `void` as used in function return types means something else altogether. A void function is *not* the same as a function that returns Object, to use the OO analogy. A function that returns Object (resp. returns Top) is returning an actual object with actual values -- it just so happens that the function doesn't specify anything more specific about said value. A `void` function, in the sense that it's used in C/C++ and also D, is a function that does not return a meaningful value. In that sense, it's closer to a Unit type: the Unit type can only ever hold a single value, and since this value cannot be distinguished from any other value of the same type (there is only one value of this type, so different values do not exist), it conveys zero information and can basically be elided. I.e., the function can be said to "return nothing" or "does not return a value (but it still does return)". This is not the same thing as "function returns a value, but it doesn't tell you what you can do with this value". The distinction is subtle, but important. The problem is that we're so used to `void` as it is used in C/C++/D, that we're essentially working with a defective "arithmetic" where "1" and "infinity" are both regarded as "not a number", and so we have trouble telling them apart, even though their distinction is very important! Furthermore, `void` as used in variable declarations behaves like a Bottom type -- it's illegal to declare a variable of type Bottom because Bottom by definition does not have *any* values -- yet the existence of a variable implies that it holds a value of that type, which is a contradiction. Hence, a function declared to return Bottom is basically a function that never returns, because if it did, you'd have a value of type Bottom, which is impossible. D does not allow you to declare a variable of type `void`, but if indeed `void` were Top, then variables of type `void` ought to be allowed, and would behave like std.variant.Variant! The fact that `void` variables are not allowed implies that, in the context of variable declarations, `void` means Bottom. So you see, `void` means different things depending on the context it's used in. Sometimes it's Top, sometimes it's Unit, sometimes it's Bottom. Again, this is like the deficient arithmetic of the ancients: "zero" was not a number, "infinity" was not a number, and the square root of a negative number was also not a number, so all three get lumped together as "not a number". So you'd talk about X being "not a number" or NaN, and sometimes NaN means your calculation yielded a 0, sometimes NaN means your calculation yielded infinity, and sometimes NaN means your calculation tried to take the square root of a negative number. They are all lumped together under the label of NaN, but they are actually very different things. Confusing the different things for one another would lead to contradictions and other (seemingly) unsolvable problems. Similarly, confusing the various different meanings of `void` just because textually they are all written as v-o-i-d will eventually lead to trouble and inconsistencies. If we want to "fix" `void` in D, the first step would be to write these different usages differently, so that they are clearly distinct from each other. Stop calling them all "void"; call them by three distinct names so that we don't confuse ourselves as to what we actually mean. Then we can talk about completing the type system with top/bottom/unit types. // And BTW, it makes me very scared that people seem to think I'm some kind of expert on type theory, which I most certainly am not. I know what I know simply by googling on the subject and reading a few articles -- which anyone could do in a couple o' hours at the most. If *that* is enough to make me an "expert" relative to the majority of the forumites here, then I really have to wonder just how much hope we have of resolving this issue in any sane way that doesn't introduce even more problems. T -- Some days you win; most days you lose.
Jan 17 2019
Am 17.01.19 um 21:04 schrieb H. S. Teoh:The distinction is subtle, but important. The problem is that we're so used to `void` as it is used in C/C++/D, that we're essentially working with a defective "arithmetic" where "1" and "infinity" are both regarded as "not a number", and so we have trouble telling them apart, even though their distinction is very important!This is a great analogy, I need to keep that in mind!Furthermore, `void` as used in variable declarations behaves like a Bottom type -- it's illegal to declare a variable of type Bottom because Bottom by definition does not have *any* values -- yet the existence of a variable implies that it holds a value of that type, which is a contradiction. Hence, a function declared to return Bottom is basically a function that never returns, because if it did, you'd have a value of type Bottom, which is impossible. D does not allow you to declare a variable of type `void`, but if indeed `void` were Top, then variables of type `void` ought to be allowed, and would behave like std.variant.Variant! The fact that `void` variables are not allowed implies that, in the context of variable declarations, `void` means Bottom.I don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized. This can trivially be done by only allwoing it to be initialized by copying because whenever you have an expression of type Bottom to which you "initialize" a variable of type Bottom, that expression never returns and thus the initialization never actually takes place. This would still allow things like ``` Bottom var = assert(0); ``` and also ``` void fun(Bottom b) { // whatever, this function can never actually be called } void main() { fun(assert(0)); } ``` Why would anyone want this? Simple, you do not have to include a special case for the Bottom type in generic code.And BTW, it makes me very scared that people seem to think I'm some kind of expert on type theory, which I most certainly am not. I know what I know simply by googling on the subject and reading a few articles -- which anyone could do in a couple o' hours at the most. If *that* is enough to make me an "expert" relative to the majority of the forumites here, then I really have to wonder just how much hope we have of resolving this issue in any sane way that doesn't introduce even more problems.I totally agree. It is basically the same for me. I do have a strong mathematical background which might help, but all my knowledge about type theory is basically from google, wikipedia and a few blog posts.
Jan 17 2019
On Thu, 17 Jan 2019 21:48:52 +0100, Johannes Loher wrote:I don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized.Which runs into default-initialization, so that's not awesome. But the same is true of structs with ` disable this();` If we treated them the same, you'd still be able to write: TBottom var = void; doStuff(var); That's...not awesome.
Jan 17 2019
Am 17.01.19 um 22:29 schrieb Neia Neutuladh:On Thu, 17 Jan 2019 21:48:52 +0100, Johannes Loher wrote:Both default initialization and void initialization of variables of type Tbottom would need to be illegal. The _only_ way to "initialize" them would be by copying, which—as explained—means they are never actually initialized.I don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized.Which runs into default-initialization, so that's not awesome. But the same is true of structs with ` disable this();` If we treated them the same, you'd still be able to write: TBottom var = void; doStuff(var); That's...not awesome.
Jan 17 2019
Am 17.01.19 um 22:29 schrieb Neia Neutuladh:On Thu, 17 Jan 2019 21:48:52 +0100, Johannes Loher wrote:By the way, I think it is debatable whether variables of type Tbottom should be declarable or not. The inconsistencies (in particular no void initialization which every other type has) are indeed a reason to not allow this. But then again, this is also an inconsistency: All other types are declarable, except for void (which should also be fixed in my opinion). There are quite a few languages which allow declaration of variables of their bottom type in the manner I described. These include: - Rust - Kotlin - Flow - Haskell - Swift On the other hand, I don't know of any language which prevents this. Do you know any?I don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized.Which runs into default-initialization, so that's not awesome. But the same is true of structs with ` disable this();` If we treated them the same, you'd still be able to write: TBottom var = void; doStuff(var); That's...not awesome.
Jan 17 2019
On Thu, 17 Jan 2019 22:56:00 +0100, Johannes Loher wrote:There are quite a few languages which allow declaration of variables of their bottom type in the manner I described. These include: - Rust - Kotlin - Flow - Haskell - Swift On the other hand, I don't know of any language which prevents this. Do you know any?C and C++ don't care if you use something before initializing it. Most languages explicitly forbid it, and Haskell particularly doesn't let you declare something before initializing it. So in order to use a variable of the bottom type, you must first assign it somehow. That means calling a function that returns the bottom type.
Jan 17 2019
On Thu, Jan 17, 2019 at 09:29:53PM +0000, Neia Neutuladh via Digitalmars-d wrote:On Thu, 17 Jan 2019 21:48:52 +0100, Johannes Loher wrote:It looks that way, but actually it totally makes sense. It only looks insane because we're thinking of it as a standalone, explicit function. But it's a lot less insane if you think of generic code that take arbitrary type parameters, e.g.: void func(T)(bool choice) { if (choice) { T var; doStuff(var); } } In this case, it's an asymmetry if func doesn't compile when T == Bottom. The writer of func would have to use sig constraints or static if to specifically check for Bottom. If instead we allow the declaration of `Bottom var;`, with the stipulation that it corresponds with the runtime code for halting the program (e.g., assert(0)), then the implementor of func wouldn't even have to think about the possibility of T == Bottom. When the user passes Bottom to func, the function naturally just aborts where var is declared. This then allows func to be used both for invoking doStuff if `choice` is true, and also, by passing T == Bottom, to *abort* the program if `choice` is true. This becomes fully transparent to the implementor of `func` -- you wouldn't need to special-case Bottom, and you wouldn't need to write another function specifically made to abort the program when choice == true. The same code would work for all cases. T -- Why can't you just be a nonconformist like everyone else? -- YHLI don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized.Which runs into default-initialization, so that's not awesome. But the same is true of structs with ` disable this();` If we treated them the same, you'd still be able to write: TBottom var = void; doStuff(var); That's...not awesome.
Jan 17 2019
On Thu, 17 Jan 2019 14:21:45 -0800, H. S. Teoh wrote:If instead we allow the declaration of `Bottom var;`, with the stipulation that it corresponds with the runtime code for halting the program (e.g., assert(0)), then the implementor of func wouldn't even have to think about the possibility of T == Bottom. When the user passes Bottom to func, the function naturally just aborts where var is declared.This is also an asymmetry: the following two functions abort at different places only when passed TBottom. In fact, they only abort when passed TBottom. void foo1(T)() { writeln("line 1"); T value = void; writeln("line 2"); } void foo2(T)() { T value = void; writeln("line 1"); writeln("line 2"); }
Jan 17 2019
On Thu, Jan 17, 2019 at 09:48:52PM +0100, Johannes Loher via Digitalmars-d wrote: [...]I don't actually think declaring a variable of the Bottom type is required to be illegal. It just needs to be impossible for it to ever be initialized. This can trivially be done by only allwoing it to be initialized by copying because whenever you have an expression of type Bottom to which you "initialize" a variable of type Bottom, that expression never returns and thus the initialization never actually takes place.That's an interesting take on Bottom. I think that makes sense. In D, we have default initialization, so a declaration like: Bottom b; implicitly means (in pseudocode): Bottom b = void; // (yikes, another meaning of `void`) b = b.init; Since b.init does not exist, this would result in a compile error. However, if you initialized it with an expression of type Bottom, then it would "work": Bottom func() {...} Bottom b = func(); This is OK, because func() never returns (by definition), so the assignment to b never takes place. Which also means the compiler can simply elide b from any stack-allocation code and skip emitting code for the assignment, etc., because it is unreachable code. All subsequent code would also be unreachable and can be elided.This would still allow things like ``` Bottom var = assert(0); ```Yes. Though *why* you'd want to declare a variable of type Bottom -- as opposed to just writing the expression of type Bottom -- is a good question that I don't have a good answer to.and also ``` void fun(Bottom b) { // whatever, this function can never actually be called }[...] This is an interesting one. IOW, the parameter Bottom b is basically equivalent to "this function is never reached". So it can be completely elided from codegen? This would let you write strange things like: fun(assert(0)); though it does make one wonder why write `fun` at all instead of just `assert(0)` directly. But on second thoughts... something like this could be extremely useful in generic code. For example, you could have a generic function that invokes a user callback and returns the value returned by the callback. You could then pass in a function that returns Bottom (i.e., it never terminates), then the generic function will automatically be inferred to be of type Bottom, and the compiler could stop codegen as soon as it encounters the first expression of type Bottom -- we get non-termination analysis for free, as a natural consequence of the type system! In fact, declaring a variable of type Bottom then becomes a synonym for writing assert(0). Generic code can then freely operate on any input type T, and if T == Bottom, then the type system automatically performs reachability analysis for us, and the codegen will only need to go as far as the first occurrence of Bottom. It can then just emit hlt and exit codegen for that function. It wouldn't matter what expression Bottom was found in: it could be nested somewhere inside a complex expression, and the type system automatically figures out that the type is Bottom and therefore codegen can just stop emitting code at that point. There would be no need to special-case checking for assert(0) to elide unreachable code, and no need for the template code to use static if to check for Bottom. It just falls out of the type system naturally. Even better yet, this means we don't even need to make it an error to declare and initialize a variable of type Bottom: the initialization can just be defined to be `assert(0);`, i.e.: Bottom b; becomes equivalent to (in pseudo-code): Bottom b = void; b = assert(0); which simplifies to just: assert(0); The unreachability then just naturally falls out of the type system. The compiler can compile the function as normal, as though we were actually initializing a variable of type Bottom, then during codegen the optimizer sees the `assert(0);` and elides everything else that follows it. At runtime, this would just abort at the point where b is initialized. This also lets us write sanity-checking expressions such as: float sqrt(float x) { return (x >= 0.0) ? ... /* implementation here */ : Bottom.init; } This is valid since Bottom converts to any type. Then at runtime, if x < 0.0, assert(0) gets triggered. There would be no need to write an if-statement that explicitly calls assert(0). Nice, well-rounded-out semantics. T -- Those who don't understand Unix are condemned to reinvent it, poorly.
Jan 17 2019
Am 17.01.19 um 23:13 schrieb H. S. Teoh:Even better yet, this means we don't even need to make it an error to declare and initialize a variable of type Bottom: the initialization can just be defined to be `assert(0);`, i.e.: Bottom b; becomes equivalent to (in pseudo-code): Bottom b = void; b = assert(0); which simplifies to just: assert(0);I didn't actually think this far, but you are right! We'd still somehow need to deal with void initialization though. In particular, what does this mean: ``` Tbottom fun() { Tbottom b = void; return b; } ``` ? With regular types, void initialization is no problem from a type system perspective because the memory the variable uses always holds some data which can be interpreted as data of that type. This is not true for Tbottom because there can not be any data at all. So I think we would still need to make (at least explicit) void initialization an error.
Jan 17 2019
On Thu, Jan 17, 2019 at 11:32:42PM +0100, Johannes Loher via Digitalmars-d wrote: [...][...] In particular, what does this mean: ``` Tbottom fun() { Tbottom b = void; return b; } ``` ? With regular types, void initialization is no problem from a type system perspective because the memory the variable uses always holds some data which can be interpreted as data of that type. This is not true for Tbottom because there can not be any data at all. So I think we would still need to make (at least explicit) void initialization an error.Hmm that's a good question. I was going to suggest that the very act of declaring a variable of type Bottom should be defined to be equivalent to writing `assert(0)`, but then it wouldn't have the correct semantics when you write: Bottom b = func(); // func returns Bottom since it would abort before calling func(), which is wrong. So it looks like we'll have no choice but to special-case void-initialization of Bottom to be illegal, much as I don't like special cases. It does lead to other corner cases like structs that contain Bottom fields -- instantiating such a struct should abort at runtime, and void initialization should be illegal -- so this special case is infectious and may lead to increased compiler complexity. OTOH, maybe this complexity can be nipped in the bud by stipulating that any aggregate that contains Bottom reduces to Bottom, i.e., any struct or class that contains a Bottom field will be defined to be Bottom itself (since it would be impossible to create an instance of such a struct or class without also creating an instance of Bottom). A particularly interesting (in a nasty way) case is a union that contains Bottom fields. How would you define its semantics? Since technically, if you only assign to its non-Bottom components, you aren't actually creating an instance of Bottom. But since the field is there, you can access it anytime. I'm tempted to say that such a union should also reduce to Bottom -- since one could argue that the Bottom field already exists in the union, even if it's uninitialized. This could fall under the auspices of the no-void-initialization rule. But there might be a type-theoretic issue here, though: unions being (approximately) a sum type, one would expect that it's legal to take a sum of Bottom with other types: since Bottom is uninhabited, the sum of Bottom with any other type should reduce to the other type (rather than Bottom itself!). Then again, D unions aren't really the same thing as a sum type in type theory AFAICT, so this may not be saying very much. But it's a tricky case that needs to be addressed before we start adding Bottom to the language. T -- "I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swr
Jan 17 2019
Am 18.01.19 um 00:05 schrieb H. S. Teoh:Hmm that's a good question. I was going to suggest that the very act of declaring a variable of type Bottom should be defined to be equivalent to writing `assert(0)`, but then it wouldn't have the correct semantics when you write: Bottom b = func(); // func returns Bottom since it would abort before calling func(), which is wrong.What we can do is simply replace this by ``` func(); assert(0); ```So it looks like we'll have no choice but to special-case void-initialization of Bottom to be illegal, much as I don't like special cases. It does lead to other corner cases like structs that contain Bottom fields -- instantiating such a struct should abort at runtime, and void initialization should be illegal -- so this special case is infectious and may lead to increased compiler complexity. OTOH, maybe this complexity can be nipped in the bud by stipulating that any aggregate that contains Bottom reduces to Bottom, i.e., any struct or class that contains a Bottom field will be defined to be Bottom itself (since it would be impossible to create an instance of such a struct or class without also creating an instance of Bottom).Structs with a bottom type member lowering to the bottom type member is the natural thing to do. Structs are product types and the product of any type with the bottom type is the bottom type.A particularly interesting (in a nasty way) case is a union that contains Bottom fields. How would you define its semantics? Since technically, if you only assign to its non-Bottom components, you aren't actually creating an instance of Bottom. But since the field is there, you can access it anytime. I'm tempted to say that such a union should also reduce to Bottom -- since one could argue that the Bottom field already exists in the union, even if it's uninitialized. This could fall under the auspices of the no-void-initialization rule. But there might be a type-theoretic issue here, though: unions being (approximately) a sum type, one would expect that it's legal to take a sum of Bottom with other types: since Bottom is uninhabited, the sum of Bottom with any other type should reduce to the other type (rather than Bottom itself!). Then again, D unions aren't really the same thing as a sum type in type theory AFAICT, so this may not be saying very much. But it's a tricky case that needs to be addressed before we start adding Bottom to the language.As far as I am concerned, unions are sum types, you just have no option to actually to find out which type they actually hold at runtime. While this is a feature that is very useful and often implied when talking about sum types (e.g. you can do it with std.variant.Algebraic), I donÄt think it is stricly necessary. This means that as you mentioned, any union type with a bottom type member should "lower" to the union type without the bottom type member. Actually trying to access it could either be a compile error or lower to `assert(0)`. I am not completely sure what is preferrable, it probably also depends on what would happen in other cases of usage of the bottom type (compile error or assert(0);). It should be as consistent as possible. There is a slightly weird situation with the default initialization of unions. Usually, the first member of the union is initialized in that case. however, in the case of ``` union A { Tbottom i; long j; } ``` this would mean that doing `auto A = A();` basically lowers to `assert(0);` while for ``` union A { long j; Tbottom i; } ``` it would work fine and `j` would be initialized to `0`. This sounds weird at first, but it is consistent with how unions of other types work: ``` import std.stdio; union A { float i; long j; } union B { long j; float i; } void main() { writeln(A().j); // 2145386496 writeln(B().j); // 0 } ```
Jan 17 2019
On Fri, Jan 18, 2019 at 12:34:39AM +0100, Johannes Loher via Digitalmars-d wrote:Am 18.01.19 um 00:05 schrieb H. S. Teoh:Hmm you're right, in this case, we're not actually declaring b as a separate step from calling func(); we're actually *initializing* b with the return value of func(). Meaning that, in implementation, we're calling func() then initializing b with the return value. So that initialization would fall under the same case as declaring `Bottom b;`, defined to lower to `assert(0);`. [...]Hmm that's a good question. I was going to suggest that the very act of declaring a variable of type Bottom should be defined to be equivalent to writing `assert(0)`, but then it wouldn't have the correct semantics when you write: Bottom b = func(); // func returns Bottom since it would abort before calling func(), which is wrong.What we can do is simply replace this by ``` func(); assert(0); ```Structs with a bottom type member lowering to the bottom type member is the natural thing to do. Structs are product types and the product of any type with the bottom type is the bottom type.You're right, the product of anything with the bottom type is the bottom type. No special casing required here.Makes sense. But worthy of note, since it may not be immediately obvious to those of us less well-versed with type theory. :-D And definitely this needs to be stated clearly in the DIP, otherwise it will be overlooked and who knows what buggy behaviour would result.A particularly interesting (in a nasty way) case is a union that contains Bottom fields. How would you define its semantics? Since technically, if you only assign to its non-Bottom components, you aren't actually creating an instance of Bottom. But since the field is there, you can access it anytime. I'm tempted to say that such a union should also reduce to Bottom -- since one could argue that the Bottom field already exists in the union, even if it's uninitialized. This could fall under the auspices of the no-void-initialization rule. But there might be a type-theoretic issue here, though: unions being (approximately) a sum type, one would expect that it's legal to take a sum of Bottom with other types: since Bottom is uninhabited, the sum of Bottom with any other type should reduce to the other type (rather than Bottom itself!). Then again, D unions aren't really the same thing as a sum type in type theory AFAICT, so this may not be saying very much. But it's a tricky case that needs to be addressed before we start adding Bottom to the language.As far as I am concerned, unions are sum types, you just have no option to actually to find out which type they actually hold at runtime. While this is a feature that is very useful and often implied when talking about sum types (e.g. you can do it with std.variant.Algebraic), I dont think it is stricly necessary. This means that as you mentioned, any union type with a bottom type member should "lower" to the union type without the bottom type member.Actually trying to access it could either be a compile error or lower to `assert(0)`. I am not completely sure what is preferrable, it probably also depends on what would happen in other cases of usage of the bottom type (compile error or assert(0);). It should be as consistent as possible.Yeah I'm not sure what should happen either. But I think lowering to assert(0) makes sense. It's how I'd implement a function declared to return Bottom: Bottom func() { for(;;) { doStuff(); } return Bottom.init; // equivalent to assert(0); } In other words, in order for func to implement its declared return type of Bottom, it ought to return an instance of Bottom, and the construction of this impossible instance should abort the program. Which would be consistent with current practice of unreachable code being self-documented by inserting `assert(0);`.There is a slightly weird situation with the default initialization of unions. Usually, the first member of the union is initialized in that case. however, in the case of ``` union A { Tbottom i; long j; } ``` this would mean that doing `auto A = A();` basically lowers to `assert(0);` while for ``` union A { long j; Tbottom i; } ``` it would work fine and `j` would be initialized to `0`. This sounds weird at first, but it is consistent with how unions of other types work: ``` import std.stdio; union A { float i; long j; } union B { long j; float i; } void main() { writeln(A().j); // 2145386496 writeln(B().j); // 0 } ```Ick. This is one dark corner of D that I really dislike. But it makes sense, in its own way, given D's guarantee that .init must always exist and be well-defined. And the choice of initializing a union by its first member's .init is as good as any other choice, since we have to initialize it with *something*. Just that when TBottom is involved, the order of declaration suddenly can mean the difference between aborting and initializing with an innocuous value. Another case to consider: what should .typeid of Tbottom return? T -- Lottery: tax on the stupid. -- Slashdotter
Jan 17 2019
Am 18.01.19 um 00:53 schrieb H. S. Teoh:Another case to consider: what should .typeid of Tbottom return?Indeed an interesting question. Since `Tbottom` is a type like any other, it needs to return an instance of TypeInfo. The even more interesting question is what the methods should return. Take for example `TypeInfo.equals`. Because there are no values of type `Tbottom`, any two instances of type `Tbottom` are equal and the method should return true. On the other hand, by the same argument, any two instances of `Tbottom` are not equal and the method should return false. From a more pragmatic point of view: It does not really matter what this method returns. It does take pointers to void which internally will be cast to `Tbottom*` (becasue we are actually comparing `Tbottom`s) and then dereferencing it will simply abort the program (`null` dereferencing). I think it is similar for the other methods. Some simply return some information about the type, e.g. `talign` should return 0.
Jan 17 2019
On Fri, Jan 18, 2019 at 01:13:28AM +0100, Johannes Loher via Digitalmars-d wrote:Am 18.01.19 um 00:53 schrieb H. S. Teoh:Right.Another case to consider: what should .typeid of Tbottom return?Indeed an interesting question. Since `Tbottom` is a type like any other, it needs to return an instance of TypeInfo.The even more interesting question is what the methods should return. Take for example `TypeInfo.equals`. Because there are no values of type `Tbottom`, any two instances of type `Tbottom` are equal and the method should return true. On the other hand, by the same argument, any two instances of `Tbottom` are not equal and the method should return false.Actually, since there can be no instances of Tbottom, that in theory also means TypeInfo.equals should never get called. Which means its implementation should simply be `assert(0);`.From a more pragmatic point of view: It does not really matter what this method returns. It does take pointers to void which internally will be cast to `Tbottom*` (becasue we are actually comparing `Tbottom`s) and then dereferencing it will simply abort the program (`null` dereferencing).This would be consistent with the implementation of .equal being `assert(0);`.I think it is similar for the other methods. Some simply return some information about the type, e.g. `talign` should return 0.Yeah, .compares should also be `assert(0);`, not sure about .tsize: what's the size of something that doesn't exist? I'm tempted to say .tsize should abort, but then returning 0 doesn't seem amiss either (if the instance doesn't exist, it also does not take up any space). It would help generic code not abort needlessly if they're just querying type information without actually trying to create an instance. Certainly .swap should assert(0), .next should return null, and .initializer should assert(0). In fact, I wonder if making .initializer call assert(0) would be enough to implement the "initializing Tbottom == assert(0)" rule -- maybe even without the compiler specifically checking for this case! (Though compiler checking will probably still be necessary in order to emit sane error messages at compile-time rather than seemingly-unexplained aborts at runtime. But it could be enough for an MVP.) Then .flags... 0 I suppose? And .offTi: null; .destroy and .postblit: assert(0). .talign... dunno, I suppose 0 is as good a value as any other? No idea what .argTypes is supposed to do. .rtInfo should probably return some generic no-op information. Or should it assert(0)? Since I'd expect someone calling .rtInfo implies that they've somehow created an instance of Tbottom, which is impossible. Though it's hard to say if the GC may pre-initialize buffers or what-not based on traversing all TypeInfo's. Don't know. T -- If you think you are too small to make a difference, try sleeping in a closed room with a mosquito. -- Jan van Steenbergen
Jan 17 2019
Am 18.01.19 um 01:33 schrieb H. S. Teoh:.talign... dunno, I suppose 0 is as good a value as any other?This really made me laugh :D It should be consistent with `Tbottom.alignof` obviously. The DIP suggests 1 (although the DIP calls the property `alignsize`, which doesnt exist), but I am not sure if there is any thought behind that.No idea what .argTypes is supposed to do. .rtInfo should probably return some generic no-op information. Or should it assert(0)? Since I'd expect someone calling .rtInfo implies that they've somehow created an instance of Tbottom, which is impossible. Though it's hard to say if the GC may pre-initialize buffers or what-not based on traversing all TypeInfo's. Don't know.Unfortunately I also don't know enough make a judgement here. But I believe this is something enough other people can give pragmatic suggestions for.
Jan 17 2019
On Fri, Jan 18, 2019 at 01:41:43AM +0100, Johannes Loher via Digitalmars-d wrote:Am 18.01.19 um 01:33 schrieb H. S. Teoh:[...] Sure, but what's the alignment of an instance that doesn't (and can't) exist? A "nonsensical" value like 0 might not be unreasonable, since you can't actually align any instances of Tbottom, there being none of them to begin with. T -- It said to install Windows 2000 or better, so I installed Linux instead..talign... dunno, I suppose 0 is as good a value as any other?This really made me laugh :D It should be consistent with `Tbottom.alignof` obviously. The DIP suggests 1 (although the DIP calls the property `alignsize`, which doesnt exist), but I am not sure if there is any thought behind that.
Jan 17 2019
On Thursday, 17 January 2019 at 23:05:58 UTC, H. S. Teoh wrote:It does lead to other corner cases like structs that contain Bottom fields -- instantiating such a struct should abort at runtime, and void initialization should be illegal -- so this special case is infectious and may lead to increased compiler complexity. OTOH, maybe this complexity can be nipped in the bud by stipulating that any aggregate that contains Bottom reduces to Bottom, i.e., any struct or class that contains a Bottom field will be defined to be Bottom itself (since it would be impossible to create an instance of such a struct or class without also creating an instance of Bottom).I think this solution would also create its own corner cases. For instance, if you were to write: auto callEach(Functions...)(Functions functions) { ReturnTypesOf!Functions returnValues; static forearch(i, f; functions) { returnValues[i] = f(); } return returnValues; } // Later... auto handleError = () => throw SomeException; callEach(&foo, &bar, &handleError); The function would abort near the beginning with no error message, instead of throwing when handleError is called. I think there are two solutions to this problem: - Forbid any aggregate to include a Bottom type, which would severely limit Bottom's use as a first-class type. - Rework the type system to gracefully handle types with no .init value. In the second case, Bottom, Bottom[10] and struct Foobar { Bottom x; } would all be legal, distinct types. Their only common point would be that they would all have a "no .init" trait.
Jan 21 2019
On Thursday, 17 January 2019 at 22:13:13 UTC, H. S. Teoh wrote:[...] This also lets us write sanity-checking expressions such as: float sqrt(float x) { return (x >= 0.0) ? ... /* implementation here */ : Bottom.init; } This is valid since Bottom converts to any type. Then at runtime, if x < 0.0, assert(0) gets triggered.While unrelated to the DIP discussion, I will insist that x >= 0.0 and x < 0.0 can both be wrong: x can be NaN.
Jan 17 2019
On Fri, Jan 18, 2019 at 01:04:58AM +0000, Q. Schroll via Digitalmars-d wrote:On Thursday, 17 January 2019 at 22:13:13 UTC, H. S. Teoh wrote:`NaN >= 0.0` evaluates to false, so the assert will still trigger. T -- English is useful because it is a mess. Since English is a mess, it maps well onto the problem space, which is also a mess, which we call reality. Similarly, Perl was designed to be a mess, though in the nicest of all possible ways. -- Larry Wall[...] This also lets us write sanity-checking expressions such as: float sqrt(float x) { return (x >= 0.0) ? ... /* implementation here */ : Bottom.init; } This is valid since Bottom converts to any type. Then at runtime, if x < 0.0, assert(0) gets triggered.While unrelated to the DIP discussion, I will insist that x >= 0.0 and x < 0.0 can both be wrong: x can be NaN.
Jan 17 2019
On 1/17/2019 2:13 PM, H. S. Teoh wrote:Nice, well-rounded-out semantics.I'm loving this discussion!
Jan 17 2019
On Friday, 18 January 2019 at 01:46:10 UTC, Walter Bright wrote:On 1/17/2019 2:13 PM, H. S. Teoh wrote:This is draft-review level discussion, not final review discussion.Nice, well-rounded-out semantics.I'm loving this discussion!
Jan 17 2019
H. S. Teoh <hsteoh quickfur.ath.cx> wrote:On Thu, Jan 17, 2019 at 09:48:52PM +0100, Johannes Loher via Digitalmars-d wrote: [...]That could be useful in generic code that is instatiated with 'Bottom' TobiThis would still allow things like ``` Bottom var = assert(0); ```Yes. Though *why* you'd want to declare a variable of type Bottom -- as opposed to just writing the expression of type Bottom -- is a good question that I don't have a good answer to. [...]
Jan 20 2019
Am 17.01.19 um 19:47 schrieb Olivier FAURE:On Thursday, 17 January 2019 at 18:38:54 UTC, Olivier FAURE wrote:I believe this view of things results directly from the fact that there are different meanings to `void`. In particular, `void*` is a pointer to _any_ type, so it is the "top type of all pointer types". If you think of `void` in the same way, then yes, `void` would be the top type. However this is not the way `void` works. At the moment `void` signals "no information" which in type theory is the unit type's responsibility. Because think about it: Which values should a type that cannot convey any information hold? Exactly one, otherwise you can have at least two different values which you can tell apart by looking at them (printing them, or whatever else you want to do with them). It is true that a top type can hold values of any type, so naively you'd think that you loose the original type information and thus it is basically the same as "no information". But this is not true because the actual values can still be different at runtime. Also usually a major feature of a top type is that you can check at runtime what the original type of the variable it holds is. For an example, take a look std.Variant. It has a `type` property which returns the typeid of the type it currently holds. (Note that this is not the case for `void*`: Without remembering, you can't actually know what type of data a variable of type `void*` points to.) If we were to fix `void`, we would need to let go of one of those views as they are inherently incompatible. One option would indeed be to make `void` a top type. But I don't think that makes much sense because then we would have to change the meaning of returning `void` (it would then mean to return anything, not no information). In my opinion, the interpretation of `void` as "no information" is its _main_ interpretation and thus the correct way would be to preserve that meaning and get rid of the other incompatible meanings, which means that `void` would not a top type but a unit type. We could even go a completely different route and get rid of the symbol `void` alltogether in order to not give people who are used to C, C++, Java, D (and 1000s of other languages which have copied C's _bad_ usage of void) false ideas. The last idea is the route that Kotlin went: They don't have a `void` type. Their unit type is called `Unit`, their bottom type is called `Nothing` and their top type is called `Any`. The type `Any?` (optional any, a type that can hold a value of any type or `null`) is very similar to what our `void*` is at the moment. Similarly, `Nothing?` is basically the same as our `typeof(null)`. This is actually quite nice, because it does not result in any wrong expectations–those types simply don't exist in C, C++, Java etc. However, all of this does not consider at all how to get C-interoperability to work. Kotlin is able to work with Java, so maybe there is actually a chance of getting this to work (though they don't have to deal with `void*` which makes it a lot easier). Additionally it means massive breaking changes. Even if there is a way to actually make it work with C, I still fear that there is simply too much code breakage for all of this to ever become reality.For instance, I think it would make sense to call a function `int foobar(void, void)` with as `foobar(1, "abd")`, which is basically the function saying it will ignore these parameters.To expand on this, I'm understanding supertyping (at least in D and similar languages) as the *discarding* of type information. Eg, A is the supertype of B if B has more specific information about what values it can hold than A, which means A can accept more different values. In that mindset, void seems like the logical top type, because it describes no information at all about any value it could hold.
Jan 17 2019
On Wednesday, 16 January 2019 at 22:32:33 UTC, Walter Bright wrote:The thing is, I know little about formal type theory. As far as I can tell, nobody in this community does either, aside from Timon Gehr.I shows, the rationale for this DIP as it stands is wafer thin. You should try to recruit Timon as a co-author and send the whole thing back to draft. There is zero reason this for the DIP to have got this far through the process.Back to the bottom type. C's ability to manipulate types is so primitive nobody misses type calculus. But D can do type calculus, and I worry that the lack of a bottom type, far from creating corner cases, causes corner cases and awkward problems analogous to what the lack of an identity function causes.Please point me to the section of the DIP where it says this, 'cause I'm not seeing it. This is an interesting potential but needs to be discussed in the draft phase.The trouble is, I don't know enough about type theory to know if this is the case or not. I only know analogies to problems other systems have when the boundary cases are not expressible.You need examples to give the DIP any credibility, as it stands the DIP makes two arguments : documentation and optimisation. Both of these exist already for LDC and GDC as attributes and it takes all of _6 lines+ to unify them and make the documentation aspect available to DMD.An example of an awkward corner case caused by lack of a bottom type: noreturn int betty(); How can it return an int yet not return? It makes no sense.Yes that is stupid. Does anyone write it now, given that it is already possible with LDC/GDC? No, because D is a pragmatic language, just because you can write something non-sensical doesn't mean that anyone will, especially when you have to go out of your way to do it. _We shouldn't be designing around the fact that you can do stupid things_.And if one was doing type calculus on the return value of betty(), it would show up as 'int' and botch everything up.First of all, the return type would canonically be void, not int (if you've gone out of your way to do something stupid its your fault). Second, I suspect the frequency of functions that always never return are a minute fraction of the total functions of a program, who is . Third even if bottom coverts to everything else functions of bottom won't, so, from what I can see that breaks a lot of its supposed advantages anyway. Finally, the addition of bottom its going to botch up a whole lot of existing code that isn't expecting it/doesn't care about it.The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem.Links please?My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out! (I.e. it's not just about functions that don't return. That's just an obvious benefit.)That DIP doesn't make that point at all! If you don't know what you're talking about, you should find out _before_ you waste everyones time reviewing it! This whole DIP needs to be sent back to draft. You need to come up with convincing ideas as to why a type is _practically_ more useful than an attribute that takes into account the fact that the claimed benefits already exist and that the implementation, learning curve and other external costs don't outweigh doing it.
Jan 16 2019
On Thu, Jan 17, 2019 at 12:44:32AM +0000, Nicholas Wilson via Digitalmars-d wrote:On Wednesday, 16 January 2019 at 22:32:33 UTC, Walter Bright wrote:[...]Actually, there *are* some cases where people might try to write something like this (not saying it's a good idea, but it's not as crazy as it might sound at first). To wit: // thirdpartylib.d class Base { int method() { ... } } // usercode.d class Derived : Base { override int method() { throw new Exception("Not implemented yet"); } } You cannot write Derived.method as void, because then it wouldn't override Base.method. [...]An example of an awkward corner case caused by lack of a bottom type: noreturn int betty(); How can it return an int yet not return? It makes no sense.Yes that is stupid. Does anyone write it now, given that it is already possible with LDC/GDC? No, because D is a pragmatic language, just because you can write something non-sensical doesn't mean that anyone will, especially when you have to go out of your way to do it.Yeah, how did this DIP get so far when it's standing on such shaky foundations? Shouldn't somebody of Timon's calibre have been recruited to iron out the kinks before "final review"? Actually, this should have been done for the *initial* draft. This DIP as it stands is far too crude and incomplete to be this far along in the process. T -- Tech-savvy: euphemism for nerdy.My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out! (I.e. it's not just about functions that don't return. That's just an obvious benefit.)That DIP doesn't make that point at all! If you don't know what you're talking about, you should find out _before_ you waste everyones time reviewing it! This whole DIP needs to be sent back to draft. You need to come up with convincing ideas as to why a type is _practically_ more useful than an attribute that takes into account the fact that the claimed benefits already exist and that the implementation, learning curve and other external costs don't outweigh doing it.
Jan 16 2019
On 1/16/2019 5:21 PM, H. S. Teoh wrote:Yeah, how did this DIP get so far when it's standing on such shaky foundations? Shouldn't somebody of Timon's calibre have been recruited to iron out the kinks before "final review"?The idea originally came up here: https://digitalmars.com/d/archives/digitalmars/D/proposed_noreturn_attribute_303875.html Timon was asked: https://digitalmars.com/d/archives/digitalmars/D/proposed_noreturn_attribute_303875.html There's a fair amount of good commentary from Timon about it, he's obviously the right person to write the DIP.
Jan 16 2019
On 1/16/2019 5:21 PM, H. S. Teoh wrote:You cannot write Derived.method as void, because then it wouldn't override Base.method.Right. The language allowing one to write silly things is one thing, but requiring it in certain cases has a certain stench about it. I'm reminded of this: https://brevzin.github.io/c++/2019/01/15/if-constexpr-isnt-broken/ where the author notes that C++ requires silly things like "requires requires" and ".template". As far as allowing the programmer to write stupid things, and relying on telling him to not do stupid things, endless experience shows that doesn't work very well. It's better that the language head off stupid things by design. For example, a < b < c is a valid C expression, but stupid, because it doesn't do what you might think it does. D does not allow such expressions, so we don't have to add to the style guide "don't do that". Of course, no language can stop every nut behind the wheel, but we're obliged to do what we can.
Jan 17 2019
On Thursday, 17 January 2019 at 00:44:32 UTC, Nicholas Wilson wrote:That DIP doesn't make that point at all! If you don't know what you're talking about, you should find out _before_ you waste everyones time reviewing it!IMO, this tone is uncalled for. Let's all try to be civil and criticize the DIP, not the person.
Jan 16 2019
On Thursday, 17 January 2019 at 03:30:08 UTC, Meta wrote:IMO, this tone is uncalled for. Let's all try to be civil and criticize the DIP, not the person.That is a criticism of the DIP and the way the DIP has been handled. The progression of this DIP throughout the stages of review, in spite of a lack of due diligence of research and response to criticisms, ignores the opportunity cost of reviewing higher impact and review ready DIPs, like the copy constructor or template constraint DIPs or any other DIP that is _actually ready_ for review.
Jan 17 2019
On Thursday, 17 January 2019 at 13:36:07 UTC, Nicholas Wilson wrote:The progression of this DIP throughout the stages of review, in spite of a lack of due diligence of research and response to criticisms, ignores the opportunity cost of reviewing higher impact and review ready DIPs, like the copy constructor or template constraint DIPs or any other DIP that is _actually ready_ for review.Agreed. But with all that said, it's still more constructive to be polite. I wouldn't say any specific paragraph of your post was blatantly rude, condescending or uncalled for, but you could have made the same points while using a less aggressive, scathing tone, and I think a lot of discussions on the D Forums would benefit from that.
Jan 17 2019
Am 17.01.19 um 01:44 schrieb Nicholas Wilson:Yes that is stupid. Does anyone write it now, given that it is already possible with LDC/GDC? No, because D is a pragmatic language, just because you can write something non-sensical doesn't mean that anyone will, especially when you have to go out of your way to do it. _We shouldn't be designing around the fact that you can do stupid things_.I don't completely agree with this statement. If possible, as language authors we should try to design the language in such a way that it is impossible (or at least very hard) to do stupid things with it. Of course we also need to empower the user, but one has to find a good balance. If something is possible in the language, people will do it, however stupid it is. The best example for this is what Walter always brings up: ``` #define BEGIN { #define END } ``` I am very happy that this is not possible in D!
Jan 16 2019
Am 16.01.19 um 23:32 schrieb Walter Bright:> [...]The trouble with 'void' as a bottom type is it is only half done. For example, 'void' functions are procedures, and certainly do return. A void* is certainly a pointer to something, not nothing. 'void' is the one with awkward corner cases, it's just we're so used to them we don't see them, like few C++ people recognized the identity function problem. [...]In a later post, H. S. Theo describes some of the problems with void in more detail. What is your stance on actually fixing void from a type theoretical perspective? Do you think that is practical?
Jan 16 2019
On 1/16/2019 8:52 PM, Johannes Loher wrote:In a later post, H. S. Theo describes some of the problems with void in more detail. What is your stance on actually fixing void from a type theoretical perspective? Do you think that is practical?Too much water under that bridge.
Jan 17 2019
On Thursday, 17 January 2019 at 09:24:26 UTC, Walter Bright wrote:On 1/16/2019 8:52 PM, Johannes Loher wrote:I suppose `bottom`, `top`, and `unit` types can be added to the language while maintaining current `void` semantics. Then, in time, the uses of `void` masquerading as `bottom`, `top`, and `unit` will diminish to a point where it wouldn't be all that disruptive to deprecate it. This just needs someone with the skill and time to implement it, which is probably the real blocker. MikeIn a later post, H. S. Theo describes some of the problems with void in more detail. What is your stance on actually fixing void from a type theoretical perspective? Do you think that is practical?Too much water under that bridge.
Jan 18 2019
On Sat, Jan 19, 2019 at 03:40:57AM +0000, Mike Franklin via Digitalmars-d wrote:On Thursday, 17 January 2019 at 09:24:26 UTC, Walter Bright wrote:[...] There would have to be some incentive for people to stop writing `void` and `void*` and start writing `top*`, `bottom`, `unit`, though. Otherwise we'll just end up with 3 more keywords that nobody uses cluttering the language. T -- Long, long ago, the ancient Chinese invented a device that lets them see through walls. It was called the "window".On 1/16/2019 8:52 PM, Johannes Loher wrote:I suppose `bottom`, `top`, and `unit` types can be added to the language while maintaining current `void` semantics. Then, in time, the uses of `void` masquerading as `bottom`, `top`, and `unit` will diminish to a point where it wouldn't be all that disruptive to deprecate it. This just needs someone with the skill and time to implement it, which is probably the real blocker.In a later post, H. S. Theo describes some of the problems with void in more detail. What is your stance on actually fixing void from a type theoretical perspective? Do you think that is practical?Too much water under that bridge.
Jan 19 2019
Am 16.01.19 um 23:32 schrieb Walter Bright:My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out!I do actually think there is a lot of value in a bottom type. The problem is that–as you mention–nobody stepped up and helped you with the DIP, although in the reviews there has been a lot of substantiated (and constructive) critique by people who (judging from their comments) seem to know more about type theory than you. Maybe the proper way forward is to reject the DIP and the current form and try again later once you got some help to bring it in proper shape? As mentioned several times in this thread, there are quite a few other severe problems with D's type system (e.g. the problems with void, which H. S. Theo described). Maybe we should set up a working group (with at least some people who know a bit more about type theory) to tackle these problems (including a bottom type)?
Jan 16 2019
On Wednesday, 16 January 2019 at 22:32:33 UTC, Walter Bright wrote:My trouble explaining the immediate value of a bottom type stems from my poor knowledge of type theory. Other languages aren't necessarily good role models here, as few language designers seem to know much about type calculus, either. I was hoping that someone who does understand it would help out!
Jan 17 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review.I know we are no longer supposed to discuss the proposal's merits, but... The proposal does not describe its merits. The only merit listed is being able to specify that a function does not return. The obvious choice is adding a function attribute for that, yet the proposal introduces a new magic type with a whole set of new problems of its own and only has a few lines of text on why an attribute would not cut it. No rationale is given for: 1 - implicit conversion of Tbottom to other types 2 - being able to use a noreturn function in expressions `a || b`, `a && b`, `a ? b : c` 3 - why a new _type_ is needed for describing a property of a function (that's what attributes are for) 4 - D already has a bottom type: `void`, why is a new type needed? "A function that returns a Tbottom is covariant with a function that returns any type T if T is returned via the registers" The word "register" has no meaning here. "Register" is (correctly) not defined in the spec and some targets don't have registers. One line down the proposal mentions that this is implementation-defined, so it acknowledges that this sentence has no clear meaning. The proposal mentions the C std function `exit()`. With this proposal, we are still not able to directly call `exit()` and have the compiler understand that the function doesn't return. The declaration `extern(C) Tbottom exit();` won't mangle correctly, whereas `extern(C) void exit() noreturn` would. In other words: interfacing with C needs to be described in the proposal. In the discussion of the alternative ` noreturn`, it is claimed that "This has the awkward result of a function specifying it has a return type `T`, but never returns that type." Such functions would simply return `void`, which I don't find awkward at all: `void` is a bottom type after all. Then it is mentioned that with ` noreturn` "Other potential uses of a bottom type will not be expressible", but there is no rationale or full description of those other uses. How is the 'bottomness' of `Tbottom` different from `void`? I would have imagined this proposal to be completely different: describe why having a new bottom type is useful, and then in a small extra paragraph mentioning that this new bottom type can also be used to describe nonreturning functions. A big addition like this needs a big justification with a solution to a major shortcoming. Not being able to specify a function never returning is just a very minor shortcoming. -Johan
Jan 15 2019
On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:4 - D already has a bottom type: `void`, why is a new type needed?Because `void` is not a bottom type - a bottom type has no values, while void has exactly one, and is thus a unit type. Proof: you can return from a void function (hence it has at least one value), and no information is transmitted through a void (hence it has at most one value).The declaration `extern(C) Tbottom exit();` won't mangle correctlyFalse. C name mangling doesn't care about return types. -- Simen
Jan 15 2019
On Tuesday, 15 January 2019 at 11:18:20 UTC, Simen Kjærås wrote:For example llvm ir has its own type system that is checked, and when a function's return type is replaced with something different, it can cause type mismatch error.The declaration `extern(C) Tbottom exit();` won't mangle correctlyFalse. C name mangling doesn't care about return types.
Jan 15 2019
On Tuesday, 15 January 2019 at 11:18:20 UTC, Simen Kjærås wrote:On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:Although I am not a mathematician, this argumentation feels broken. The argumentation of "a function returning bottom can never return" assumes mathematical functions (which must return a value), but in D a "function" is not necessarily a mathematical function. A function is a procedure, and can return no value, described by "void". For example, this is not allowed: ``` void foo(); void g() { auto f = foo(); // not allowed, foo does not return any value } ``` Interestingly, the proposal defines `a || b()` to be OK when `b()` returns Tbottom, but does not change that it is not OK when `b()` returns `void`...4 - D already has a bottom type: `void`, why is a new type needed?Because `void` is not a bottom type - a bottom type has no values, while void has exactly one, and is thus a unit type. Proof: you can return from a void function (hence it has at least one value), and no information is transmitted through a void (hence it has at most one value).Indeed, thanks for the correction. -JohanThe declaration `extern(C) Tbottom exit();` won't mangle correctlyFalse. C name mangling doesn't care about return types.
Jan 15 2019
On 15.01.19 14:31, Johan Engelen wrote:On Tuesday, 15 January 2019 at 11:18:20 UTC, Simen Kjærås wrote:But procedures are not outside the reach of mathematics. A procedure is a mathematical function that takes a state together with some arguments and gives you back a modified state. Its type is State×Args→State. The syntactic sugar that imperative languages put on top of this does not make this any less true, it is just a distraction. A procedure that returns a value gives you both the modified state and a value. Its type is State×Args→State×Ret. If Ret has no values then State×Ret has no values. You have the following natural isomorphisms: State×Unit ≈ State and State×Bottom ≈ Bottom So if your D function returns Unit, it is essentially a procedure, and if it has return type Bottom, it automatically does not return a state either. I.e. it just does not return. The entire thing is completely uniform, there are no weird special cases.On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:Although I am not a mathematician, this argumentation feels broken. The argumentation of "a function returning bottom can never return" assumes mathematical functions (which must return a value), but in D a "function" is not necessarily a mathematical function. A function is a procedure,4 - D already has a bottom type: `void`, why is a new type needed?Because `void` is not a bottom type - a bottom type has no values, while void has exactly one, and is thus a unit type. Proof: you can return from a void function (hence it has at least one value), and no information is transmitted through a void (hence it has at most one value).and can return no value, described by "void". For example, this is not allowed: ``` void foo(); void g() { auto f = foo(); // not allowed, foo does not return any value } ``` ...But that's because the compiler explicitly checks for void. It's an unprincipled special case arising from the misunderstanding that void has no values. It makes little sense to define a type "void" that has "no values" and then say "but as a special case, a function with return type void is a procedure instead", because you can just have a unit type. The whole "variables cannot be of type void"-nonsense is also nothing but annoying. But of course you have a point. The only (and valid) reason to argue against the concept of a bottom type in D is that it tries to shoehorn a principled concept on top of the unprincipled and messy C legacy; as you said, you indeed lose compatibility, because C++ functions can both be annotated with [[noreturn]] and have a specified return type. Therefore, we might need both an attribute and a bottom type, such that the bottom return type automatically infers the attribute. You can also argue that putting only the attribute is "pragmatically" good enough, because it is needed anyway and "PL theory is useless" because you can just hack around non-uniformity with explicit special cases (like we have to do for void already).Interestingly, the proposal defines `a || b()` to be OK when `b()` returns Tbottom, but does not change that it is not OK when `b()` returns `void`...I don't get why it would. What boolean value would you assign to a void b()?
Jan 15 2019
On Tuesday, 15 January 2019 at 16:23:57 UTC, Timon Gehr wrote:[...] It makes little sense to define a type "void" that has "no values" and then say "but as a special case, a function with return type void is a procedure instead", because you can just have a unit type. The whole "variables cannot be of type void"-nonsense is also nothing but annoying.The void type is one of *three* type-theoretically different types: 1. For any variables (local, parameter, field, ...), it *is* the bottom type: You cannot have values of it. 2. For function returns, it is a unit type. In D, we have another one: typeof(null). In fact, we could deprecate void returning functions and use typeof(null) instead. 3. For pointer (i.e. void*) (and arrays (void[n]) and slices (void[])), void is neither of the above. It's rather a top type (or any, or unknown in TypeScript) which can hold any value. As it is with Object, you cannot do anything (meaningful) with it without explicit downcast. This is not new -- it's inherited from C. If we get a bottom type, void in the sense of 2. can be defined as void = bottom*.
Jan 16 2019
On 17.01.19 03:32, Q. Schroll wrote:On Tuesday, 15 January 2019 at 16:23:57 UTC, Timon Gehr wrote:No, you cannot have _variables_ of this type. There is nothing like this in type theory. If you can't have a variable of a certain "type", it's not a type.[...] It makes little sense to define a type "void" that has "no values" and then say "but as a special case, a function with return type void is a procedure instead", because you can just have a unit type. The whole "variables cannot be of type void"-nonsense is also nothing but annoying.The void type is one of *three* type-theoretically different types: 1. For any variables (local, parameter, field, ...), it *is* the bottom type: You cannot have values of it.2. For function returns, it is a unit type. In D, we have another one: typeof(null). In fact, we could deprecate void returning functions and use typeof(null) instead.No, that has different low-level semantics.3. For pointer (i.e. void*) (and arrays (void[n]) and slices (void[])), void is neither of the above. It's rather a top type (or any, or unknown in TypeScript) which can hold any value. As it is with Object, you cannot do anything (meaningful) with it without explicit downcast. ...No, this is the "untyped block of memory" usage of 'void'. This does not have a parallel in type theory because _untyped_ and only useful if used in an unsafe manner.This is not new -- it's inherited from C. If we get a bottom type, void in the sense of 2. can be defined as void = bottom*.See 2.
Jan 20 2019
On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:Not being able to specify a function never returning is just a very minor shortcoming. -Johan+1 for all this. And the name for this in other language is "no return"
Jan 15 2019
On Tue, 15 Jan 2019 10:59:40 +0000, Johan Engelen wrote:The proposal mentions the C std function `exit()`. With this proposal, we are still not able to directly call `exit()` and have the compiler understand that the function doesn't return. The declaration `extern(C) Tbottom exit();` won't mangle correctly, whereas `extern(C) void exit() noreturn` would. In other words: interfacing with C needs to be described in the proposal.C and C++ name mangling doesn't include return types for free functions, and that should be mentioned in the proposal. C++ does include return types for function pointers, though, so that's one area where this fails. The proposal should explicitly mention all this.In the discussion of the alternative ` noreturn`, it is claimed that "This has the awkward result of a function specifying it has a return type `T`, but never returns that type." Such functions would simply return `void`, which I don't find awkward at all: `void` is a bottom type after all.Or they will return a different type for structural reasons. For instance, I might use an external library that auto-exposes D functions to a scripting language but requires them to have a particular signature. Adding noreturn wouldn't break that signature but would allow the compiler to do better flow analysis.Then it is mentioned that with ` noreturn` "Other potential uses of a bottom type will not be expressible", but there is no rationale or full description of those other uses. How is the 'bottomness' of `Tbottom` different from `void`?void has one value you can only refer to implicitly and can't store in a variable. Tbottom has zero values, so it's even less usable than void.I would have imagined this proposal to be completely different: describe why having a new bottom type is useful, and then in a small extra paragraph mentioning that this new bottom type can also be used to describe nonreturning functions. A big addition like this needs a big justification with a solution to a major shortcoming. Not being able to specify a function never returning is just a very minor shortcoming.Except GDC and LDC both let you do this already, so it's only a shortcoming for DMD.
Jan 15 2019
On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:With all due respect to all involved in this conversation, this DIP should be almost entirely non-controversial, but it is not, because most people posting on this mailing list have a more practical programming background, rather than a theoretical one. When I say that, I am not excluding myself, so please don't interpret it as an insult. Let's look at Rust: they have introduced a bottom type, denoted as !, and to my knowledge it was almost entirely without controversy, because that community has a much stronger theoretical focus than D. I know that D is not Rust, but in this instance I feel that we could learn a thing or two rather than grafting on some noreturn hack (and it *is* a hack that just poorly emulates a bottom type). The bottom type is well-understood and has already had all the kinks worked out by academia. It is more a less a free lunch, because somebody else has already done the heavy intellectual lifting for us.DIP 1017, "Add Bottom Type", is now ready for Final Review.I know we are no longer supposed to discuss the proposal's merits, but... The proposal does not describe its merits. The only merit listed is being able to specify that a function does not return. The obvious choice is adding a function attribute for that, yet the proposal introduces a new magic type with a whole set of new problems of its own and only has a few lines of text on why an attribute would not cut it.No rationale is given for: 1 - implicit conversion of Tbottom to other types 2 - being able to use a noreturn function in expressions `a || b`, `a && b`, `a ? b : c`There are good reasons for why _|_ behaves this way; in type theory, it is the subtype of all other types and thus can implicitly convert to them with no type soundness issues. However, I agree that we do not necessarily _need_ this functionality, thought it would be annoying to only implement a half-working bottom type. It would also reflect badly on D IMO.3 - why a new _type_ is needed for describing a property of a function (that's what attributes are for) 4 - D already has a bottom type: `void`, why is a new type needed?`void` is a unit type, and is not the same as a bottom type. It does not denote that a function does not return a value; it denotes that the function can only return exactly _one_ value. The fact that most C family languages do not implement unit types correctly is probably why this misconception comes about (I had the same misconception for a long time)."A function that returns a Tbottom is covariant with a function<snip, no comment here>The proposal mentions the C std function `exit()`. With this<snip, already answered in this thread>In the discussion of the alternative ` noreturn`, it is claimed that "This has the awkward result of a function specifying it has a return type `T`, but never returns that type." Such functions would simply return `void`, which I don't find awkward at all: `void` is a bottom type after all.This is incorrect, as has been mentioned elsewhere in this thread. It's the difference between 0 and 1; the bottom type has 0 values, while unit types have exactly 1 value.Then it is mentioned that with ` noreturn` "Other potential uses of a bottom type will not be expressible", but there is no rationale or full description of those other uses. How is the 'bottomness' of `Tbottom` different from `void`?As stated above, Tbottom is the subtype of all types, while void is not. It's like asking how the number 0 is different from the number 1.I would have imagined this proposal to be completely different: describe why having a new bottom type is useful, and then in a small extra paragraph mentioning that this new bottom type can also be used to describe nonreturning functions.To me, that doesn't make any sense, as the bottom type is THE canonical way to express that a function will not return. It is a type with 0 values, thus, intuitively, there is no possible way a function with a return type of Tbottom could ever possibly return. Thus, it must throw an exception, loop forever, or abort the program.A big addition like this needs a big justification with a solution to a major shortcoming. Not being able to specify a function never returning is just a very minor shortcoming.I recommend reading up on what the bottom type is, and why it is useful in the context of a language's type system. Not only does it enable certain optimizations, it also has many other useful properties that we can take advantage of. Stack Overflow can explain it better than me: https://softwareengineering.stackexchange.com/questions/277197/is-there-a-reason-to-have-a-bottom-type-in-a-programming-language
Jan 15 2019
On Tuesday, 15 January 2019 at 18:26:55 UTC, Meta wrote:On Tuesday, 15 January 2019 at 10:59:40 UTC, Johan Engelen wrote:Indeed the whole D community could use more theory input, but the DIP too. However, D is also a practical language and with that constraint come compromises in how to apply theory. I am indeed out of my depth here, although I know what the bottom type is, but that doesn't mean the DIP shouldn't justify itself much more extensively (with the help of references).On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:With all due respect to all involved in this conversation, this DIP should be almost entirely non-controversial, but it is not, because most people posting on this mailing list have a more practical programming background, rather than a theoretical one. When I say that, I am not excluding myself, so please don't interpret it as an insult.DIP 1017, "Add Bottom Type", is now ready for Final Review.I know we are no longer supposed to discuss the proposal's merits, but... The proposal does not describe its merits. The only merit listed is being able to specify that a function does not return. The obvious choice is adding a function attribute for that, yet the proposal introduces a new magic type with a whole set of new problems of its own and only has a few lines of text on why an attribute would not cut it.Let's look at Rust: they have introduced a bottom type, denoted as !, and to my knowledge it was almost entirely without controversy, because that community has a much stronger theoretical focus than D.Rust is indeed interesting here because, as far as I understand, Rust used to have a bottom type but they removed it because it was causing too much trouble. So we'd need to learn from them why `!` is not a bottom type proper in Rust, and what justification they have for `!` being a not-quite-bottom type and how that applies to D.Yeah, but D's `void` is also not a proper unit type. D's `void` behaves somewhere in-between the theoretical bottom and unit types.4 - D already has a bottom type: `void`, why is a new type needed?`void` is a unit type, and is not the same as a bottom type.The link does not really have answers that explain the use of a bottom type in a practical imperative language, beyond that it signifies `noreturn`. The question we have is whether there is a compelling use for "bottom". I'd like to see a real code use case, besides signifying `noreturn` (trivial). Btw, you can still throw from such functions (at least that is what the DIP is proposing, e.g. `throw` itself), and the DIP's assertion that you can remove stack unwinding code from blocks calling a `noreturn`/bottom-return function is therefore false. Being able to specify `noreturn` is definitely useful. Do we really need to complicate the language for it? In case people are wondering, in LDC you can use ` (ldc.attributes.llvmAttr("noreturn"))`. Ugly, non-standard, yes :/ -JohanA big addition like this needs a big justification with a solution to a major shortcoming. Not being able to specify a function never returning is just a very minor shortcoming.I recommend reading up on what the bottom type is, and why it is useful in the context of a language's type system. Not only does it enable certain optimizations, it also has many other useful properties that we can take advantage of. Stack Overflow can explain it better than me: https://softwareengineering.stackexchange.com/questions/277197/is-there-a-reason-to-have-a-bottom-type-in-a-programming-language
Jan 15 2019
On Tuesday, 15 January 2019 at 22:44:56 UTC, Johan Engelen wrote:Being able to specify `noreturn` is definitely useful. Do we really need to complicate the language for it?IMO, no.In case people are wondering, in LDC you can use ` (ldc.attributes.llvmAttr("noreturn"))`. Ugly, non-standard, yes :/non-standardness can be fixed quite easily version (LDC) enum noreturn = ldc.attributes.llvmAttr("noreturn")); else version (GDC) enum noreturn = gcc.attribute.attribute("noreturn"); // ? else version (DigitalMars) enum noreturn; I think it fixes the ugliness too.
Jan 15 2019
On Tuesday, 15 January 2019 at 22:44:56 UTC, Johan Engelen wrote:The link does not really have answers that explain the use of a bottom type in a practical imperative language, beyond that it signifies `noreturn`. The question we have is whether there is a compelling use for "bottom". I'd like to see a real code use case, besides signifying `noreturn` (trivial).A very practical use case is with error code paths where you end up asserting/exiting the program, something like: int f(int i) { if (i) { exit(0); } else { return 0; } } D currently special cases assert(0) for that. But practically you can have function other than a special cased assert that never return. Without the bottom type I'd have to write "return make-up-a-number" and then if there's a bug in "exit" i could be returning make-up-a-number and using it and I'd never be the wiser.
Jan 15 2019
On Wednesday, 16 January 2019 at 04:20:42 UTC, aliak wrote:On Tuesday, 15 January 2019 at 22:44:56 UTC, Johan Engelen wrote:This is just another example of using the bottom type to signify `noreturn`, which is trivially done with an attribute and doesn't need a new type. I'd like to see _other_ practical uses of the bottom type. -JohanThe link does not really have answers that explain the use of a bottom type in a practical imperative language, beyond that it signifies `noreturn`. The question we have is whether there is a compelling use for "bottom". I'd like to see a real code use case, besides signifying `noreturn` (trivial).A very practical use case is with error code paths where you end up asserting/exiting the program, something like: int f(int i) { if (i) { exit(0); } else { return 0; } }
Jan 16 2019
On Wednesday, 16 January 2019 at 11:23:18 UTC, Johan Engelen wrote:This is just another example of using the bottom type to signify `noreturn`, which is trivially done with an attribute and doesn't need a new type. I'd like to see _other_ practical uses of the bottom type. -JohanHere's an example I ran into while working on my `expectations` library. [1] Internally, an Expected!(T, E) is represented as a discriminated union containing either an "expected" value of type T or an error value of type E. I would like to implement a property method, `value`, that either returns the expected value if there is one, or throws an exception if there isn't (similar to Rust's `unwrap`). The obvious implementation looks like this: struct Expected(T, E) { private SumType!(T, E) data; property T value() { return data.match!( (T val) => val, (E err) { throw new Exception(err.to!string); } ); } } However, this will not compile, because the second lambda is inferred to have a return type of void, and void is not implicitly convertible to T. Instead, I have to resort to the following ugly workaround: property T value() { try { return data.tryMatch!( (T val) => val ); } catch (MatchException _) { data.tryMatch!( (E err) => throw new Exception(err.to!string) ); } } If the second lambda were inferred to have a return type of Tbottom, none of this would be necessary. The first version would Just Work™. [1] http://expectations.dub.pm
Jan 16 2019
On Wednesday, 16 January 2019 at 23:08:38 UTC, Paul Backus wrote:On Wednesday, 16 January 2019 at 11:23:18 UTC, Johan Engelen wrote:As far as the DIP goes, it does not appear to make any mention of it working this way. It does say that the bottom type is implicitly convertible to any type, but being implicitly convertible does not mean that function types are also implicitly convertible. Just as float and int may be implicitly convertible, their functions are not. The only function types that appear to be implicitly convertible are those of class and their derived classes. This would mean that the bottom type function would need to be ABI compatible with every single function type. I'm not sure if that's possible, but I feel that would need to addressed in the DIP.This is just another example of using the bottom type to signify `noreturn`, which is trivially done with an attribute and doesn't need a new type. I'd like to see _other_ practical uses of the bottom type. -JohanHere's an example I ran into while working on my `expectations` library. [1] Internally, an Expected!(T, E) is represented as a discriminated union containing either an "expected" value of type T or an error value of type E. I would like to implement a property method, `value`, that either returns the expected value if there is one, or throws an exception if there isn't (similar to Rust's `unwrap`). The obvious implementation looks like this: struct Expected(T, E) { private SumType!(T, E) data; property T value() { return data.match!( (T val) => val, (E err) { throw new Exception(err.to!string); } ); } } However, this will not compile, because the second lambda is inferred to have a return type of void, and void is not implicitly convertible to T. Instead, I have to resort to the following ugly workaround: property T value() { try { return data.tryMatch!( (T val) => val ); } catch (MatchException _) { data.tryMatch!( (E err) => throw new Exception(err.to!string) ); } } If the second lambda were inferred to have a return type of Tbottom, none of this would be necessary. The first version would Just Work™. [1] http://expectations.dub.pm
Jan 16 2019
On Wednesday, 16 January 2019 at 23:32:22 UTC, luckoverthere wrote:On Wednesday, 16 January 2019 at 23:08:38 UTC, Paul Backus wrote:`match` is a template function whose return type is inferred from the lambdas passed to it (i.e., it returns `auto`). If an auto-returning function has multiple return statements that return values of different types, then the function's return type is inferred to be the common type to which all of those different types are implicitly convertible. You can see this for yourself by compiling the following example: auto fun(bool b) { if (b) return int.init; else return double.init; } import std.traits; pragma(msg, ReturnType!fun); // double It follows that if `match` is called with a lambda that returns Tbottom, and a lambda that returns T, its return type will be inferred to be T.Here's an example I ran into while working on my `expectations` library. [1] [...]As far as the DIP goes, it does not appear to make any mention of it working this way. It does say that the bottom type is implicitly convertible to any type, but being implicitly convertible does not mean that function types are also implicitly convertible. Just as float and int may be implicitly convertible, their functions are not. The only function types that appear to be implicitly convertible are those of class and their derived classes. This would mean that the bottom type function would need to be ABI compatible with every single function type. I'm not sure if that's possible, but I feel that would need to addressed in the DIP.
Jan 16 2019
On Wednesday, 16 January 2019 at 23:08:38 UTC, Paul Backus wrote:On Wednesday, 16 January 2019 at 11:23:18 UTC, Johan Engelen wrote:Also a simpler work around to your problem: struct Expected(T, E) { private SumType!(T, E) data; property T value() { return data.match!( (T val) => val, (E err) { throw new Exception(err.to!string); return T.init; } ); } } Or depending on what you are doing even just using an if statement if you only have 2 types like that.This is just another example of using the bottom type to signify `noreturn`, which is trivially done with an attribute and doesn't need a new type. I'd like to see _other_ practical uses of the bottom type. -JohanHere's an example I ran into while working on my `expectations` library. [1] Internally, an Expected!(T, E) is represented as a discriminated union containing either an "expected" value of type T or an error value of type E. I would like to implement a property method, `value`, that either returns the expected value if there is one, or throws an exception if there isn't (similar to Rust's `unwrap`). The obvious implementation looks like this: struct Expected(T, E) { private SumType!(T, E) data; property T value() { return data.match!( (T val) => val, (E err) { throw new Exception(err.to!string); } ); } } However, this will not compile, because the second lambda is inferred to have a return type of void, and void is not implicitly convertible to T. Instead, I have to resort to the following ugly workaround: property T value() { try { return data.tryMatch!( (T val) => val ); } catch (MatchException _) { data.tryMatch!( (E err) => throw new Exception(err.to!string) ); } } If the second lambda were inferred to have a return type of Tbottom, none of this would be necessary. The first version would Just Work™. [1] http://expectations.dub.pm
Jan 16 2019
On Wednesday, 16 January 2019 at 23:40:03 UTC, luckoverthere wrote:Also a simpler work around to your problem: struct Expected(T, E) { private SumType!(T, E) data; property T value() { return data.match!( (T val) => val, (E err) { throw new Exception(err.to!string); return T.init; } ); } }Yes, this would also work. But having to insert a spurious return statement just to placate the type system is still unsatisfactory compared to the original version. In fact, it's the exact sort of special-case hackery that Walter expressed concern about in his post.
Jan 16 2019
Johan Engelen <j j.nl> wrote:Rust is indeed interesting here because, as far as I understand, Rust used to have a bottom type but they removed it because it was causing too much trouble. So we'd need to learn from them why `!` is not a bottom type proper in Rust, and what justification they have for `!` being a not-quite-bottom type and how that applies to D.AFAIK the reason is, that Rust has no subtyping in the language. In type theory, the bottom type is the subtype of all other types, which is difficult to achieve without proper subtyping. Tobi
Jan 15 2019
On Tuesday, 15 January 2019 at 18:26:55 UTC, Meta wrote:With all due respect to all involved in this conversation, this DIP should be almost entirely non-controversial, but it is not, because most people posting on this mailing list have a more practical programming background, rather than a theoretical one. When I say that, I am not excluding myself, so please don't interpret it as an insult.I think this is a very astute observation.Let's look at Rust: they have introduced a bottom type, denoted as !, and to my knowledge it was almost entirely without controversy, because that community has a much stronger theoretical focus than D. I know that D is not Rust, but in this instance I feel that we could learn a thing or two rather than grafting on some noreturn hack (and it *is* a hack that just poorly emulates a bottom type).To me, bottom is a complex way to signal to the reader and to the optimiser that the function never returns. This task is much more easily done (i.e. the machinery to do so already exists) by an attribute.The bottom type is well-understood and has already had all the kinks worked out by academia. It is more a less a free lunch, because somebody else has already done the heavy intellectual lifting for us.What I still don't see is the _practical benefit_ of a type over an attribute (excluding for the sake of argument the cost of implementation complexity).To me, coming from a C-like background, an attribute is the canonical way to express that: a function that "noreturn"s, will never returns.I would have imagined this proposal to be completely different: describe why having a new bottom type is useful, and then in a small extra paragraph mentioning that this new bottom type can also be used to describe nonreturning functions.To me, that doesn't make any sense, as the bottom type is THE canonical way to express that a function will not return. It is a type with 0 values, thus, intuitively, there is no possible way a function with a return type of Tbottom could ever possibly return. Thus, it must throw an exception, loop forever, or abort the program.A big addition like this needs a big justification with a solution to a major shortcoming. Not being able to specify a function never returning is just a very minor shortcoming.I recommend reading up on what the bottom type is, and why it is useful in the context of a language's type system. Not only does it enable certain optimizations, it also has many other useful properties that we can take advantage of. Stack Overflow can explain it better than me: https://softwareengineering.stackexchange.com/questions/277197/is-there-a-reason-to-have-a-bottom-type-in-a-programming-languageNow, as for the benefit? The fact that a function does not return can be useful for: * optimization: one can prune any code after it (it won't return), there is no need to save the registers (as it won't be necessary to restore them), ... * static analysis: it eliminates a number of potential execution paths * maintainability: (see static analysis, but by humans)Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.
Jan 15 2019
On Wednesday, 16 January 2019 at 01:14:58 UTC, Nicholas Wilson wrote:Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.Is it "much" more costly? What about the benefit of not being able to write code like: noreturn int f() { ... } // wat?
Jan 15 2019
On Wednesday, 16 January 2019 at 04:24:34 UTC, aliak wrote:On Wednesday, 16 January 2019 at 01:14:58 UTC, Nicholas Wilson wrote:Ooh and eventually getting a "top type"... ? Can't have a toptype attribute can you?Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.Is it "much" more costly? What about the benefit of not being able to write code like: noreturn int f() { ... } // wat?
Jan 15 2019
On Wednesday, 16 January 2019 at 04:24:34 UTC, aliak wrote:On Wednesday, 16 January 2019 at 01:14:58 UTC, Nicholas Wilson wrote:Bottom, must be implemented in the compiler. Attributes exist _now_. version (LDC) enum noreturn = ldc.attributes.llvmAttr("noreturn")); else version (GDC) enum noreturn = gcc.attribute.attribute("noreturn"); // ? else version (DigitalMars) enum noreturn; This exhibits the documentation benefits on all compilers, and performance gains on the compilers that matter.Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.Is it "much" more costly?What about the benefit of not being able to write code like: noreturn int f() { ... } // wat?Yes that is possible. Does it make sense to write? Of course not. We shouldn't be taking into considerations of people writing code like that when designing the language. ` noreturn void` is what people use in C/C++ I don't see why that wouldn't follow in D.
Jan 15 2019
On Wednesday, 16 January 2019 at 04:33:08 UTC, Nicholas Wilson wrote:On Wednesday, 16 January 2019 at 04:24:34 UTC, aliak wrote:You'd have to take that in to consideration because you'd have to implement the logic to handle it though no? What would that logic be? Compiler error maybe ... 🤔 but then that's special casing in the compiler? (yuk). And I guess I just disagree with not taking how people would write code in to consideration. A proper type gives you everything the attribute does, and has the advantage of not allowing for weird code, and makes the type system more solid, and provides future top-type possibilities. It sounds like the only argument for an attribute is "it's a bit quicker to implement" ... in which case I'd not vote for it.On Wednesday, 16 January 2019 at 01:14:58 UTC, Nicholas Wilson wrote:Bottom, must be implemented in the compiler. Attributes exist _now_. version (LDC) enum noreturn = ldc.attributes.llvmAttr("noreturn")); else version (GDC) enum noreturn = gcc.attribute.attribute("noreturn"); // ? else version (DigitalMars) enum noreturn; This exhibits the documentation benefits on all compilers, and performance gains on the compilers that matter.Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.Is it "much" more costly?What about the benefit of not being able to write code like: noreturn int f() { ... } // wat?Yes that is possible. Does it make sense to write? Of course not. We shouldn't be taking into considerations of people writing code like that when designing the language. ` noreturn void` is what people use in C/C++ I don't see why that wouldn't follow in D.
Jan 15 2019
On Wednesday, 16 January 2019 at 04:52:15 UTC, aliak wrote:On Wednesday, 16 January 2019 at 04:33:08 UTC, Nicholas WilsonNo, the logic to handle it is already there `typeof(f()) == int`, just if you call it, it is undefined behaviour for it to return.Yes that is possible. Does it make sense to write? Of course not. We shouldn't be taking into considerations of people writing code like that when designing the language. ` noreturn void` is what people use in C/C++ I don't see why that wouldn't follow in D.You'd have to take that in to consideration because you'd have to implement the logic to handle it though no?And I guess I just disagree with not taking how people would write code in to consideration.I agree with the sentiment, but people are not going to write code like that. People can write a spaghetti of gotos, do they? Of course not. Should we designing around the fact that they can?A proper type gives you everything the attribute does, and has the advantage of not allowing for weird code, and makes the type system more solid, and provides future top-type possibilities. It sounds like the only argument for an attribute is "it's a bit quicker to implement" ... in which case I'd not vote for it.Solidification of the type comes at the cost of complexity, time, learning curve and bunch of other things. If the only advantage of type over an attribute is that it disallows code that people are not going to write in the first place, then the benefits its provides are not _practical benefits_ and we should treat them as such in the cost benefit analysis. The fact that it is already implemented is icing on the cake (or nail in the coffin, depending in which way you look at it).
Jan 15 2019
On Wed, 16 Jan 2019 04:24:34 +0000, aliak wrote:On Wednesday, 16 January 2019 at 01:14:58 UTC, Nicholas Wilson wrote:That's a downside. Consider a binding processor for a scripting language. It accepts functions of the form: int fn(State* state); where the return value indicates the number of return values pushed onto the stack. You want this processor to handle an `abort` function. You must declare it as: int abort(State* state); You also want these functions to be able to call each other, and you want the `abort` function to have proper flow analysis when you do so. Therefore you must declare it as: noreturn abort(State* state); This is obviously a problem. You either need two different functions with different names (and since both do the same thing, they'll be similar names, easy to confuse), or to forego the flow analysis benefits of noreturn. It's a disincentive to use noreturn. This might not be enough of a reason to use an attribute instead of a type, but it's *a* reason, and that kind of code has its place.Those can all be done by an attribute. So it comes down to a cost benefit analysis: the benefits they provide are the same, except the implementation of bottom is going to be much more costly.Is it "much" more costly? What about the benefit of not being able to write code like: noreturn int f() { ... } // wat?
Jan 15 2019
On 2019-01-15 11:59, Johan Engelen wrote:I know we are no longer supposed to discuss the proposal's merits, but... The proposal does not describe its merits.I completley agree.3 - why a new _type_ is needed for describing a property of a function (that's what attributes are for)Not sure it's even worth an attribute. I'm thinking more a pragma. -- /Jacob Carlborg
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. [...] Thanks in advance for your participation.I agree that this proposal should still be in the "Community Review stage". The DIP procedure document says:At the end of the review period, the DIP Manager will work with the POC to ensure all feedback has been addressed, revise the DIP as necessary, and include a summary of the review round in the Reviews section of the DIP.While a "Reviews" section has been added, no other change has been added to the document. For instance, while the Reviews section notes that `Tbottom*` should logically be `typeof(null)`, the document body still lists `Tbottom*` == `Tbottom`. But most importantly, the document does nothing to explore and compare alternatives to having a bottom type, that were suggested in the previous review threads. For instance, it was suggested that a function not returning could be expressed through an out contract: void foobar() out (; false); We could even add a __traits(wontReturn, ...) syntax, so that template functions could detect such contracts, and propagate them. Now, maybe these functions would be bloated and overly complicated and adding a bottom type would simplify them... but we should probably wait until these problems actually exist to add a layer of complexity to the type system.
Jan 15 2019
On Tuesday, 15 January 2019 at 12:20:20 UTC, Olivier FAURE wrote:I agree that this proposal should still be in the "Community Review stage". The DIP procedure document says:"has been addressed" does not mean that the DIP will be revised to reflect feedback. What it means is I will ask the DIP author to respond to any feedback in the review thread and to make any changes the author feels should be made. It's ultimately up to the author whether or not to do either, but I won't move forward until I get a response to both requests. If the DIP was revised in response to any of the feedback, I will mention it in the summary.At the end of the review period, the DIP Manager will work with the POC to ensure all feedback has been addressed, revise the DIP as necessary, and include a summary of the review round in the Reviews section of the DIP.While a "Reviews" section has been added, no other change has been added to the document. For instance, while the Reviews section notes that `Tbottom*` should logically be `typeof(null)`, the document body still lists `Tbottom*` == `Tbottom`.
Jan 15 2019
On Tuesday, 15 January 2019 at 14:01:12 UTC, Mike Parker wrote:[snip]There's a typo in the abstract that should get fixed at least... "The primary purpose is to specify *vthe* return type of a function that does not return.""has been addressed" does not mean that the DIP will be revised to reflect feedback. What it means is I will ask the DIP author to respond to any feedback in the review thread and to make any changes the author feels should be made. It's ultimately up to the author whether or not to do either, but I won't move forward until I get a response to both requests. If the DIP was revised in response to any of the feedback, I will mention it in the summary.
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.No, absolutely no. This shouldn't have passed draft review, this shouldn't have passed community review and it _absolutely_ should not pass final. This is a complete waste of review time, nothing has changed based on past reviews: No rationale has been given for this over the status quo for achieving this in LDC and GDC using an attribute. It is confusing in its specification of interaction and looks to be complicated in its implementation for _zero_ gain in functionality, neither documentation, nor optimisation. I'm not sure what the process is for dealing with "major flaws are discovered" that were discovered, bought up and ignored completely with _zero_ communications, not even a refutation, in _every_ _previous_ _review_. I cannot be more blunt: This final review should discarded, and the DIP sent back to draft review, postponed until the author has addressed the points raised in the draft review, or simply rejected.
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.I don't remember in detail the discussion for a bottom type, but currently I don't see anything here that wouldn't better served by an attribute.
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.I'd like to point out that this proposal is introducing 2 new semantic features that are related but actually orthogonal. 1. Allow a functions to be marked as "no return" 2. Introduce a "bottom" type defined as typeof(assert(0)). Any function that returns this type will be marked as "no return". Other reviewers have asked for why using a "bottom" type is preferable to a function attribute or a special out contract. In regards to the first feature (allow functions to be marked as "no return") they are all "semantically equivalent". They all provide a way for the control-flow analyzer to know when a function doesn't return and optimize the code surrounding it. The second feature goes another step further by introducing the "bottom type" into the language. This introduces new semantics that define how non-returning functions interact with the type system. Consider the following: int main(string[] args) { return (args.length > 1) || assert(0); } This currently doesn't compile but will with the new "bottom" type. Note that this example is another demonstration for why these 2 features are orthogonal. So the question of whether we should use a type/function attribute/contract to mark functions as "no return" is a different question than whether we should be introducing a new bottom type. The 2 are conflated because if we do introduce a new "bottom" type, then it means we will already have a way to mark a function as "no return". I would first try to answer whether or not we want the "bottom" type. If the answer is yes, then we now have a way to mark functions as "no return". If the answer is no, then we can use a function attribute or special out contract.
Jan 15 2019
On Tuesday, 15 January 2019 at 16:01:13 UTC, Jonathan Marler wrote:On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote: I would first try to answer whether or not we want the "bottom" type. If the answer is yes, then we now have a way to mark functions as "no return". If the answer is no, then we can use a function attribute or special out contract.+1
Jan 15 2019
On Tuesday, 15 January 2019 at 16:01:13 UTC, Jonathan Marler wrote:On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:To clarify a bit more. The rationale of the DIP only applies to the first feature which is to come up with some way of marking a functions as "no return". I haven't seen any rationale for the second orthogonal feature which is adding a bottom type to the language. From what I understand, adding a bottom type to the language allows us to call no-return functions inside expressions. return a || assert(0); instead of if (a) return a; assert(0); Though I can't come up with a practical use case for this. What else does the new bottom type give us? And be sure to distinguish between things that only require "no-return" functions and things that require a bottom type. If they only require "no-return" functions, then they are not rationale for adding a bottom type.DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.I'd like to point out that this proposal is introducing 2 new semantic features that are related but actually orthogonal. 1. Allow a functions to be marked as "no return" 2. Introduce a "bottom" type defined as typeof(assert(0)). Any function that returns this type will be marked as "no return". Other reviewers have asked for why using a "bottom" type is preferable to a function attribute or a special out contract. In regards to the first feature (allow functions to be marked as "no return") they are all "semantically equivalent". They all provide a way for the control-flow analyzer to know when a function doesn't return and optimize the code surrounding it. The second feature goes another step further by introducing the "bottom type" into the language. This introduces new semantics that define how non-returning functions interact with the type system. Consider the following: int main(string[] args) { return (args.length > 1) || assert(0); } This currently doesn't compile but will with the new "bottom" type. Note that this example is another demonstration for why these 2 features are orthogonal. So the question of whether we should use a type/function attribute/contract to mark functions as "no return" is a different question than whether we should be introducing a new bottom type. The 2 are conflated because if we do introduce a new "bottom" type, then it means we will already have a way to mark a function as "no return". I would first try to answer whether or not we want the "bottom" type. If the answer is yes, then we now have a way to mark functions as "no return". If the answer is no, then we can use a function attribute or special out contract.
Jan 15 2019
This part is quite terse and I'm not sure what the implications are:A function that returns a Tbottom is covariant with a function that returns any type T if T is returned via the registers or if the function returning Tbottom is overriding a function returning T.Would this work? class C { override Tbottom toString() { assert(false); } } It's implied that that would work, but using more words and examples for clarity would be nice. Would this print `int function()[immutable(char)[]]`, as it does today? import core.stdc.stdlib : exit; int doStuff(); auto actions = ["quit": &exit, "stuff": &doStuff]; writeln(typeof(actions.stringof)); It's not implied that that would work, and I strongly suspect it would not -- that it would find `&exit` and assume the delegates need to return Tbottom, then error because `doStuff` doesn't.
Jan 15 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.Similar to other reviewers, I am very disappointed in the lack of adjustments that were made to the proposal after the previous reviews. It basically only says that there were concerns but they are invalid, without giving any reasons. Unlike many other reviewers, I actually like the idea of having a bottom type very much (which might be related to me liking functional concepts in general very much). However, I still have severe concerns about the current proposal: 1.Introduce a bottom type which is a type that has no values. The primary purpose is to specify vthe return type of a function that does not return.It is not clear at all how these two statements are related. It is absolutely possible to have a type with no values, which does not allow this, so it should be explicitly stated that the type being introduced will support this. Currently it sounds like it is a natural consequence, which it is not: `void` behaves like a bottom type in this regard—it has no value. I would prefer something like "A bottom type—a type with no values—is introduced. Additionally, a function returning this type indicates that this particular function never returns." Also I believe that the DIP should propose a name for the introduced bottom type. This would allow clearer formulations of the above statement: "A bottom type—a type with no values— named `Tbottom` is introduced. Additionally, a function returning `Tbottom` indicates that this particular function never returns." 2.It's necessary to be competitive with other systems programming languages.Why is this the case? In what way does not being able to specify that a function does not return prevent D from being competitive with other systems programming languages? 3.RAII cleanup, scope, finally, and catch clauses need not be generated for such branches.Shouldn't this be better listed under "Smaller/faster code"? It is also "smaller code". 4.Properties of Tbottom: Tbottom.mangleof == "??"; Tbottom.sizeof == 0; Tbottom.alignsize == 1;Why are these values chosen? In particular, why `Tbottom.sizeof == 0;`, while `void.sizeof == 1;`? What is `alignsize` (I could not find that property on any other type)? What about other properties (`init`, `stringof`, ...)? In particular, it should be mentioned that `Tbottom.init` is an error (similar to `void.init`). 5.a || b a && b b may be of type TbottomI don't even understand what this means. Is it meant to express: "In the expressions ``` a || b a && b ``` `b` may be of type `TBottom`" ? Why no mention of `a`? This makes it very confusing. From "A Tbottom is implicitly convertible to any other type. No other type is implicitly convertible to Tbottom." I am guessing, that `a` may also be of type `Tbottom`, but this is really confusing. Also what is the type of the expressions? 6.a ? b : c If b and c are of type Tbottom, the result type is Tbottom. Else if b is Tbottom, then the result is typeof(c). Else if c is Tbottom, then the result is typeof(b).Most points from 5. also apply here. At least the proposed types of the expression are defined. However, from a type theory point of view, they make sense only partially: If one of `b` and `c` is of type `Tbottom` and the other is not, the expression either returns `typeof(c)` or it never returns, i.e. it is of type `Tbottom`. So the correct type of the expression would be a sumtype of `Tbottom` and `typeof(c)`. D cannot express sumtypes at the moment, so the decision to simply use `typeof(c)` seems to be the pragmatic one. But the DIP should at least explain why this decision was made. 7.cast(T)expression is allowed if expression is of type Tbottom.This should mention that the expression is allowed for any type `T`. 8.Any attempt to use the value of a Tbottom expression is an error.What does "use the value of a `Tbottom` expression" mean precisely? 9.Conversion An expression of type Tbottom can be implicitly converted to any other type.This is already stated above. 10.A function that returns a Tbottom is covariant with a function that returns any type T if T is returned via the registers or if the function returning Tbottom is overriding a function returning T. It is implementation defined if a type T is returned via the registers.This sounds horrible to me. It basically means that you cannot expect a function that returns `Tbottom` to be covariant with any function that does not return `Tbottom` if you want to compiler agnostic, so why bother allowing it at all? Also talking about registers at all in this DIP seems like a very bad smell to me. The concept of a bottom type is a high level concept and should not be concerned at all with the low level details of registers etc. (they are an implementation detail). 11.Alternative Use an attribute such as noreturn. C++ uses this approach. This has the awkward result of a function specifying it has a return type T, but never returns that type. Other potential uses of a bottom type will not be expressible. An alias cannot be made from an attribute, and templates cannot accept attributes as arguments. The advantage to noreturn is a function that does not return can be made covariant with a function that returns a value via a hidden pointer.Some more explicit "Other potential uses" should be given. This is very vague. In particular, why would one want to have an alias for `Tbottom` if its only use is to indicate that a function never returns? What could be uses of `Tbottom` as template parameter? Also the potential uses should be mentioned in the rationale. 12.Currently, the return type of a function declared with an auto return type is inferred to be void when no value is returned. With this proposal, such functions may be inferred to return Tbottom.Are there any implications to this? Are there any circumstances when the user might not want this? If not, it should be explicitly stated. 13.A common criticism that arose in the Draft Review and was repeated in this review was that the proposed feature would be better implemented as an attribute or pragma rather than as an aliased type. A suggestion was made that the proposal could be expanded to describe interactions with other language features and the implications thereof. Alternative names for the type alias were proposed, including: Bottom, bottom_t, never_t, never, nothing. A point was raised that Tbottom* == typeof(null) and Tbottom[] == typeof([]) would be preferable to both cases equating to Tbottom. The concern was raised that the feature would complicate the language implementation.Aside from "A common criticism that arose in the Draft Review and was repeated in this review was that the proposed feature would be better implemented as an attribute or pragma rather than as an aliased type.", this DIP does not address any of these concerns. About the naming issue: "`Tbottom`" is very inconsistent with the name of other "builtin" types. It is also very inconsistent with what other languages use. Here is a list of the names of the bottom types of some other languages which have bottom types similar to the proposed one: - Scala: Nothing - Kotlin: Nothing, - Ceylon: Nothing, - Flow: empty - TypeScript: never - Rust: ! I suggest that we use something that is consistent with other "builtin" types is not completely different from what other languages use. 14. The DIP proposes to add a type which is located at the bottom of the type hierarchy, but there is no mention at all of the dual concept—a top type, i.e. a type which every type implicitly converts to. From a type theory point of view, this asymmetry seems really weird. I'd like the DIP to go into if this is being considered and if not, why. It should probably be done in a "Future work" section or something similar. 15. If the proposed `Tbottom` type is introduced, it basically means we have 2 types that have no value: `Tbottom` and `void`. Until now, `void` was basically always used, when syntactically you need a type, but there is not actually a type. Unfortunately, the spec is very brief on `void`: " no type". To cite Ali from his book: "the keyword void represents having no type". `Tbottom` basically means the same, but it has additional semantic. I'd like the DIP to go into more detail where this leaves `void`. 16. Maybe not really relevant for the Final Review, but I find the DIP to be very difficult to understand at several places. This not due to the fact that the topic is actually very complicated, but rather because of style of the used language. In particular, it seems that the DIP author avoided using complete sentences in a lot of places where using a well formulated sentence could have made the intended meaning very clear. The "Expressions" section is a particularly bad example (at least to me, it was only possible to understand it with a lot of guess work). Outlook: Suppose we add a bottom type to D. Type theory tells us that there cannot be two different bottom types. And this is true: `void` does not implicitly convert all other types, so `void` is not actually a bottom type. `void` could rather be considered to be a unit type, i.e. a type with exactly one value (which does not signal "nothing", but rather "no information"). Unfortunately it is not possible to access that value (i.e. you can't declare a variable of type void) in D at the moment. In my opinion, if we decide to actually go the route of using types to represent information like "does not return", the proper way forward is to also properly support the related concepts, i.e. a top type (which is dual to a bottom type in regards to the type hierarchy) and a unit type (which is dual to a bottom type in a category theoretical sense: The unit type is the terminal object in the category of types and typed functions and the bottom type is the initial object in that category). Properly defining `void` to be a unit type might lead to very difficult problems with C-interoperability because it would require us to actually give `void` a value and `void` has no value in C. This might be an indicator that going that route is not the correct decision (as long as we care about C-interoperability, but I think that's a given). Consequently (applying the above paragraph) using the type system to signal things like "does not return" is NOT the correct way forward for D. This leaves us with the option of using an attribute like noreturn, which is also very consistent with the C world: It is how this is implemented in C++ ([[noreturn]]).
Jan 15 2019
On Tue, 15 Jan 2019 18:21:25 +0000, Johannes Loher wrote:assert(0)++, for instance, is not allowed. It might not be allowed to write: void doNothing(T)(T value) {} doNothing(assert(0)); Depends on whether passing something to a function is considered "using" its value. Combined with convertibility-style template constraints, this is going to explode in your face: void increment(T : long)(ref T value) { value++; } increment(assert(0)); While this example is contrived, it's inevitable that some people will run into it on occasion in more complex code, resulting in a lot of work to make every template bottom-safe.Any attempt to use the value of a Tbottom expression is an error.What does "use the value of a `Tbottom` expression" mean precisely?14. The DIP proposes to add a type which is located at the bottom of the type hierarchy, but there is no mention at all of the dual concept—a top type, i.e. a type which every type implicitly converts to. From a type theory point of view, this asymmetry seems really weird. I'd like the DIP to go into if this is being considered and if not, why. It should probably be done in a "Future work" section or something similar.As a library type, Variant, but that's not exactly within the type system.16. Maybe not really relevant for the Final Review, but I find the DIP to be very difficult to understand at several places. This not due to the fact that the topic is actually very complicated, but rather because of style of the used language. In particular, it seems that the DIP author avoided using complete sentences in a lot of places where using a well formulated sentence could have made the intended meaning very clear. The "Expressions" section is a particularly bad example (at least to me, it was only possible to understand it with a lot of guess work).Agreed. DIPs should use simple language and aim to use examples whenever possible. Brevity and sounding technical should be explicit non-goals.
Jan 15 2019
On Tuesday, 15 January 2019 at 18:36:30 UTC, Neia Neutuladh wrote:Agreed. DIPs should use simple language and aim to use examples whenever possible. Brevity and sounding technical should be explicit non-goals.+1 IMO, part of the DIP requirements should be that they are written as simply as possible with minimal use of jargon. That's just good sense, though, because people will not be accepting of a DIP that they can't grok.
Jan 15 2019
On Tuesday, 15 January 2019 at 18:21:25 UTC, Johannes Loher wrote:[...]17. The DIP does not explicitly state when a function can be declared to return `Tbottom` and when `Tbottom` is inferred as return type. Is there a difference between ``` Tbottom fun() { return assert(0); } ``` and ``` Tbottom fun() { assert(0); } ``` ? Will inference only work if expressions evaluating to `Tbottom` are returned, or is inference for simple things like ``` auto fun() { while(true) {} } ``` planned? (Obviously this cannot be done in all cases as it is equivalent to the halting problem)
Jan 15 2019
On Tuesday, 15 January 2019 at 18:51:13 UTC, Johannes Loher wrote:Will inference only work if expressions evaluating to `Tbottom` are returned, or is inference for simple things like ``` auto fun() { while(true) {} } ``` planned? (Obviously this cannot be done in all cases as it is equivalent to the halting problem)Looks like the compiler already some some kind of analysis to determine if there's a common type for different runtime branches: import std.stdio; auto test(int n, float f) { if (n > 0) return n; else return f; } auto test2(int n, string s) { if (n > 0) return n; else return s; // Error: mismatched function return type inference of string and int } void main() { //Works the same way in CTFE as at runtime pragma(msg, test(0, 0.1), " ", typeof(test(0, 0.1)).stringof); // prints "0.1 float" writeln(test(0, 0.1), " ", typeof(test(0, 0.1)).stringof); // ditto pragma(msg, test(1, 0.1), " ", typeof(test(1, 0.1)).stringof); // prints "1.0000 float writeln(test(1, 0.1), " ", typeof(test(1, 0.1)).stringof); // ditto } This is actually pretty cool, because if the type checker finds that one branch of the function returns Bottom, it can automatically assume that the return type is the type of the other branch, because a function returning T | Bottom can be simplified to a function returning just T. This is because iff the function returns, it must return a value of type T; otherwise, it will not return at all.
Jan 15 2019
On Tuesday, 15 January 2019 at 18:51:13 UTC, Johannes Loher wrote:[...]18.A point was raised that Tbottom* == typeof(null) and Tbottom[] == typeof([]) would be preferable to both cases equating to Tbottom.I totally agree with this criticism. What does `Tbottom*` mean conceptually? It is a type which can hold either an address where a `Tbottom` is located, or `null`. However, there cannot be any address where a `Tbottom` is located, because `Tbottom` does not have any values. This means that `Tbottom*` is a type which can hold exactly one value: `null`. By the way, Kotlin does basically the same thing. Kotlin does not have pointers, but it has optional types and the type `Nothing?` is a type which can hold exactly one value: `null`. This also means that it is basically a unit type. While it is not actually Kotlin's `Unit` type, it could have been. Similarly, should we decide to go down that road and add proper top, bottom and unit types, we could define ``` alias Tbottom = typeof(assert(0)); alias void = Tbottom*; ```
Jan 16 2019
On Wednesday, 16 January 2019 at 08:47:48 UTC, Johannes Loher wrote:On Tuesday, 15 January 2019 at 18:51:13 UTC, Johannes Loher wrote:I think it was me who brought that pointer thing up in last review. Nicholas Wilson lambasted my idea, and rightfully, because I didn't come up with any practical use for null being defined like that. Well, now I have one: It could be used to finally let `null` to be assigned to `std.typecons.Nullable`.[...]18.A point was raised that Tbottom* == typeof(null) and Tbottom[] == typeof([]) would be preferable to both cases equating to Tbottom.What does `Tbottom*` mean conceptually? It is a type which can hold either an address where a `Tbottom` is located, or `null`. However, there cannot be any address where a `Tbottom` is located, because `Tbottom` does not have any values. This means that `Tbottom*` is a type which can hold exactly one value: `null`.``` alias void = Tbottom*; ```I believe you meant `enum null = typeof(Tbottom*).init;`
Jan 16 2019
On Wednesday, 16 January 2019 at 09:43:24 UTC, Dukc wrote:On Wednesday, 16 January 2019 at 08:47:48 UTC, Johannes Loher wrote:No, I actually meant it exactly as I wrote it. The reasoning is the following: If we were to make `void` a proper unit type, it needs to be a type with exactly one value. We could choose any type we want for this, as long as it only has one value. E.g. `struct A {}` could be a candidate for such a type as any two instances of it are equal. However, `Tbottom*` is also a type with only one value: `null`. To me it seemed like the canonical choice for defining `void`. There might actually be reasons not to do this. As I mentioned, Kotlin is going another route. `Unit` and `Nothing?` are different types in Kotlin, although they both can hold exactly one value: `Nothing?` can only hold `null` and `Unit` only has the value `Unit`. I don't think their decision to separate those types is arbitrary. In particular, `Nothing?` is a subtype of all optional types in Kotlin. Maybe there are some bad implications of making the unit type a subtype of all optional types, I don't know. The analogue in D (if `void` is defined to be `Tbottom*` would be that `void` is a subtype of all pointer types, which might seem weird. What you are describing is actually completely orthogonal to what I suggested. Even if `void` is defined in some other way and even if we keep it like it is (instead of making it a proper unit type), we can still define `null` to be the only value of `Tbottom*`. Now that you suggested it, it also really makes sense to do this in my opinion. (It would allow us to get rid of `null` as a builtin symbol)On Tuesday, 15 January 2019 at 18:51:13 UTC, Johannes Loher ``` alias void = Tbottom*; ```I believe you meant `enum null = typeof(Tbottom*).init;`
Jan 16 2019
On Wednesday, 16 January 2019 at 18:52:17 UTC, Johannes Loher wrote:No, I actually meant it exactly as I wrote it. The reasoning is the following: If we were to make `void` a proper unit type, it needs to be a type with exactly one value. We could choose any type we want for this, as long as it only has one value. E.g. `struct A {}` could be a candidate for such a type as any two instances of it are equal. However, `Tbottom*` is also a type with only one value: `null`. To me it seemed like the canonical choice for defining `void`.An interesting find! I cannot immediately see any good argument against it. Deserves a closer look IMO.
Jan 16 2019
On Wednesday, 16 January 2019 at 08:47:48 UTC, Johannes Loher wrote:By the way, Kotlin does basically the same thing. Kotlin does not have pointers, but it has optional types and the type `Nothing?` is a type which can hold exactly one value: `null`. This also means that it is basically a unit type. While it is not actually Kotlin's `Unit` type, it could have been. Similarly, should we decide to go down that road and add proper top, bottom and unit types, we could define ``` alias Tbottom = typeof(assert(0)); alias void = Tbottom*; ```That is amazing. I love it.
Jan 17 2019
On Thu, 17 Jan 2019 08:27:36 +0000, FeepingCreature wrote:On Wednesday, 16 January 2019 at 08:47:48 UTC, Johannes Loher wrote:Unfortunately, it would mean void.sizeof is (void*).sizeof and incrementing a void* would increase it by four or eight instead of one, which would be a breaking change. If D had distinct unit and raw-memory types, then that would be a valid way to define unit, but it would reserve four or eight bytes per variable instead of zero, which is suboptimal.By the way, Kotlin does basically the same thing. Kotlin does not have pointers, but it has optional types and the type `Nothing?` is a type which can hold exactly one value: `null`. This also means that it is basically a unit type. While it is not actually Kotlin's `Unit` type, it could have been. Similarly, should we decide to go down that road and add proper top, bottom and unit types, we could define ``` alias Tbottom = typeof(assert(0)); alias void = Tbottom*; ```That is amazing. I love it.
Jan 17 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment.What is this new ".alignsize" property ? Is that supposed to be ".alignof" ?
Jan 15 2019
As a historical note, subroutines used to be grouped into two categories: * functions return a value * procedures do not return a value See Pascal and Ada for example, they have "function" and "procedure" keywords. C decided to merge the two into functions, which has its advantages (a pointer to a function need not be a separate type than a pointer to a procedure). They are distinguished by having the function return 'void'.
Jan 15 2019
There are at least five parts to this proposal: a. Add a way to indicate that a function doesn't return. b. Use a special return type to indicate that. c. Make that type implicitly convert to absolutely every other type. d. Make it a compile error to use that type in most ways. e. Stop running catch/finally after assert errors, throwing exceptions, etc. I believe this was poor phrasing instead of a real part of the suggestion. The proposal should be explicit about having these parts. I feel like the proposal came this far with as little objection as it did in part because it was not clearer. The only points that are at all defended are (a) and (b), and the defense for (b) was sorely lacking.It's necessary to be competitive with other systems programming languages.If we add features merely because other languages do so, we are claiming that the designers of those languages are authorities to be blindly trusted. At that point, we can give up on the language and just use Rust or C++ or whatnot. That said, it's worthwhile to examine how other languages implement similar features. We can see what works for them and what doesn't, and we can see how that might work for D. The fact that the DIP does not have this analysis is surprising. Let me make up for this lack slightly. Rust has a token called never, written !. It is not a type aside from syntactically. It has two functions: first, it indicates that the function doesn't return ("diverging", the documentation calls it). Second, it tells the compiler to infer the type from its later contextual usage. (Or at least, that's how it worked circa 2014. It might have some other mechanism for not showing type errors today.) fn temp() -> ! { panic!("I am panicking"); } fn main() { let mut x : ! = temp(); println!("x = {}", x); x = temp(); println!("hello world"); } Swift has a type called Never. It's used to indicate that a function doesn't return -- it panics or throws an exception. It doesn't have any implicit conversions. You can have variables of its type. You can pass it to functions. You can assign it to things. It doesn't support the ternary expression logic that this DIP proposes. enum HelloError : Error { case badName } func sayHello(name: String) throws -> Never { throw HelloError.badName } struct Foo { var never : Never } do { var n : Never n = try sayHello(name: "Todd") var foo : Foo = Foo(never: n) } catch { } Kotlin has a type called Nothing. It doesn't do implicit conversions. It supports the ternary logic that this DIP proposes as a side effect (you can use throw and return in the same way). You can have variables of this type, pass it to functions, assign it to things, etc. fun foo(): Nothing { throw Exception("this doesn't work") } fun foo2(n: Nothing) {} fun bar() { try { var n: Nothing = foo() foo2(n) var n2 = if (true) { 12 } else { foo() } } catch (e: Exception) {} } C++ uses an attribute and does nothing special with the function's return type. Nim uses an attribute, likewise. Go lets you add a documentation comment, but that's for humans, not the compiler. There may be internal, undocumented mechanism to tell the compiler that a particular function doesn't return, intended to be used only within the runtime; that's the sort of thing the Go team does. I'd expect the proposal to also include the languages mentioned on the Wikipedia page for Bottom type that actually have a bottom type. (It has some egregious inaccuracies, such as claiming that Javascript has a bottom type named undefined.) It seems like forbidding variables, function parameters, fields, etc of the no-return type would be unique for D. It's also mostly unique *within* D; only void works similarly. Had the author done this sort of review, it would have been an opportunity to notice that the proposal is unique in this way and reflect on the advantages and disadvantages.RAII cleanup, scope, finally, and catch clauses need not be generated for such branches.This isn't true currently: void main() { scope (exit) writeln("scope exit"); assert(0); } This prints "scope exit" as you'd expect. Destructors and catch/finally clauses are run too. Is this intended as part of the proposal? If so, that would be a nontrivial change that would almost certainly be rejected. Or is it talking about code after the noreturn function? If so, that should be included in point 1, and it should be rephrased to clarify what "such branches" means.
Jan 15 2019
On Wednesday, 16 January 2019 at 07:04:37 UTC, Neia Neutuladh wrote:Kotlin has a type called Nothing. It doesn't do implicit conversions. It supports the ternary logic that this DIP proposes as a side effect (you can use throw and return in the same way). You can have variables of this type, pass it to functions, assign it to things, etc. fun foo(): Nothing { throw Exception("this doesn't work") } fun foo2(n: Nothing) {} fun bar() { try { var n: Nothing = foo() foo2(n) var n2 = if (true) { 12 } else { foo() } } catch (e: Exception) {} }This is not completely accurate. Nothing actually is the bottom type in Kotlin in the sense that it DOES implicitly convert to any type. E.g. ``` fun foo() : Nothing { throw Exception() } fun bar(s: String) { } fun main(args: Array<String>) { bar(foo()) } ``` is a valid Kotlin program. As you mention, you can however pass around variables of type `Nothing`. You can also somewhat do this with the type proposed by the DIP, but it is kind of limited: You can return expressions of type `Tbottom`, but you cannot declare a variable of that type or use it as parameter, similar to `void`. The reason for this is probably that in D every declared variable must by default be initiated to some value and this is not possible for void and `Tbottom`. However, I think this limitation is a bit arbitrary. We also have other types which do not have default initialization. Also this is valid D: ``` struct Tbottom { disable this(); this(const ref Tbottom t) { } disable static Tbottom init() property; } struct A {} Tbottom fun() { assert(0); return cast(Tbottom) A(); } void main() { Tbottom t = fun(); } ``` Granted, this still allows casting to `Tbottom` and this type is not implicitly convertible to every other type (these properties need to be baked into the language, I think) and you can still use the struct literal syntax to initialize variables of type `Tbottom`. But I think this showcases that it is not necessary conceptually to disallow declaring variables of type Tbottom. They just need to be initialized to an expression of type `Tbottom`. They still can never have an actual value or be used because by definition, the expression they are initialized to never returns. The same is true for function parameters. I actually also think it is correct to do the same for `void`, but `void` should even a single value and thus it should be default constructible and have an `init` property. As I mentioned earlier in this thread, this would make `void` a proper unit type, drawing a clear line between `Tbottom` and `void`. It is however very complicated to get this to work with C-interoperability.
Jan 16 2019
On Wed, 16 Jan 2019 08:09:39 +0000, Johannes Loher wrote:This is not completely accurate. Nothing actually is the bottom type in Kotlin in the sense that it DOES implicitly convert to any type. E.g.Thank you for the correction.I actually also think it is correct to do the same for `void`, but `void` should even a single value and thus it should be default constructible and have an `init` property. As I mentioned earlier in this thread, this would make `void` a proper unit type, drawing a clear line between `Tbottom` and `void`. It is however very complicated to get this to work with C-interoperability.I very much agree with void being a proper type. It's an annoying special case for generic code as is.
Jan 16 2019
On Wednesday, 16 January 2019 at 07:04:37 UTC, Neia Neutuladh wrote:There are at least five parts to this proposal: a. Add a way to indicate that a function doesn't return. b. Use a special return type to indicate that. c. Make that type implicitly convert to absolutely every other type. d. Make it a compile error to use that type in most ways. e. Stop running catch/finally after assert errors, throwing exceptions, etc. I believe this was poor phrasing instead of a real part of the suggestion.Thanks for this analysis. a. already exists as an attribute for LDC and GDC. b. as you mentioned is not well argued c, d & e follow from a bad argumentThe proposal should be explicit about having these parts. I feel like the proposal came this far with as little objection as it did in part because it was not clearer. The only points that are at all defended are (a) and (b), and the defense for (b) was sorely lacking.On the contrary, there was lots of objection but no response. As to why it has managed to get this far through the DIP process is beyond me: none of the points in the draft review have been dealt with.Indeed, I have a hard time imagining that point of rationale is serious on nature.It's necessary to be competitive with other systems programming languages.If we add features merely because other languages do so, we are claiming that the designers of those languages are authorities to be blindly trusted. At that point, we can give up on the language and just use Rust or C++ or whatnot.That said, it's worthwhile to examine how other languages implement similar features. We can see what works for them and what doesn't, and we can see how that might work for D. The fact that the DIP does not have this analysis is surprising. Let me make up for this lack slightly.Indeed, thanks for doing this.
Jan 16 2019
On Wednesday, 16 January 2019 at 09:19:13 UTC, Nicholas Wilson wrote:On the contrary, there was lots of objection but no response. As to why it has managed to get this far through the DIP process is beyond me: none of the points in the draft review have been dealt with.I agree with this. It's okay to reject the proposed alternatives, but the DIP should, with some of the proposals at least, state why. Otherwise, the DIP should have no business to final review, if even to community review.
Jan 16 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.The DIP says this: Any attempt to use the value of a Tbottom expression is an error. What does this mean? Just before this statement, we see a set of examples of how TBottom can be "used" inside expressions, i.e. a || b So what does that statement mean then?
Jan 17 2019
Am 17.01.19 um 15:21 schrieb Jonathan Marler:The DIP says this: Any attempt to use the value of a Tbottom expression is an error. What does this mean? Just before this statement, we see a set of examples of how TBottom can be "used" inside expressions, i.e. a || b So what does that statement mean then?I asked the same question earlier. What I _think_ it means is that you can never assign it to a variable, instanciate a variable with it or pass it to functions (maybe I forgot a few things). The main idea is that you can not use it in such a way, that you would actually need a value.
Jan 17 2019
On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.An advantage of the bottom type over an attribute is, that it is part of the function type. A library allowing to set a custom error handler could specify that the error handler must not return: alias ErrorHandler = TBottom function(string msg); Here is another example for TBottom* == typeof(null): If a library type allows custom user data with a template and you don't need, you could specify TBottom. struct LibraryType(UserData) { // ... UserData* userData; } Since the compiler knows, that userData is always null, it could eliminate dead code. Note that it would not be possible to specify typeof(null) as the template parameter of LibraryType, because then userData would be of type typeof(null)*. The rules TBottom* == typeof(null) and TBottom[] == typeof([]) can also be generalized to custom types: struct CustomArray(T) { T[] data; void opAssign(T2)(CustomArray!T2 a2) if(is(T2:T)) { data.length = a2.data.length; foreach(i, x; a2.data) data[i] = x; } } auto literal(T...)(T params) { import std.traits; static if(params.length == 0) return CustomArray!TBottom(); else return CustomArray!(CommonType!T)([params]); } unittest { CustomArray!int a = literal(); } This could be simplified if CommonType!() == TBottom holds. Note that this could also be implemented by replacing CustomArray!TBottom with a special EmptyCustomArray type.
Jan 17 2019
On Thu, 17 Jan 2019 17:17:45 +0000, Tim wrote:An advantage of the bottom type over an attribute is, that it is part of the function type. A library allowing to set a custom error handler could specify that the error handler must not return: alias ErrorHandler = TBottom function(string msg);It was unclear to me, however, whether you could implicitly convert a TBottom function(string msg) to an int function(string msg). It's much more common to have a bunch of handlers, most of which should complete properly and only one of which aborts the program every time.
Jan 17 2019
On Thursday, 17 January 2019 at 17:17:45 UTC, Tim wrote:On Tuesday, 15 January 2019 at 08:59:07 UTC, Mike Parker wrote:Great example, let's include this in the DIP.DIP 1017, "Add Bottom Type", is now ready for Final Review. This is the last chance for community feedback before the DIP is handed off to Walter and Andrei for the Formal Assessment. Please read the procedures document for details on what is expected in this review stage: https://github.com/dlang/DIPs/blob/master/PROCEDURE.md#final-review The current revision of the DIP for this review is located here: https://github.com/dlang/DIPs/blob/4716033a8cbc2ee024bfad8942b2ff6b63521f63/DIPs/DIP1017.md In it you'll find a link to and summary of the previous review round. This round of review will continue until 11:59 pm ET on January 30 unless I call it off before then. Thanks in advance for your participation.An advantage of the bottom type over an attribute is, that it is part of the function type. A library allowing to set a custom error handler could specify that the error handler must not return: alias ErrorHandler = TBottom function(string msg);Here is another example for TBottom* == typeof(null): If a library type allows custom user data with a template and you don't need, you could specify TBottom. struct LibraryType(UserData) { // ... UserData* userData; } Since the compiler knows, that userData is always null, it could eliminate dead code. Note that it would not be possible to specify typeof(null) as the template parameter of LibraryType, because then userData would be of type typeof(null)*.This is another good example. I'd like to point out that the Unit type would allow you to do the same thing for a struct like: struct AnotherLibraryType(UserData) { UserData userData; } But in your example the template uses a pointer to UserData. In this case the only way to express that you don't want any user data is to pass the Bottom type. To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.
Jan 17 2019
On Thu, Jan 17, 2019 at 06:51:13PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.This would introduce an asymmetry with the rest of the pointer types in the language which all have equal sizes, and this asymmetry will percolate down to other language constructs, causing special cases and inconsistencies everywhere. I don't recommend doing this. A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist. (Of course, TBottom.sizeof would have to be either 0 or some kind of non-existent value, because instances of TBottom cannot actually exist. Based on the principle of symmetry, I'd think it should be 0, because making it an error would imply specialcasing generic code that may unknowingly be handed TBottom as a template type argument, and making it return some other value like -1 or size_t.min may cause problems in other generic code that might, for example, wish to allocate storage of the given size. OTOH, maybe a negative .sizeof might be just the ticket to prevent generic code from trying to instantiate TBottom. Either way, it would have to be treated specially -- which also means there's a price to be paid for adding TBottom to the language: there is no free lunch.) T -- Those who've learned LaTeX swear by it. Those who are learning LaTeX swear at it. -- Pete Bleackley
Jan 17 2019
Am 17.01.19 um 21:15 schrieb H. S. Teoh:On Thu, Jan 17, 2019 at 06:51:13PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]+1 for making Tbottom* have the single value `null` (this totally makes sense, it is a type which holds `null` or the address of a value of type `Tbottom`, but no such address can exist, so it can only hold `null`). `sizeof(Tbottom*) == 0` is then a logical consequence, even if it is inconsistent with the size of other pointer types.To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.This would introduce an asymmetry with the rest of the pointer types in the language which all have equal sizes, and this asymmetry will percolate down to other language constructs, causing special cases and inconsistencies everywhere. I don't recommend doing this. A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist. (Of course, TBottom.sizeof would have to be either 0 or some kind of non-existent value, because instances of TBottom cannot actually exist. Based on the principle of symmetry, I'd think it should be 0, because making it an error would imply specialcasing generic code that may unknowingly be handed TBottom as a template type argument, and making it return some other value like -1 or size_t.min may cause problems in other generic code that might, for example, wish to allocate storage of the given size. OTOH, maybe a negative .sizeof might be just the ticket to prevent generic code from trying to instantiate TBottom. Either way, it would have to be treated specially -- which also means there's a price to be paid for adding TBottom to the language: there is no free lunch.) T
Jan 17 2019
On Thursday, 17 January 2019 at 20:15:20 UTC, H. S. Teoh wrote:On Thu, Jan 17, 2019 at 06:51:13PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]Yes that's kind of the point :) It's a new pointer type that has a unique size from all other pointer types. This means you can no-longer assume all pointers types are the same size, you have a special case where it's size can be zero. This might warrant some code changes to handle this case, but does enable some new semantics which can be useful for library writers. Case in point was you can now have a template parameter T* that the user can instantiate in such a way as to eliminate the pointer alltogether.To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.This would introduce an asymmetry with the rest of the pointer types in the language which all have equal sizes, and this asymmetry will percolate down to other language constructs, causing special cases and inconsistencies everywhere. I don't recommend doing this.A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.That's conceptually the same thing. Saying that TBottom* is "always equal to null" is the same as saying it's a Unit Type, which is the same as saying that it contains no information so the storage needed is 0 (same as void).
Jan 17 2019
On Thu, Jan 17, 2019 at 09:43:52PM +0000, Jonathan Marler via Digitalmars-d wrote:On Thursday, 17 January 2019 at 20:15:20 UTC, H. S. Teoh wrote:No, that's backwards. (1) Introducing asymmetry, i.e., special cases, is bad, because inevitably people will forget to check for it, leading to endless bugs. It will also percolate all over the language, creating exceptions and new corner cases, which are what we're trying to *reduce* here. (2) Eliminating a field is achieved by passing in a Unit type, not a Bottom type. E.g., if you have a struct template: struct S(T, U) { T t; U u; } then you could instantiate a single-field struct by doing: S!(Unit, int) s; // Equivalent to: struct { int u; } or: S!(string, Unit) t; // Equivalent to: struct { string t; } Instantiating it with Bottom means forcing a compile error (or runtime abort).On Thu, Jan 17, 2019 at 06:51:13PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]Yes that's kind of the point :) It's a new pointer type that has a unique size from all other pointer types. This means you can no-longer assume all pointers types are the same size, you have a special case where it's size can be zero. This might warrant some code changes to handle this case, but does enable some new semantics which can be useful for library writers. Case in point was you can now have a template parameter T* that the user can instantiate in such a way as to eliminate the pointer alltogether.To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.This would introduce an asymmetry with the rest of the pointer types in the language which all have equal sizes, and this asymmetry will percolate down to other language constructs, causing special cases and inconsistencies everywhere. I don't recommend doing this.I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed. T -- Freedom of speech: the whole world has no right *not* to hear my spouting off!A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.That's conceptually the same thing. Saying that TBottom* is "always equal to null" is the same as saying it's a Unit Type, which is the same as saying that it contains no information so the storage needed is 0 (same as void).
Jan 17 2019
Am 17.01.19 um 23:33 schrieb H. S. Teoh:I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed.I suggested this earlier in this thread, i.e. ``` alias void = Tbottom*; ``` This would make `null` the single value `void`. If we were to follow type theory rigorously, we would not even have another choice than doing this because all types which have exactly one value are naturally isomorphic and this means they are equal from a type theory perspective. This is not the case in D (in particular while struct A {} and struct B {} are of the same type from a rigorous type theory perspective, they are not in D). So we don't actually have to do it like this, but it would be an option (if we fixed void to be a proper unit type).
Jan 17 2019
On Thu, Jan 17, 2019 at 11:52:02PM +0100, Johannes Loher via Digitalmars-d wrote:Am 17.01.19 um 23:33 schrieb H. S. Teoh:This is where I wish someone who actually knows type theory would step up and clear up the situation, because in my mind, the type system we have in D contains an inherent, additional layer of complexity beyond what you're describing, and this is related to what you say below:I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed.I suggested this earlier in this thread, i.e. ``` alias void = Tbottom*; ``` This would make `null` the single value `void`. If we were to follow type theory rigorously, we would not even have another choice than doing this because all types which have exactly one value are naturally isomorphic and this means they are equal from a type theory perspective.This is not the case in D (in particular while struct A {} and struct B {} are of the same type from a rigorous type theory perspective, they are not in D). So we don't actually have to do it like this, but it would be an option (if we fixed void to be a proper unit type).I don't think we can realistically force empty structs `struct A {}` and `struct B {}` to be the same type, because even though they are arguably both unit types, they can still be distinguished from each other by name. This distinction is also more than just mere convention; it plays a crucial role in UDAs, for example: struct DontSerialize {} struct SerializeAsBytes {} struct Data { DontSerialize uint runtimeId; SerializeAsBytes int payload; } If we collapsed both structs into a single Unit type, the UDA mechanism would completely fall apart. Furthermore, types in D cannot be identified merely by their constituent parts. For example: struct CartesianCoors { float x; float y; } struct PolarCoors { float theta; float radius; } Technically, both structs are products of two floats, but that does not make them the same thing, because the values of their constituent components are interpreted differently and should not be confused one for another. At the very least, it would seem that the *name* of the type plays an essential role in its identification. I.e., it's almost as if a struct declaration is actually defining a type that, in addition to the types of its fields, contains also an implicit string type identifying the name of the struct. Or alternatively, we're dealing with a type system where types are additionally decorated with string identifiers that distinguish otherwise-identical types from each other. (Or it could be that I've no idea what I'm talking about, and this is the consequence of this community having very few people who actually know type theory thoroughly enough to be able to work out a sane solution to all of these issues. :-P) In any case, coming back to TBottom*, another issue that makes me wary of defining TBottom* == void is the top pointer `void*`. Since any pointer implicitly converts to `void*`, this means TBottom* implicitly converts to `void*` too, which in turn means `void` should also implicitly convert to `void*`: void procedure(...) { } void* ptr = procedure(...); // valid if TBottom* == Unit == void I think the problems that such a construct would cause would far outweigh whatever benefits that we may have reaped from introducing Unit and Bottom types! T -- Life is complex. It consists of real and imaginary parts. -- YHL
Jan 17 2019
Am 18.01.19 um 00:34 schrieb H. S. Teoh:I don't think we can realistically force empty structs `struct A {}` and `struct B {}` to be the same type, because even though they are arguably both unit types, they can still be distinguished from each other by name. This distinction is also more than just mere convention; it plays a crucial role in UDAs, for example: struct DontSerialize {} struct SerializeAsBytes {} struct Data { DontSerialize uint runtimeId; SerializeAsBytes int payload; } If we collapsed both structs into a single Unit type, the UDA mechanism would completely fall apart. Furthermore, types in D cannot be identified merely by their constituent parts. For example: struct CartesianCoors { float x; float y; } struct PolarCoors { float theta; float radius; } Technically, both structs are products of two floats, but that does not make them the same thing, because the values of their constituent components are interpreted differently and should not be confused one for another.I completely agree. D's types are not structural types.At the very least, it would seem that the *name* of the type plays an essential role in its identification. I.e., it's almost as if a struct declaration is actually defining a type that, in addition to the types of its fields, contains also an implicit string type identifying the name of the struct. Or alternatively, we're dealing with a type system where types are additionally decorated with string identifiers that distinguish otherwise-identical types from each other. (Or it could be that I've no idea what I'm talking about, and this is the consequence of this community having very few people who actually know type theory thoroughly enough to be able to work out a sane solution to all of these issues. :-P)This actually sounds like reasonable way to view it. You can even get that additional field at runtime (typeid).In any case, coming back to TBottom*, another issue that makes me wary of defining TBottom* == void is the top pointer `void*`. Since any pointer implicitly converts to `void*`, this means TBottom* implicitly converts to `void*` too, which in turn means `void` should also implicitly convert to `void*`: void procedure(...) { } void* ptr = procedure(...); // valid if TBottom* == Unit == void I think the problems that such a construct would cause would far outweigh whatever benefits that we may have reaped from introducing Unit and Bottom types!You are absolutely correct. I don't think it makes any sense to define `void` to be `Tbottom*` if we don't fix the problem with `void*`. I am even tend to think that in this case it might also not be reasonable to give `void` a value at all because it would make the difference in meaning of void in `void fun();` and `void*` even more apparent. This could also be a good thing though because at the moment almost nobody realizes that there is a difference.
Jan 17 2019
On Fri, Jan 18, 2019 at 12:45:19AM +0100, Johannes Loher via Digitalmars-d wrote:Am 18.01.19 um 00:34 schrieb H. S. Teoh:[...][...] Well, void* is simply a bad name for Any*, where Any is the top type. Even if we fix void* by renaming it to Any* (so that `void*` actually means a pointer to a unit type, whatever we decide that would be), we still have the problem of the chain of implicit conversions: void --> TBottom* --> Any* so as long as you define void == TBottom*, you have the weirdness: void procedure(...) {} Any* ptr = procedure(); // subsequent fun with casting and dereferencing ptr. This is why I don't think we should define void == TBottom*, even though both are ostensibly unit types. Besides, following the same logic, one would have to conclude typeof(null) == void too (since typeof(null) also only contains a single value), which leads to other weird situations. Anyway, you brought up an excellent point that D's types are not structural types. So perhaps pointers, including TBottom*, aren't as simple as we thought they were. Perhaps we could analyze pointers as having an implicit (and unrepresented) component identifying themselves as a pointer type, in addition to holding the pointer value itself. If you will, they're a product type of an atomic isPointer object with the pointer value to their specified type. So TBottom* isn't the same as the real unit type, which conveys no information; it (implicitly) conveys the information that it is a pointer, so it may be distinguished from the unit type as being the product of isPointer with Unit, not just Unit itself. Then we can still define void as the unit type, and TBottom* would be a distinct type that always has the (explicit) value null (with an associated implicit isPointer attribute). Dereferencing TBottom* would then be identical to dereferencing null, which should cause a runtime abort. (And similarly, empty structs would not be true unit types either, but product types of their names with the types of their fields. True product types in the type theoretic sense would have to be anonymous structs or entities like std.typecons.Tuple.) Or alternatively, we can think of TBottom* as a *decorated* type (decorated with isPointer or TBottom as the type of its target) that is to be distinguished from the (undecorated) true unit type. Similarly with named empty structs. T -- Too many people have open minds but closed eyes.In any case, coming back to TBottom*, another issue that makes me wary of defining TBottom* == void is the top pointer `void*`. Since any pointer implicitly converts to `void*`, this means TBottom* implicitly converts to `void*` too, which in turn means `void` should also implicitly convert to `void*`: void procedure(...) { } void* ptr = procedure(...); // valid if TBottom* == Unit == void I think the problems that such a construct would cause would far outweigh whatever benefits that we may have reaped from introducing Unit and Bottom types!You are absolutely correct. I don't think it makes any sense to define `void` to be `Tbottom*` if we don't fix the problem with `void*`.
Jan 17 2019
Am 18.01.19 um 01:17 schrieb H. S. Teoh:Well, void* is simply a bad name for Any*, where Any is the top type. Even if we fix void* by renaming it to Any* (so that `void*` actually means a pointer to a unit type, whatever we decide that would be), we still have the problem of the chain of implicit conversions: void --> TBottom* --> Any* so as long as you define void == TBottom*, you have the weirdness: void procedure(...) {} Any* ptr = procedure(); // subsequent fun with casting and dereferencing ptr. This is why I don't think we should define void == TBottom*, even though both are ostensibly unit types. Besides, following the same logic, one would have to conclude typeof(null) == void too (since typeof(null) also only contains a single value), which leads to other weird situations.I actually think this is ok. I don't reall have an issue with it. But I am also not convinced that we _need_ to do it that way. As I mentioned ealier, Kotlin for example does not do this (you have to replace pointers with optional types). They have distinct `Unit` and `Nothing?` types and this is fine in my opinion. On the other hand, I definitely think we should make `Tbottom* == typeof(null)`.Anyway, you brought up an excellent point that D's types are not structural types. So perhaps pointers, including TBottom*, aren't as simple as we thought they were. Perhaps we could analyze pointers as having an implicit (and unrepresented) component identifying themselves as a pointer type, in addition to holding the pointer value itself. If you will, they're a product type of an atomic isPointer object with the pointer value to their specified type. So TBottom* isn't the same as the real unit type, which conveys no information; it (implicitly) conveys the information that it is a pointer, so it may be distinguished from the unit type as being the product of isPointer with Unit, not just Unit itself. Then we can still define void as the unit type, and TBottom* would be a distinct type that always has the (explicit) value null (with an associated implicit isPointer attribute). Dereferencing TBottom* would then be identical to dereferencing null, which should cause a runtime abort. (And similarly, empty structs would not be true unit types either, but product types of their names with the types of their fields. True product types in the type theoretic sense would have to be anonymous structs or entities like std.typecons.Tuple.) Or alternatively, we can think of TBottom* as a *decorated* type (decorated with isPointer or TBottom as the type of its target) that is to be distinguished from the (undecorated) true unit type. Similarly with named empty structs. TThis sounds very reasonable.
Jan 17 2019
On Thursday, 17 January 2019 at 23:34:17 UTC, H. S. Teoh wrote:At the very least, it would seem that the *name* of the type plays an essential role in its identification. I.e., it's almost as if a struct declaration is actually defining a type that, in addition to the types of its fields, contains also an implicit string type identifying the name of the struct. Or alternatively, we're dealing with a type system where types are additionally decorated with string identifiers that distinguish otherwise-identical types from each other.I wonder if there's a body of research in type theory about "decorated members" structural type systems; eg, where the name of an aggregate isn't part of its type, but the name of its members is. For instance, in the following example: struct Point { float x; float y; } struct CartesianCoors { float x; float y; } struct PolarCoors { float theta; float radius; } Point would be equivalent to CartesianCoors, but different from PolarCoors(Or it could be that I've no idea what I'm talking about, and this is the consequence of this community having very few people who actually know type theory thoroughly enough to be able to work out a sane solution to all of these issues. :-P)Don't feel bad. Type theory communities aren't exactly great at designing sane solution usable by human beings either :P
Jan 21 2019
On Mon, 21 Jan 2019 17:01:54 +0000, Olivier FAURE wrote:On Thursday, 17 January 2019 at 23:34:17 UTC, H. S. Teoh wrote:That's what structural typing generally is. In essence, with structural typing, everything is a NamedTuple that implicitly casts to any projection of its fields. The more extreme variant, where everything is a tuple that implicitly casts to any prefix of itself, is not very useful.At the very least, it would seem that the *name* of the type plays an essential role in its identification. I.e., it's almost as if a struct declaration is actually defining a type that, in addition to the types of its fields, contains also an implicit string type identifying the name of the struct. Or alternatively, we're dealing with a type system where types are additionally decorated with string identifiers that distinguish otherwise-identical types from each other.I wonder if there's a body of research in type theory about "decorated members" structural type systems; eg, where the name of an aggregate isn't part of its type, but the name of its members is.
Jan 21 2019
On Monday, 21 January 2019 at 17:52:52 UTC, Neia Neutuladh wrote:That's what structural typing generally is. In essence, with structural typing, everything is a NamedTuple that implicitly casts to any projection of its fields.I'm not sure. Most discussions I've seen equate structural typing with structures being tuples with no naming, and using field names to differentiate types with nominal typing. I haven't seen much discussion of the difference between "structural with names" type systems (structs are NamedTuples) and "pure structural" type systems (structs are tuples).The more extreme variant, where everything is a tuple that implicitly casts to any prefix of itself, is not very useful.Why? Everything you can do with nominal typing, you can do with named structural typing.
Jan 22 2019
On Tue, 22 Jan 2019 12:57:10 +0000, Olivier FAURE wrote:On Monday, 21 January 2019 at 17:52:52 UTC, Neia Neutuladh wrote:Because it makes field order significant. It means that these two types are different: struct A { int x; string y; } struct B { string y; int x; } The first is Tuple!(int, string); the second is Tuple!(string, int). They look like they should convert to each other, but they don't. And these two types *do* convert to each other, but not in the way you'd want: struct A { bool destroyEarth; bool preserveHumanity; } struct B { bool preserveHumanity; bool destroyEarth; } Passing the wrong type flips the fields around, because the order of the fields is what matters, because that's part of the structure. And that's why nobody actually suggests this.The more extreme variant, where everything is a tuple that implicitly casts to any prefix of itself, is not very useful.Why?
Jan 22 2019
On Tuesday, 22 January 2019 at 16:09:39 UTC, Neia Neutuladh wrote:And these two types *do* convert to each other, but not in the way you'd want: struct A { bool destroyEarth; bool preserveHumanity; } struct B { bool preserveHumanity; bool destroyEarth; } Passing the wrong type flips the fields around, because the order of the fields is what matters, because that's part of the structure. And that's why nobody actually suggests this.See, this is exactly what I was talking about earlier. I thought you were saying "structs as NamedTuples are not very useful", while you were saying "structs as pure Tuples are not very useful". I think people often forget the distinction between the two in type theory. For the record, in an ideal C-style type system, I think both field order and names would be important, and nothing else. Eg: struct Tuple2Int { int; int; } struct Point2d { int x; int y; } struct Vec3Int { int x; int y; int z; } struct MyVec3 { int x; int y; int z; } struct MyCoord { int x; int z; int y; } In my ideal type system: - Tuple2Int is a supertype of Point2d is a supertype of Vec3Int - Vec3Int and MyVec3 are interchangeable - Tuple2Int is a supertype of MyCoord - MyCoord is not a subtype or a supertype of Point2d or Vec3Int That said, there should also be some idiomatic way to write MyCoord pt = ...; foobar(..., Vec3Int(pt), ...); and have it Just Work.Because it makes field order significant. It means that these two types are different: struct A { int x; string y; } struct B { string y; int x; }I think that's a fair distinction to make. Except for the most hardcore "virtualize every member" type system implementations, most languages will require a non-trivial amount of operations (as many as there are fields) to get an A from a B and vice-versa, whereas prefix casting is essentially free. So it makes sense that getting A from B would require an explicit operation.
Jan 22 2019
On Tue, 22 Jan 2019 16:52:12 +0000, Olivier FAURE wrote:On Tuesday, 22 January 2019 at 16:09:39 UTC, Neia Neutuladh wrote:In a structural type system, functions take "anything that conforms to this interface" and return "something that conforms to this interface". The Go-style implementation is to separate interfaces from normal types and for the compiler to create adapters left and right. This is what you seem to be thinking of. The Haskell-style implementation (if I recall correctly) is to implicitly convert a function involving an interface into a template function. This has some implications for covariance and contravariance that functional languages tend to sidestep using immutability.Because it makes field order significant. It means that these two types are different: struct A { int x; string y; } struct B { string y; int x; }I think that's a fair distinction to make. Except for the most hardcore "virtualize every member" type system implementations, most languages will require a non-trivial amount of operations (as many as there are fields) to get an A from a B and vice-versa, whereas prefix casting is essentially free.
Jan 22 2019
On Thursday, 17 January 2019 at 22:33:35 UTC, H. S. Teoh wrote:On Thu, Jan 17, 2019 at 09:43:52PM +0000, Jonathan Marler via Digitalmars-d wrote:This is definitely not true in all cases, therefore, is itself not a valid argument. (2) Eliminating aOn Thursday, 17 January 2019 at 20:15:20 UTC, H. S. Teoh wrote:No, that's backwards. (1) Introducing asymmetry, i.e., special cases, is bad, because inevitably people will forget to check for it, leading to endless bugs. It will also percolate all over the language, creating exceptions and new corner cases, which are what we're trying to *reduce* here.On Thu, Jan 17, 2019 at 06:51:13PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]Yes that's kind of the point :) It's a new pointer type that has a unique size from all other pointer types. This means you can no-longer assume all pointers types are the same size, you have a special case where it's size can be zero. This might warrant some code changes to handle this case, but does enable some new semantics which can be useful for library writers. Case in point was you can now have a template parameter T* that the user can instantiate in such a way as to eliminate the pointer alltogether.To summarize, the bottom type allows you to declare a special pointer with no storage! TBottom*.sizeof == 0 This is also something that should be in the DIP and Walter should chime in with whether or not he thinks these semantics can actually be implemented.This would introduce an asymmetry with the rest of the pointer types in the language which all have equal sizes, and this asymmetry will percolate down to other language constructs, causing special cases and inconsistencies everywhere. I don't recommend doing this.field is achieved by passing in a Unit type, not a Bottom type. E.g., if you have a struct template: struct S(T, U) { T t; U u; } then you could instantiate a single-field struct by doing: S!(Unit, int) s; // Equivalent to: struct { int u; } or: S!(string, Unit) t; // Equivalent to: struct { string t; } Instantiating it with Bottom means forcing a compile error (or runtime abort).Checkout this comment: https://forum.dlang.org/post/zxkgzziaoygkynqndpds forum.dlang.orgYou're mixing up what you know about programming languages like C/C++ and D with type theory. TBottom* by definition is a unit type. It is a pointer to nothing, which can only ever have one value. It may not "seem" like a unit type, but when you spend enough time in set theory and logic you'll find most things are not what they seem. Note that there are multiple ways to express and define a unit type. I suggest you read up on the definitions of these types and try to understand them a bit more, it becomes much clearer once you do.I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed. TA better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.That's conceptually the same thing. Saying that TBottom* is "always equal to null" is the same as saying it's a Unit Type, which is the same as saying that it contains no information so the storage needed is 0 (same as void).
Jan 17 2019
On Fri, Jan 18, 2019 at 12:54:52AM +0000, Jonathan Marler via Digitalmars-d wrote:On Thursday, 17 January 2019 at 22:33:35 UTC, H. S. Teoh wrote:[...]On Thu, Jan 17, 2019 at 09:43:52PM +0000, Jonathan Marler via Digitalmars-d wrote:[...]That's conceptually the same thing. Saying that TBottom* is "always equal to null" is the same as saying it's a Unit Type, which is the same as saying that it contains no information so the storage needed is 0 (same as void).I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed.You're mixing up what you know about programming languages like C/C++ and D with type theory. TBottom* by definition is a unit type. It is a pointer to nothing, which can only ever have one value. It may not "seem" like a unit type, but when you spend enough time in set theory and logic you'll find most things are not what they seem.[...] I don't argue that it's not a unit type. But that's not the same thing as saying it's *the* unit type. There may be multiple, distinct unit types, because D types are not structural types in the type theoretic sense; for example: struct A { int x; } is a distinct type from: struct B { int x; } in spite of being identical product types according to type theory. And indeed, struct C { } is distinct from: struct D { } in spite of both C and D being, ostensibly, "unit types". Similarly: enum E1 { A } is distinct from enum E2 { A } even though they are supposedly "unit types" inhabited by the same, identical value. (Well, actually they are logically distinct values, E1.A and E2.A according to the language spec, even if they are represented in machine code as the same integer value 0, and even if they are the only value of their respective types.) So the mapping from the concepts of type theory to D is not as straightforward as it might appear at first glance -- at least not if you want to retain any semblance of backward compatibility. You could, I suppose, gut the entire existing system and replace it with something directly derived from type theory, with a single, unique unit type that encompassess all unit types in the language. But that would also basically break all D code in existence, which makes it not a viable approach. At the very least, to account for the distinctness of differently-named (D) types with identical contents you'd have to decorate the (type-theoretic) type with some kind of distinguishing feature, such as something that encodes the name of the struct / enum. Once you have that, it's not so unreasonable to also say that pointer types are inherently distinct from non-pointer types, even if a particular pointer type happens to only ever allow 1 value, just as a particular non-pointer type also only ever allows 1 value. Nothing dictates that this single value must be the same value across the two types (I could have `enum F { A=1 }` and `enum G { A=2 }` for example). There may exist multiple unit types that are not necessarily compatible with each other. Which makes the idea of Tbottom* and void being distinct types a definite, well-founded possibility. (Though whether or not it's a good idea remains to be seen.) T -- Without outlines, life would be pointless.
Jan 17 2019
On Friday, 18 January 2019 at 01:31:47 UTC, H. S. Teoh wrote:I don't argue that it's not a unit type. But that's not the same thing as saying it's *the* unit type. There may be multiple, distinct unit types, because D types are not structural types in the type theoretic sense; for example: struct A { int x; } is a distinct type from: struct B { int x; } in spite of being identical product types according to type theory.The official term for this is "nominal typing" [1]. In a nominally-typed language, it is perfectly normal for there to be many distinct types that are structurally identical. In fact, the entire *point* of a nominal type system is to allow the programmer to distinguish between types that are structurally identical. For example, without nominal typing, it would be impossible to write code like this: struct XY { double x, y; } struct Polar { double r, theta; } Polar xyToPolar(XY src) { return PolarCoords( sqrt(src.x^^2 + src.y^^2), atan2(src.y, src.x) ); } // In a structurally-typed language, this assertion would fail assert(!__traits(compiles, xyToPolar(Polar(1, 3.14)))); The downside of a nominal type system is, as you've noticed, that one cannot speak of "the" unit type, or indeed "the" type with any particular structure. However, even if there are many possible unit types in D, that does not mean we cannot single one out for special treatment--indeed, we *do* single one out, already. `void`, in its role as a function return type, is different from all other (structurally-equivalent) unit types in that it is the only one that can be returned implicitly: struct Unit {} void foo() { return; } // Implicitly, `return void();` Unit bar() { return Unit(); } // Can't just write `return;` here So, even though `void` is not "the" unit type in the sense of uniqueness, it is still "special" in a way that no other unit type in D is. Another way to think of it is that, in a nominally-typed language, it is not enough for "the" unit type (if such a thing is to exist) to have a canonical structure. It must also have a canonical *name*. [1] https://en.wikipedia.org/wiki/Nominal_type_system
Jan 17 2019
On Fri, Jan 18, 2019 at 03:55:40AM +0000, Paul Backus via Digitalmars-d wrote:On Friday, 18 January 2019 at 01:31:47 UTC, H. S. Teoh wrote:Thank you. That's the gist of it. [...]I don't argue that it's not a unit type. But that's not the same thing as saying it's *the* unit type. There may be multiple, distinct unit types, because D types are not structural types in the type theoretic sense; for example: struct A { int x; } is a distinct type from: struct B { int x; } in spite of being identical product types according to type theory.The official term for this is "nominal typing" [1]. In a nominally-typed language, it is perfectly normal for there to be many distinct types that are structurally identical. In fact, the entire *point* of a nominal type system is to allow the programmer to distinguish between types that are structurally identical.The downside of a nominal type system is, as you've noticed, that one cannot speak of "the" unit type, or indeed "the" type with any particular structure. However, even if there are many possible unit types in D, that does not mean we cannot single one out for special treatment--indeed, we *do* single one out, already. `void`, in its role as a function return type, is different from all other (structurally-equivalent) unit types in that it is the only one that can be returned implicitly:[...]Another way to think of it is that, in a nominally-typed language, it is not enough for "the" unit type (if such a thing is to exist) to have a canonical structure. It must also have a canonical *name*.[...] So now we're clearer about what exactly is involved here. If we want to "clean up" the "messy" meaning of `void`, we should first recognize that it's an overloaded keyword with at least 3 or 4 different meanings: 1) A unit type -- in the context of a function return type. 2) A bottom type -- in the context of a variable declaration, which makes said declaration illegal (though this latter point is not mandatory and could possibly be altered as part of introducing an explicit bottom type to D). 3) The top type -- when used in its pointer form as void*, where it really means Any* where Any is the top type. 4) An uninitialized value -- when used in variable declarations of the form `T x = void;`. So in some sense, D already *has* a distinguished unit type, a bottom type (of sorts), and even a top type. (For our purposes, (4) is not really relevant.) It's just that they are currently not treated as first-class citizens, and therefore can only be used in certain, constrained contexts, and cannot be freely combined with other types as one might desire to. Our "arithmetic system", so to speak, already has the necessary concepts of 0, 1, and infinity, but they are just inconsistently lumped together under a single name and consequently arbitrarily restricted. So the course of action seems quite obvious, at least at a high level: a) Rename the various usages of `void` into distinct names reflecting their actual meaning; b) Remove the arbitrary restrictions on their usage. We cannot do (b) before doing (a), because it will end up with a mess where `void` means multiple things in the *same* context, which is unworkable. (And besides, continuing to use `void` only prolongs the conflation of incompatible concepts in users' minds, which will only lead to misunderstanding and wrong usage -- i.e., bad UI design.) Of course, there's a lot of details omitted in this simple plan of action. Before we can remove the restrictions on the unit / top / bottom types, we better be clear exactly what kind of semantics we expect when we combine them with other types in the existing system. We have to worry about backward-compatibility, migration schedules, and so on. It will be a pretty big change, more than what the DIP in question appears to be suggesting. T -- Right now I'm having amnesia and deja vu at the same time. I think I've forgotten this before.
Jan 18 2019
H. S. Teoh <hsteoh quickfur.ath.cx> wrote:I don't argue that it's not a unit type. But that's not the same thing as saying it's *the* unit type. There may be multiple, distinct unit types, because D types are not structural types in the type theoretic sense;Since a unit type consists of a single value, there exist as much unit types as there exist values (at least in theory). The type that only has the value '123' is a unit type as well as the type with the single value 'true' or an empty struct or tuple. Which one serves as 'canonical' unit type is merely convention. OTOH there can only be a single top type and a single bottom type by definition. --- Tobi
Jan 21 2019
H. S. Teoh <hsteoh quickfur.ath.cx> wrote:On Thu, Jan 17, 2019 at 09:43:52PM +0000, Jonathan Marler via Digitalmars-d wrote: [...]A unit type carrying no value means that the *dynamic value at runtime* carries no information that is not already known from the type itself. Take 'bool' as an example. It carries only one bit of dynami information, yet from the type itself you also know that it's no 'int' or pointer type which is definitely more than one bit of information. But it's the dynamic information that counts. If you have a variable (or return value) of type 'TBottom*' you already know from the type that the value will be 'null'. In other words there is no need to use any storage for that value. TobiThat's conceptually the same thing. Saying that TBottom* is "always equal to null" is the same as saying it's a Unit Type, which is the same as saying that it contains no information so the storage needed is 0 (same as void).I'm not so sure about that. A true Unit type conveys no information, yet TBottom* conveys at least this information: that it's a pointer, and that the pointer cannot be dereferenced. A true Unit type would be the return type of a void function; would you equate TBottom* with the return type of a void function? That would be very strange indeed.
Jan 21 2019
On Tue, 22 Jan 2019 06:29:41 +0000, Tobias Müller wrote:Take 'bool' as an example. It carries only one bit of dynami information, yet from the type itself you also know that it's no 'int' or pointer type which is definitely more than one bit of information.Point of pedantry: bool is an integer type according to the type specialization rules, a more specific subtype of 'int'. It can participate in some arithmetic expressions, though it omits things like ++ and +=. So you can write things like: i = i + true | ((true + true) ^^ (true + true + true) ^ false); And this code uses the bool overload: void foo(bool b) { writeln("bool"); } void foo(short b) { writeln("ubyte"); } foo(cast(ubyte)0); I'm not sure anyone but Walter thinks this is a good idea.
Jan 21 2019
On Tuesday, 22 January 2019 at 06:57:24 UTC, Neia Neutuladh wrote:Point of pedantry: bool is an integer type according to the type specialization rules, a more specific subtype of 'int'. It can participate in some arithmetic expressions, though it omits things like ++ and +=. So you can write things like: i = i + true | ((true + true) ^^ (true + true + true) ^ false); And this code uses the bool overload: void foo(bool b) { writeln("bool"); } void foo(short b) { writeln("ubyte"); } foo(cast(ubyte)0); I'm not sure anyone but Walter thinks this is a good idea.Indeed, hopefully we can convince Andrei to reverse the decision on DIP1015 at dconf.
Jan 22 2019
On Tuesday, 22 January 2019 at 10:23:09 UTC, Nicholas Wilson wrote:On Tuesday, 22 January 2019 at 06:57:24 UTC, Neia Neutuladh wrote:Good luck with that, you going to need it.Point of pedantry: bool is an integer type according to the type specialization rules, a more specific subtype of 'int'. It can participate in some arithmetic expressions, though it omits things like ++ and +=. So you can write things like: i = i + true | ((true + true) ^^ (true + true + true) ^ false); And this code uses the bool overload: void foo(bool b) { writeln("bool"); } void foo(short b) { writeln("ubyte"); } foo(cast(ubyte)0); I'm not sure anyone but Walter thinks this is a good idea.Indeed, hopefully we can convince Andrei to reverse the decision on DIP1015 at dconf.
Jan 22 2019
On Tuesday, 22 January 2019 at 06:57:24 UTC, Neia Neutuladh wrote:On Tue, 22 Jan 2019 06:29:41 +0000, Tobias Müller wrote:I'm still salty about DIP1015[...]Point of pedantry: bool is an integer type according to the type specialization rules, a more specific subtype of 'int'. It can participate in some arithmetic expressions, though it omits things like ++ and +=. So you can write things like: i = i + true | ((true + true) ^^ (true + true + true) ^ false); And this code uses the bool overload: void foo(bool b) { writeln("bool"); } void foo(short b) { writeln("ubyte"); } foo(cast(ubyte)0); I'm not sure anyone but Walter thinks this is a good idea.
Jan 22 2019
On Thu, 17 Jan 2019 12:15:20 -0800, H. S. Teoh wrote:A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.Dereferencing TBottom* would have to be rewritten as `assert(false);` to make this type of illegal work with generic code.
Jan 17 2019
Am 17.01.19 um 22:59 schrieb Neia Neutuladh:On Thu, 17 Jan 2019 12:15:20 -0800, H. S. Teoh wrote:No, dereferencing an instance of type `Tbottom*` is always dereferencing `null`, i.e. programm abbortion (which is not the same as `assert(0)`).A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.Dereferencing TBottom* would have to be rewritten as `assert(false);` to make this type of illegal work with generic code.
Jan 17 2019
On Thu, 17 Jan 2019 23:07:54 +0100, Johannes Loher wrote:Am 17.01.19 um 22:59 schrieb Neia Neutuladh:Oh, I misread that as "assume that TBottom* is null", sorry. Forcibly converting it to null also works. You would have to do that with every expression of type TBottom*, not just on cast.On Thu, 17 Jan 2019 12:15:20 -0800, H. S. Teoh wrote:No, dereferencing an instance of type `Tbottom*` is always dereferencing `null`, i.e. programm abbortion (which is not the same as `assert(0)`).A better approach might be to make TBottom* always equal to null -- i.e., it's always illegal to dereference it because no instances of TBottom can exist.Dereferencing TBottom* would have to be rewritten as `assert(false);` to make this type of illegal work with generic code.
Jan 17 2019
Am 17.01.19 um 18:17 schrieb Tim:An advantage of the bottom type over an attribute is, that it is part of the function type. A library allowing to set a custom error handler could specify that the error handler must not return: alias ErrorHandler = TBottom function(string msg);You could also do this with an attribute. Consider the following: ``` void bar() system { } void main() { alias SafeFun = void function() safe; SafeFun safeFun = &bar; // Error: cannot implicitly convert expression & bar of type void function() system to void function() safe } ``` You could do the same thing for an ` noreturn` attribute: ``` alias ErrorHandler = void function(string msg) noreturn; ``` What I am trying to say: ` noreturn` could also be part of the type of the function.
Jan 17 2019