www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - DIP 1017--Add Bottom Type--Final Review

reply Mike Parker <aldacron gmail.com> writes:
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
next sibling parent reply ixid <nuaccount gmail.com> writes:
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
parent reply Meta <jared771 gmail.com> writes:
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
parent reply aliak <something something.com> writes:
On Tuesday, 15 January 2019 at 21:39:08 UTC, Meta wrote:
 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).
+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.md
Jan 15
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling next sibling parent aliak <something something.com> writes:
On Wednesday, 16 January 2019 at 06:43:59 UTC, Walter Bright 
wrote:
 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.
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?
 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
prev sibling next sibling parent NaN <divide by.zero> writes:
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
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Wednesday, 16 January 2019 at 06:43:59 UTC, Walter Bright 
wrote:
 "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.
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.
Jan 16
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
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
parent Mike Parker <aldacron gmail.com> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply Dukc <ajieskola gmail.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Thursday, 17 January 2019 at 01:45:13 UTC, Walter Bright wrote:
 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.
OTOH, it gets us easy compatibility and familiarity.
 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
prev sibling next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent rikki cattermole <rikki cattermole.co.nz> writes:
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:
 [...]
 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.
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.
Jan 17
prev sibling next sibling parent reply luckoverthere <luckoverthere gmail.cm> writes:
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
parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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: [...]
 [...]
[...] 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. [...]
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?
Jan 16
next sibling parent reply Meta <jared771 gmail.com> writes:
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:
 On Wed, Jan 16, 2019 at 02:32:33PM -0800, Walter Bright via 
 Digitalmars-d wrote: [...]
 [...]
[...] 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. [...]
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?
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.
Jan 16
next sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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 missed
While 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
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 [...]
 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.
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.
 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).
[...] 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.
Jan 17
next sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
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:
 [...]
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
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.
Jan 17
prev sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
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:
 [...]
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
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.
Jan 17
prev sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
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
next sibling parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent Dukc <ajieskola gmail.com> writes:
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: [...]
 -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.
Oh yes. Stratch that.
Jan 17
prev sibling parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
next sibling parent Paul Backus <snarwin gmail.com> writes:
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:
 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)
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.
Jan 17
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
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.
 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
parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
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
next sibling parent Johannes Loher <johannesloher fg4f.de> writes:
Am 17.01.19 um 22:29 schrieb Neia Neutuladh:
 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.
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.
Jan 17
prev sibling next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
Am 17.01.19 um 22:29 schrieb Neia Neutuladh:
 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.
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?
Jan 17
parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
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? -- YHL
Jan 17
parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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 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); ```
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);`. [...]
 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.
 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.
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.
 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
parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
Right.
 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
parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 .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.
[...] 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.
Jan 17
prev sibling parent Olivier FAURE <couteaubleu gmail.com> writes:
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
prev sibling next sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 [...]
 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.
`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
Jan 17
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/17/2019 2:13 PM, H. S. Teoh wrote:
 Nice, well-rounded-out semantics.
I'm loving this discussion!
Jan 17
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Friday, 18 January 2019 at 01:46:10 UTC, Walter Bright wrote:
 On 1/17/2019 2:13 PM, H. S. Teoh wrote:
 Nice, well-rounded-out semantics.
I'm loving this discussion!
This is draft-review level discussion, not final review discussion.
Jan 17
prev sibling parent =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 On Thu, Jan 17, 2019 at 09:48:52PM +0100, Johannes Loher via Digitalmars-d
wrote:
 [...]
 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. [...]
That could be useful in generic code that is instatiated with 'Bottom' Tobi
Jan 20
prev sibling parent Johannes Loher <johannesloher fg4f.de> writes:
Am 17.01.19 um 19:47 schrieb Olivier FAURE:
 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.
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.
Jan 17
prev sibling next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
[...]
 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.
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. [...]
 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.
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.
Jan 16
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent reply Meta <jared771 gmail.com> writes:
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
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
parent Olivier FAURE <couteaubleu gmail.com> writes:
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
prev sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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
prev sibling next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Mike Franklin <slavo5150 yahoo.com> writes:
On Thursday, 17 January 2019 at 09:24:26 UTC, Walter Bright wrote:
 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.
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. Mike
Jan 18
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
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.
[...] 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".
Jan 19
prev sibling next sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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
prev sibling parent Chris M. <chrismohrfeld comcast.net> writes:
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!
Get Bartosz in here :#)
Jan 17
prev sibling next sibling parent reply Johan Engelen <j j.nl> writes:
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
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
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 
 correctly
False. C name mangling doesn't care about return types. -- Simen
Jan 15
next sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 15 January 2019 at 11:18:20 UTC, Simen Kjærås wrote:
 The declaration `extern(C) Tbottom exit();` won't mangle 
 correctly
False. C name mangling doesn't care about return types.
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.
Jan 15
prev sibling parent reply Johan Engelen <j j.nl> writes:
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:
 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).
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`...
 The declaration `extern(C) Tbottom exit();` won't mangle 
 correctly
False. C name mangling doesn't care about return types.
Indeed, thanks for the correction. -Johan
Jan 15
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 15.01.19 14:31, Johan Engelen wrote:
 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:
 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).
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,
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.
 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
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
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
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 17.01.19 03:32, Q. Schroll wrote:
 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.
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.
 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
prev sibling next sibling parent Guillaume Piolat <first.last gmail.com> writes:
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
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling next sibling parent reply Meta <jared771 gmail.com> writes:
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:
 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.
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.
 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
next sibling parent reply Johan Engelen <j j.nl> writes:
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:
 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.
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.
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).
 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.
 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.
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.
 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
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 :/ -Johan
Jan 15
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
prev sibling next sibling parent reply aliak <something something.com> writes:
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
parent reply Johan Engelen <j j.nl> writes:
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:
 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; } }
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. -Johan
Jan 16
parent reply Paul Backus <snarwin gmail.com> writes:
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.

 -Johan
Here'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
next sibling parent reply luckoverthere <luckoverthere gmail.cm> writes:
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:
 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.

 -Johan
Here'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
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
parent Paul Backus <snarwin gmail.com> writes:
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:
 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.
`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.
Jan 16
prev sibling parent reply luckoverthere <luckoverthere gmail.cm> writes:
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:
 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.

 -Johan
Here'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
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.
Jan 16
parent Paul Backus <snarwin gmail.com> writes:
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
prev sibling parent =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
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
prev sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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).
 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.
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.
 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
Now, 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
parent reply aliak <something something.com> writes:
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
next sibling parent aliak <something something.com> writes:
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:
 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?
Ooh and eventually getting a "top type"... ? Can't have a toptype attribute can you?
Jan 15
prev sibling next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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:
 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?
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.
 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
parent reply aliak <something something.com> writes:
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:
 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?
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.
 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.
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.
Jan 15
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Wednesday, 16 January 2019 at 04:52:15 UTC, aliak wrote:
 On Wednesday, 16 January 2019 at 04:33:08 UTC, Nicholas Wilson
 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?
No, the logic to handle it is already there `typeof(f()) == int`, just if you call it, it is undefined behaviour for it to return.
 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
prev sibling parent Neia Neutuladh <neia ikeran.org> writes:
On Wed, 16 Jan 2019 04:24:34 +0000, aliak wrote:
 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?
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.
Jan 15
prev sibling parent Jacob Carlborg <doob me.com> writes:
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
prev sibling next sibling parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent reply Mike Parker <aldacron gmail.com> writes:
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:

 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`.
"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
parent jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 15 January 2019 at 14:01:12 UTC, Mike Parker wrote:
 [snip]

 "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.
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."
Jan 15
prev sibling next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
prev sibling next sibling parent Atila Neves <atila.neves gmail.com> writes:
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
prev sibling next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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
next sibling parent Johannes Loher <johannes.loher fg4f.de> writes:
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
prev sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
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:
 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.
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.
Jan 15
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling next sibling parent reply Johannes Loher <johannes.loher fg4f.de> writes:
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 Tbottom
I 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
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Tue, 15 Jan 2019 18:21:25 +0000, Johannes Loher wrote:
 Any attempt to use the value of a Tbottom expression is an error.
What does "use the value of a `Tbottom` expression" mean precisely?
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.
 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
parent Meta <jared771 gmail.com> writes:
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
prev sibling parent reply Johannes Loher <johannes.loher fg4f.de> writes:
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
next sibling parent Meta <jared771 gmail.com> writes:
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
prev sibling parent reply Johannes Loher <johannes.loher fg4f.de> writes:
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
next sibling parent reply Dukc <ajieskola gmail.com> writes:
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:
 [...]
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`.
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`.
 ```
 alias void = Tbottom*;
 ```
I believe you meant `enum null = typeof(Tbottom*).init;`
Jan 16
parent reply Johannes Loher <johannes.loher fg4f.de> writes:
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:
 On Tuesday, 15 January 2019 at 18:51:13 UTC, Johannes Loher ```
 alias void = Tbottom*;
 ```
I believe you meant `enum null = typeof(Tbottom*).init;`
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)
Jan 16
parent Dukc <ajieskola gmail.com> writes:
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
prev sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
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
parent Neia Neutuladh <neia ikeran.org> writes:
On Thu, 17 Jan 2019 08:27:36 +0000, FeepingCreature wrote:
 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.
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.
Jan 17
prev sibling next sibling parent Basile B. <b2.temp gmx.com> writes:
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
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
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.) #![feature(never_type)] 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
next sibling parent reply Johannes Loher <johannes.loher fg4f.de> writes:
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
parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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 argument
 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.
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.
 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.
Indeed, I have a hard time imagining that point of rationale is serious on nature.
 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
parent Dukc <ajieskola gmail.com> writes:
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
prev sibling next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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
parent Johannes Loher <johannesloher fg4f.de> writes:
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
prev sibling parent reply Tim <tim.dlang t-online.de> writes:
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
next sibling parent Neia Neutuladh <neia ikeran.org> writes:
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
prev sibling next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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:
 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);
Great example, let's include this in the DIP.
 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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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:
 [...]
 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
+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.
Jan 17
prev sibling next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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: [...]
 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.
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.
 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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
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.
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).
 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).
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!
Jan 17
next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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 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:
 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
next sibling parent reply Johannes Loher <johannesloher fg4f.de> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
[...]
 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*`.
[...] 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.
Jan 17
parent Johannes Loher <johannesloher fg4f.de> writes:
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.
 
 
 T
 
This sounds very reasonable.
Jan 17
prev sibling parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent reply Neia Neutuladh <neia ikeran.org> writes:
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:
 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.
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.
Jan 21
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent reply Neia Neutuladh <neia ikeran.org> writes:
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:
 The more extreme variant, where everything is a tuple that implicitly
 casts to any prefix of itself, is not very useful.
Why?
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.
Jan 22
parent reply Olivier FAURE <couteaubleu gmail.com> writes:
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
parent Neia Neutuladh <neia ikeran.org> writes:
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:
 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.
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.
Jan 22
prev sibling next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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:
 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: [...]
 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.
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.
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.
This is definitely not true in all cases, therefore, is itself not a valid argument. (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).
Checkout this comment: https://forum.dlang.org/post/zxkgzziaoygkynqndpds forum.dlang.org
 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).
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
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. 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.
Jan 17
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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:
 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.
Thank you. That's the gist of it. [...]
 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
prev sibling parent =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
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
prev sibling parent reply =?UTF-8?Q?Tobias=20M=C3=BCller?= <troplin bluewin.ch> writes:
H. S. Teoh <hsteoh quickfur.ath.cx> 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.
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. Tobi
Jan 21
parent reply Neia Neutuladh <neia ikeran.org> writes:
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
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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
parent 12345swordy <alexanderheistermann gmail.com> writes:
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:
 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.
Good luck with that, you going to need it.
Jan 22
prev sibling parent Chris M. <chrismohrfeld comcast.net> writes:
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:
 [...]
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.
I'm still salty about DIP1015
Jan 22
prev sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
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
parent reply Johannes Loher <johannesloher fg4f.de> writes:
Am 17.01.19 um 22:59 schrieb Neia Neutuladh:
 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.
No, dereferencing an instance of type `Tbottom*` is always dereferencing `null`, i.e. programm abbortion (which is not the same as `assert(0)`).
Jan 17
parent Neia Neutuladh <neia ikeran.org> writes:
On Thu, 17 Jan 2019 23:07:54 +0100, Johannes Loher wrote:
 Am 17.01.19 um 22:59 schrieb Neia Neutuladh:
 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.
No, dereferencing an instance of type `Tbottom*` is always dereferencing `null`, i.e. programm abbortion (which is not the same as `assert(0)`).
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.
Jan 17
prev sibling parent Johannes Loher <johannesloher fg4f.de> writes:
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