digitalmars.D - What is the reasoning behind the lack of conversions when passing
- Carl Sturtivant (19/19) Feb 02 ```
- Jonathan M Davis (30/49) Feb 02 I want to say that TDPL discusses this somewhere, but I can't find where...
- Carl Sturtivant (40/53) Feb 02 Those complications could have been embraced *because* parameter
- bachmeier (14/22) Feb 02 I've long hated this inconvenience. It could be solved without a
- Carl Sturtivant (3/6) Feb 03 You may find something you could use here.
- Jonathan M Davis (23/52) Feb 02 I really don't see how this is crippling anything, but then again, I'd b...
- H. S. Teoh (35/52) Feb 02 [...]
- Carl Sturtivant (31/65) Feb 08 The above is overtly about implicit conversions as a consequence
- monkyyy (34/35) Feb 02 I think its mostly for overloads
- Walter Bright (9/9) Feb 02 C++ has this, implicit conversion of structs. It looks great with simple...
- Carl Sturtivant (8/10) Feb 02 It is apparent that it is overloading and `alias this` in its
- Walter Bright (6/15) Feb 03 I agree it is in conflict with overloading. But overloading and alias th...
- Carl Sturtivant (16/18) Feb 03 I wonder why overloading doesn't need an `overload` keyword at
- monkyyy (13/15) Feb 03 What do you have against overloading?
- Carl Sturtivant (4/7) Feb 03 Good point about templates.
- Walter Bright (2/3) Feb 03 It never occurred to anyone.
- TheTeaLady (13/19) Feb 03 I like this principle, that the compiler 'ensures' the author
- Basile B. (13/19) Feb 05 What D does is "implicit" overload creation. If the same name
- Atila Neves (4/17) Feb 05 It got bad enough in C++ that the guideline is now to use
- FeepingCreature (9/30) Feb 06 I just don't think that "this feature can be abused" is a
- Atila Neves (6/22) Feb 06 Sure, this would definitely help. I think the lesson learned
- Richard (Rikki) Andrew Cattermole (16/41) Feb 06 +1 to all of this.
- ryuukk_ (3/48) Feb 07 Simple yet effective, i love it
- Walter Bright (10/13) Feb 07 If this only impacted the person who wrote the code, that would be ok. B...
- Richard (Rikki) Andrew Cattermole (6/23) Feb 07 Yeah, its basically just a foreach in ``callMatch``.
- Walter Bright (2/3) Feb 07 LOL
- Richard (Rikki) Andrew Cattermole (5/9) Feb 07 Yes, I think its mostly the member of operator + some fleshing out.
- Carl Sturtivant (26/43) Feb 08 I don't think this kind of concrete experience-based pattern
- Walter Bright (8/8) Feb 11 We've considered it many times, and have decided against it due to the
- Carl Sturtivant (8/17) Feb 13 Variant is important to some of us because of its universality.
- Walter Bright (2/9) Feb 13 Isn't that what a sumtype is?
- Carl Sturtivant (4/14) Feb 14 Informally Any would be the sum of all types. Would your proposal
- Walter Bright (8/9) Feb 14 No, each type would have to be enumerated in the declaration.
- Paul Backus (4/10) Feb 14 The way Phobos currently does it is (essentially) by storing a
- Carl Sturtivant (6/19) Feb 15 OK, I was asking if Any could explicitly be added to the language
- Carl Sturtivant (34/34) Feb 16 Further to the above, examination of the documentation of
- Carl Sturtivant (7/13) Feb 06 +1
- Carl Sturtivant (5/10) Feb 09 At one point something related was viewed with approval. Probably
- Walter Bright (2/6) Feb 09 Overloading is the distinguishing reason.
- Dukc (17/25) Feb 19 There's another way: Template parameter deduction. Just like IFTI
``` import std.variant, std.math, std.stdio; void f(Variant z) { writeln(z); } void main() { auto x = sqrt(2.0); Variant v = x; f(v); //f(x); } ``` The commented out call `f(x)` is illegal in D. `f(VariantN!32LU v) is not callable using argument types (double)` Yet in principle it is an attempt to create and initialize a variable `z` local to `f` of type Variant. Exactly like the legal `Variant v = x` declaration. Why exactly is vanilla parameter passing not given the same semantics as initialization?
Feb 02
On Friday, February 2, 2024 2:16:33 PM MST Carl Sturtivant via Digitalmars-d wrote:``` import std.variant, std.math, std.stdio; void f(Variant z) { writeln(z); } void main() { auto x = sqrt(2.0); Variant v = x; f(v); //f(x); } ``` The commented out call `f(x)` is illegal in D. `f(VariantN!32LU v) is not callable using argument types (double)` Yet in principle it is an attempt to create and initialize a variable `z` local to `f` of type Variant. Exactly like the legal `Variant v = x` declaration. Why exactly is vanilla parameter passing not given the same semantics as initialization?I want to say that TDPL discusses this somewhere, but I can't find where at the moment. So, I can't provide the (probably better) answer that it would have. However, I'm pretty sure what it comes down to is that it simplifies function overloading (which can get fairly complex in C++ thanks to it allowing such conversions). It also reduces the number of bugs that you get from implicit conversions (though we do have alias this). By having alias this without the implicit construction with function arguments, we make it so that the conversion is only in one direction, whereas if we also allowed conversion based on constructors, then it would complicate the choices considerably in some cases. In contrast, when you're explicitly initializing a variable, there's no question as to what the type is. The same would be true with a function that wasn't overloaded, but allowing conversions based on constructors when there are no overloads but disallowing them when there are would be confusing and would result in code breakage when adding function overloads. It's similar to how D usually doesn't determine the type of an expression based on where it's used (it does in a few cases with literals, but it mostly doesn't). We _could_ allow it, which would enable some things which might be nice, but it's just simpler to disallow it and require that the programmer be explicit. We still have plenty of type inferrence going on. It's just that it only goes in one direction, which is much easier for the compiler to handle as well as generally being easier for the programmer to understand and reason about. And if you want to pass something to a function where construction is required, it's as simple as calling the constructor, which isn't particularly onerous, and it makes the code easier to understand. - Jonathan M Davis
Feb 02
On Friday, 2 February 2024 at 22:02:30 UTC, Jonathan M Davis wrote:However, I'm pretty sure what it comes down to is that it simplifies function overloading (which can get fairly complex in C++ thanks to it allowing such conversions).Those complications could have been embraced *because* parameter passing and initialization are best kept to exactly the same semantics when there is no overloading. And that would force a system of overloading compatible with such that extends naturally when an additional function of the same name is added, that works without changing the semantics of the others. C++ can do it. So D can. Overloading is complicated anyway, so pushing the complications into there rather than crippling parameter passing is making the language more effective. Scripting naively with Variant from std.variant would just work for example.It also reduces the number of bugs that you get from implicit conversions (though we do have alias this). By having alias this without the implicit construction with function arguments, we make it so that the conversion is only in one direction, whereas if we also allowed conversion based on constructors, then it would complicate the choices considerably in some cases.Why is `alias this` conversion permitted with parameter passing? Why isn't the no conversion restriction applied there? Your 'not onerous' argument below could be applied, and the name from the `alias this` declaration could have to be explicitly used. And that would affect only types that have alias this, not *everything* which is what the current restriction affects. Seems like the wrong decision. For consistency with initialization, `alias this` conversions could be disabled for initialization too. Interaction with constructors occurs there and for the same reasons. Initialization and parameter passing parallel one another naturally, and that simplicity has been broken in D's design.And if you want to pass something to a function where construction is required, it's as simple as calling the constructor, which isn't particularly onerous, and it makes the code easier to understand.However, this makes a mess of D's versatility, for example with naive scripting using Variant. Or standalone functions that carry their own imports (including for parameter types using `imported!()` from object) that when pasted into other code need to have modules containing their parameter types imported before they can be used, so constructors can be called to pass parameters to them. Working around this design decision in some more-than-trivial situations *is* onerous. Not all coding is in the context of big software projects. Versatility matters. Someone might reasonably expect parameter passing and initialization to have the same semantics: after all, parameter passing /is/ initialization of parameters to arguments. D lacking this possibility in some form seems to be a design mistake.
Feb 02
On Friday, 2 February 2024 at 23:49:21 UTC, Carl Sturtivant wrote:Working around this design decision in some more-than-trivial situations *is* onerous. Not all coding is in the context of big software projects. Versatility matters. Someone might reasonably expect parameter passing and initialization to have the same semantics: after all, parameter passing /is/ initialization of parameters to arguments. D lacking this possibility in some form seems to be a design mistake.I've long hated this inconvenience. It could be solved without a language change by putting something like this in Phobos (I've never actually written the code): ``` void foo(Variant x) {} mixin(rewrite!(foo, Variant)); ``` which would result in ``` void foo(T)(T x) { foo(Variant(x)); } ```
Feb 02
On Saturday, 3 February 2024 at 00:23:59 UTC, bachmeier wrote:I've long hated this inconvenience. It could be solved without a language change by putting something like this in Phobos (I've never actually written the code):You may find something you could use here. https://forum.dlang.org/post/shlldocweumneexbeuqx forum.dlang.org
Feb 03
On Friday, February 2, 2024 4:49:21 PM MST Carl Sturtivant via Digitalmars-d wrote:On Friday, 2 February 2024 at 22:02:30 UTC, Jonathan M Davis wrote:I really don't see how this is crippling anything, but then again, I'd be perfectly fine with requiring explicit constructor syntax when initializing a variable. It's how I program anyway.However, I'm pretty sure what it comes down to is that it simplifies function overloading (which can get fairly complex in C++ thanks to it allowing such conversions).Those complications could have been embraced *because* parameter passing and initialization are best kept to exactly the same semantics when there is no overloading. And that would force a system of overloading compatible with such that extends naturally when an additional function of the same name is added, that works without changing the semantics of the others. C++ can do it. So D can. Overloading is complicated anyway, so pushing the complications into there rather than crippling parameter passing is making the language more effective. Scripting naively with Variant from std.variant would just work for example.Well, personally, I'd actually be in favor of removing alias this entirely, because I think that implicit conversions like it are usually a mistake (and they definitely cause problems with templated code), but I'm sure that there would be complaints about it, since there are cases where it can be useful, and some folks like being able to do it. However, my experience in general is that implicit conversions are a source of bugs and best avoided, much as they can occasionally be useful. Regardless, I explained to you what was my understanding of why things work the way that they do. For better or worse, Walter made a number of choices like this that tried to simplify the situation in comparison to C++. Obviously, some stuff became impossible as a result, and some folks aren't going to like that, but you'll have to take that up with Walter. Personally, I've dealt with function overloading involving constructors in C++, and I've dealt with the situation in D, and I find the result in D to be far easier to understand without being a problem. So, I'm inclined to think that D made the right choice, but not everyone is going to agree with that. - Jonathan M DavisIt also reduces the number of bugs that you get from implicit conversions (though we do have alias this). By having alias this without the implicit construction with function arguments, we make it so that the conversion is only in one direction, whereas if we also allowed conversion based on constructors, then it would complicate the choices considerably in some cases.Why is `alias this` conversion permitted with parameter passing? Why isn't the no conversion restriction applied there? Your 'not onerous' argument below could be applied, and the name from the `alias this` declaration could have to be explicitly used. And that would affect only types that have alias this, not *everything* which is what the current restriction affects. Seems like the wrong decision.
Feb 02
On Fri, Feb 02, 2024 at 06:27:55PM -0700, Jonathan M Davis via Digitalmars-d wrote:On Friday, February 2, 2024 4:49:21 PM MST Carl Sturtivant via Digitalmars-d[...][...] I agree. In my early D code I also used a ton of `alias this` - it's very convenient, and lets me drop in replacements for existing types without a lot of tedious refactoring work. After a lot of experience working with it, though, I've come to think that in the long term it's not a good idea. While `alias this` lets you pretend that type T is type U, in the end it's still type T, and there comes a point where the distinction becomes important. In one of my larger D codebases where I liberally used `alias this` for a handful of very frequently-used types, it got to a point where I had 3 different types for representing exactly the same thing. It really should have been 1 type, but I had a wrapper type for the underlying static array using `alias this`, then an extension over this wrapper some additional functionality. But (no) thanks to alias this, these types could interconvert, so for the most part things Just Worked(tm). Except sometimes they can't, then it's a head-scratching exercise of asking "now which of the 3 types is being used here, and which one should it have been?!". This question is a lot harder than it looks when stuff is being passed between templated generic code, where it's not obvious which concrete type is actually at work. And of course, there's the risk of instantiating the same template with 3 different types that ultimately represent exactly the same thing. Needless template bloat. I really should have just refactored the code to use a single, consistent type throughout. And in general, my experience has been that implicit conversions tend to do more harm than good. They're really tempting in the short term because of the convenience. But in the long term they're really more trouble than they're worth. Living without implicit conversions is inconvenient, but in the long term it helps keep code easier to understand and more maintainable. T -- Never trust an operating system you don't have source for! -- Martin SchulzeWhy is `alias this` conversion permitted with parameter passing? Why isn't the no conversion restriction applied there? Your 'not onerous' argument below could be applied, and the name from the `alias this` declaration could have to be explicitly used. And that would affect only types that have alias this, not *everything* which is what the current restriction affects. Seems like the wrong decision.Well, personally, I'd actually be in favor of removing alias this entirely, because I think that implicit conversions like it are usually a mistake (and they definitely cause problems with templated code), but I'm sure that there would be complaints about it, since there are cases where it can be useful, and some folks like being able to do it. However, my experience in general is that implicit conversions are a source of bugs and best avoided, much as they can occasionally be useful.
Feb 02
On Saturday, 3 February 2024 at 02:14:00 UTC, H. S. Teoh wrote:I agree. In my early D code I also used a ton of `alias this` - it's very convenient, and lets me drop in replacements for existing types without a lot of tedious refactoring work. After a lot of experience working with it, though, I've come to think that in the long term it's not a good idea. While `alias this` lets you pretend that type T is type U, in the end it's still type T, and there comes a point where the distinction becomes important. In one of my larger D codebases where I liberally used `alias this` for a handful of very frequently-used types, it got to a point where I had 3 different types for representing exactly the same thing. It really should have been 1 type, but I had a wrapper type for the underlying static array using `alias this`, then an extension over this wrapper some additional functionality. But (no) thanks to alias this, these types could interconvert, so for the most part things Just Worked(tm). Except sometimes they can't, then it's a head-scratching exercise of asking "now which of the 3 types is being used here, and which one should it have been?!". This question is a lot harder than it looks when stuff is being passed between templated generic code, where it's not obvious which concrete type is actually at work. And of course, there's the risk of instantiating the same template with 3 different types that ultimately represent exactly the same thing. Needless template bloat. I really should have just refactored the code to use a single, consistent type throughout.The above is overtly about implicit conversions as a consequence of using `alias this`. I largely agree about that particular construct. It can lead to conversions occurring almost anywhere a value of a type containing the `alias this` occurs, i.e. *pervasive* implicit conversion. Another place where implicit conversions occur is in initialization in a variable declaration, where a single parameter constructor of the variable's type may be implicitly called. I think this is a good thing, and I haven't seen any complaints about this in D despite looking for them. Here implicit conversion is confined to only one kind of context. It is not pervasive.And in general, my experience has been that implicit conversions tend to do more harm than good. They're really tempting in the short term because of the convenience. But in the long term they're really more trouble than they're worth. Living without implicit conversions is inconvenient, but in the long term it helps keep code easier to understand and more maintainable.Perhaps that's because various implicit conversions have often been defined dangerously in the languages you have used. I think having some flexibility to define a single parameter constructor of your type to be implicitly called in a narrow context only by flagging it in your type's definition with say ` implicit` is valuable. It would only occur when passing a value to a parameter in the same way as it would be already be implicitly called when initializing a variable of your type to such a value. So the context for conversion would be limited, and have the same semantics as initialization does now in D. It would simply turn parameter passing into limited initialization for your type. The same limited initialization that D already has in variable declarations with initialization. Furthermore, if the view is that implicit conversion should be limited by default, the semantics of variable initialization could now be limited, and only constructors annotated with ` implicit` could be implicitly called in initializations too.
Feb 08
On Friday, 2 February 2024 at 21:16:33 UTC, Carl Sturtivant wrote:I think its mostly for overloads You can write a small piece of code that will attempt to conv anything to a function parameter ```d import std.traits; template polymorphic(alias F){ auto polymorphic(T...)(T args){ mixin((){ import std.conv:to; string o; foreach(i,S;(Parameters!F)[0..T.length]){ o~="args["~i.to!string~"].to!"~S.stringof~","; } return "return F("~o~");"; }()); }} void foo(int,float,bool=true){} alias bar=polymorphic!foo; struct myint{ int i; int to(T:int)()=>i; } alias somefloat=double; float to(T:float)(somefloat)=>float.init; int add(int i,int j)=>i+j; alias add_=polymorphic!add; void main(){ bar(myint(),somefloat.init); import std; int i=add_(myint(1),myint(2)); i.writeln; } ```
Feb 02
C++ has this, implicit conversion of structs. It looks great with simple cases. However, as overloading gets more complex, it becomes a source of mass confusion. I've had many C++ programmers tell me that nobody actually knows how C++ overloading works - they just try random things until they get the result they want. There was a lot of talk in the 90s about implicit conversion being a mistake, but by then it was too late and C++ has had to learn to live with it. It's in that class of features that inevitably lead to incomprehensible code. I tried to make overloading in D much simpler, but it's still overly complex. Let's not dig that cat out of the pet semetary.
Feb 02
On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I tried to make overloading in D much simpler, but it's still overly complex.It is apparent that it is overloading and `alias this` in its present form that are the mistakes. Not the initialization mechanism. Without that pair of mistakes, having parameter passing being initialization would be as simple as, well, initialization. Surely we don't think there's a design problem with initialization implicitly calling constructors?
Feb 02
On 2/2/2024 10:42 PM, Carl Sturtivant wrote:On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I agree it is in conflict with overloading. But overloading and alias this are very useful features. Language design is all about making tradeoffs. It's like designing a house. Making the bathroom bigger means making the bedroom smaller. P.S. Overloading is often used excessively, even in the DMD source code.I tried to make overloading in D much simpler, but it's still overly complex.It is apparent that it is overloading and `alias this` in its present form that are the mistakes. Not the initialization mechanism. Without that pair of mistakes, having parameter passing being initialization would be as simple as, well, initialization. Surely we don't think there's a design problem with initialization implicitly calling constructors?
Feb 03
On Saturday, 3 February 2024 at 17:58:22 UTC, Walter Bright wrote:P.S. Overloading is often used excessively, even in the DMD source code.I wonder why overloading doesn't need an `overload` keyword at the site of an overloaded function definition. After all, it's about a situation where the possible repertoire of functions that could be called at the site of a function call is extended. This is much like `override` when a method call's repertoire is extended. This last design decision is great because it makes overt what's going on at the textual point where new code is being written. The compiler ensures the author knows about the interaction with existing code. No doubt there are some ramifications to having an `overload` keyword with the obvious semantics. But the general effect would be to discourage overloading so that its use is an overt decision. Was the idea of an `overload` keyword ever considered for D?
Feb 03
On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant wrote:Was the idea of an `overload` keyword ever considered for D?What do you have against overloading? Breaking overloading would kill d, just unironically; how templates even exist is fundamentally about overloading, and the rabbit hole of overloading is rich and wonderful. I want function overloading to be 90% of all features in a language; and what auto-promotion rules d has is already to much and breaks overloading for example an overload set containing `foo(int[3])` and `foo()(string)` when matched to `foo("foo")` or something like it matches insanely to the int[3] in some cases. If you want a highly polymorphic function grab some of the example metaprogramming code and just alias
Feb 03
On Saturday, 3 February 2024 at 23:04:39 UTC, monkyyy wrote:Breaking overloading would kill d, just unironically; how templates even exist is fundamentally about overloading, and the rabbit hole of overloading is rich and wonderful.Good point about templates. I just have a sense that there is some hidden simplicity that has not been found in the present design.
Feb 03
On 2/3/2024 10:53 AM, Carl Sturtivant wrote:Was the idea of an `overload` keyword ever considered for D?It never occurred to anyone.
Feb 03
On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant wrote:... This last design decision is great because it makes overt what's going on at the textual point where new code is being written. The compiler ensures the author knows about the interaction with existing code. ...I like this principle, that the compiler 'ensures' the author knows about the interaction with existing code. The interaction of code in D, is not always clear. It's the downside of C too. For a modern 21st century PL, there are surely improvements that can be made in this area. Unfortunately, they will inevitably come at a cost - just as they would in C. D is very much attached to C. One should always keep that in mind - in your code that is.
Feb 03
On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant wrote:On Saturday, 3 February 2024 at 17:58:22 UTC, Walter Bright wrote:What D does is "implicit" overload creation. If the same name already exists in the current scope it just creates an "overload set", adding possible candidates in a sort of linked list. There are restrictions however on what can be part of a set, for example you cannot overload a function declaration with an int declaration. However this becomes complicated when it's about eponymous templates. There are probably still a couple of bugs related to that, because the way the semantics of a D program are checked does not allow to verify 100% of the time that the eponymous member(s) (yes plural) are all functions.P.S. Overloading is often used excessively, even in the DMD source code.I wonder why overloading doesn't need an `overload` keyword at the site of an overloaded function definition.
Feb 05
On Monday, 5 February 2024 at 19:16:40 UTC, Basile B. wrote:On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant wrote:If you like, the way sets are created is cristal-clear but the way a member of the set is picked is more complex ;)[...]What D does is "implicit" overload creation. If the same name already exists in the current scope it just creates an "overload set", adding possible candidates in a sort of linked list. There are restrictions however on what can be part of a set, for example you cannot overload a function declaration with an int declaration. However this becomes complicated when it's about eponymous templates. There are probably still a couple of bugs related to that, because the way the semantics of a D program are checked does not allow to verify 100% of the time that the eponymous member(s) (yes plural) are all functions.
Feb 05
On Monday, 5 February 2024 at 23:23:36 UTC, Basile B. wrote:On Monday, 5 February 2024 at 19:16:40 UTC, Basile B. wrote:I can show you a case of overload set that's absurd. We knew exactly what to call. But we tought that overloads were better. That's when explicit overload declarations [became clear](https://gitlab.com/styx-lang/styx/-/commit/ed04d535f2da5c9e1c52f2 475a11b7f59584c0d). Why the heck do you need overload sets when you exactly know what has to be called 😏. Just compile time lost. You know what to visit, just visit it ;)On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant wrote:If you like, the way sets are created is cristal-clear but the way a member of the set is picked is more complex ;)[...]What D does is "implicit" overload creation. If the same name already exists in the current scope it just creates an "overload set", adding possible candidates in a sort of linked list. There are restrictions however on what can be part of a set, for example you cannot overload a function declaration with an int declaration. However this becomes complicated when it's about eponymous templates. There are probably still a couple of bugs related to that, because the way the semantics of a D program are checked does not allow to verify 100% of the time that the eponymous member(s) (yes plural) are all functions.
Feb 05
On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:C++ has this, implicit conversion of structs. It looks great with simple cases. However, as overloading gets more complex, it becomes a source of mass confusion. I've had many C++ programmers tell me that nobody actually knows how C++ overloading works - they just try random things until they get the result they want. There was a lot of talk in the 90s about implicit conversion being a mistake, but by then it was too late and C++ has had to learn to live with it. It's in that class of features that inevitably lead to incomprehensible code. I tried to make overloading in D much simpler, but it's still overly complex. Let's not dig that cat out of the pet semetary.It got bad enough in C++ that the guideline is now to use explicit on single parameter constructors: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-explicit
Feb 05
On Monday, 5 February 2024 at 09:04:23 UTC, Atila Neves wrote:On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something. Even per-parameter would be useful! Honestly, if you just hardcoded implicit construction for `std.sumtype.SumType` I would be quite happy already.C++ has this, implicit conversion of structs. It looks great with simple cases. However, as overloading gets more complex, it becomes a source of mass confusion. I've had many C++ programmers tell me that nobody actually knows how C++ overloading works - they just try random things until they get the result they want. There was a lot of talk in the 90s about implicit conversion being a mistake, but by then it was too late and C++ has had to learn to live with it. It's in that class of features that inevitably lead to incomprehensible code. I tried to make overloading in D much simpler, but it's still overly complex. Let's not dig that cat out of the pet semetary.It got bad enough in C++ that the guideline is now to use explicit on single parameter constructors: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-explicit
Feb 06
On Tuesday, 6 February 2024 at 10:10:32 UTC, FeepingCreature wrote:On Monday, 5 February 2024 at 09:04:23 UTC, Atila Neves wrote:Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something. Even per-parameter would be useful![...]It got bad enough in C++ that the guideline is now to use explicit on single parameter constructors: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-explicitHonestly, if you just hardcoded implicit construction for `std.sumtype.SumType` I would be quite happy already.Something I've wanted every time I've used it.
Feb 06
On 06/02/2024 11:17 PM, Atila Neves wrote:On Tuesday, 6 February 2024 at 10:10:32 UTC, FeepingCreature wrote:+1 to all of this. The implicit conversion wouldn't be too hard to implement either. I actually implemented an implicit conversion today, in preparation of sumtypes! https://github.com/dlang/dmd/pull/16161 To make the following work: ```d Enum varF2 = identity(:Middle); Enum identity(Enum e) { return e; } enum Enum { Middle } ```On Monday, 5 February 2024 at 09:04:23 UTC, Atila Neves wrote:Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something. Even per-parameter would be useful![...]It got bad enough in C++ that the guideline is now to use explicit on single parameter constructors: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-explicitHonestly, if you just hardcoded implicit construction for `std.sumtype.SumType` I would be quite happy already.Something I've wanted every time I've used it.
Feb 06
On Tuesday, 6 February 2024 at 10:47:15 UTC, Richard (Rikki) Andrew Cattermole wrote:On 06/02/2024 11:17 PM, Atila Neves wrote:Simple yet effective, i love itOn Tuesday, 6 February 2024 at 10:10:32 UTC, FeepingCreature wrote:+1 to all of this. The implicit conversion wouldn't be too hard to implement either. I actually implemented an implicit conversion today, in preparation of sumtypes! https://github.com/dlang/dmd/pull/16161 To make the following work: ```d Enum varF2 = identity(:Middle); Enum identity(Enum e) { return e; } enum Enum { Middle } ```On Monday, 5 February 2024 at 09:04:23 UTC, Atila Neves wrote:Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something. Even per-parameter would be useful![...]It got bad enough in C++ that the guideline is now to use explicit on single parameter constructors: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-explicitHonestly, if you just hardcoded implicit construction for `std.sumtype.SumType` I would be quite happy already.Something I've wanted every time I've used it.
Feb 07
On 2/6/2024 2:17 AM, Atila Neves wrote:Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.If this only impacted the person who wrote the code, that would be ok. But these sorts of things have their way of insidiously infecting large code bases long after the guy who wrote it has gone. For example, macros and version algebra have caused endless suffering of maintainers who have no say in what the dear departed originator did. I've even known of a couple cases where the entire codebase was scrapped because of it. However, I understand that sumtypes can make good use of implicit conversions. I'm working on a language proposal to enable that by making sumtypes a language feature in and of itself.
Feb 07
On 08/02/2024 10:42 AM, Walter Bright wrote:On 2/6/2024 2:17 AM, Atila Neves wrote:Yeah, its basically just a foreach in ``callMatch``. Then do the rewrite for real in ``functionParameters``. Of course I'm not happy with your design, hence I made the following PoC PR to solve specifying names without the type :) https://github.com/dlang/dmd/pull/16161Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.If this only impacted the person who wrote the code, that would be ok. But these sorts of things have their way of insidiously infecting large code bases long after the guy who wrote it has gone. For example, macros and version algebra have caused endless suffering of maintainers who have no say in what the dear departed originator did. I've even known of a couple cases where the entire codebase was scrapped because of it. However, I understand that sumtypes can make good use of implicit conversions. I'm working on a language proposal to enable that by making sumtypes a language feature in and of itself.
Feb 07
On 2/7/2024 2:20 PM, Richard (Rikki) Andrew Cattermole wrote:Of course I'm not happy with your designLOL
Feb 07
On 08/02/2024 11:40 AM, Walter Bright wrote:On 2/7/2024 2:20 PM, Richard (Rikki) Andrew Cattermole wrote:Yes, I think its mostly the member of operator + some fleshing out. The implicit conversion was something we differed upon, but looks like we're converging there. I'm hopeful that I'm not going to have to write a DIP ;)Of course I'm not happy with your designLOL
Feb 07
On Wednesday, 7 February 2024 at 21:42:17 UTC, Walter Bright wrote:On 2/6/2024 2:17 AM, Atila Neves wrote:I don't think this kind of concrete experience-based pattern matching fits the actual logical details of the situation. The above is a not reason to block implicit. implicit would have to be used deliberately, i.e. it's opt-in not there by default. Any language feature can be abused. Blocking all conversions for parameter passing has the effect of blocking sane uses of conversions added deliberately. For example it blocks sane aspects of any sum type and any analog of Variant.Sure, this would definitely help. I think the lesson learned there is that implicit conversions *by default* are a bad idea. I don't even like integer conversions, cast if you want it.If this only impacted the person who wrote the code, that would be ok. But these sorts of things have their way of insidiously infecting large code bases long after the guy who wrote it has gone.For example, macros and version algebra have caused endless suffering of maintainers who have no say in what the dear departed originator did. I've even known of a couple cases where the entire codebase was scrapped because of it.In many many respects implicit is not at all like these. Please put this aside and consider the notion of implicit on its logical merits.However, I understand that sumtypes can make good use of implicit conversions. I'm working on a language proposal to enable that by making sumtypes a language feature in and of itself.implicit would enable a solution in D with no fuss. A sum type in the language that cannot be implemented in D would seem to be a serious breach of the D philosophy. In a discussion of built-in types versus user defined types TDPL's operator overloading chapter (p.365) lists 3 aspects of built-ins that D takes advantage of. None are relevant here. There's no good reason it is impossible to make a sum type in D that works as reasonably expected. I hope you will consider changing your mind about completely blocking implicit and instead consider that it solves a lot of problems, without operating by default, and accept that it is a positive thing to add to D.
Feb 08
We've considered it many times, and have decided against it due to the consistently negative experience with it in C++. Despite it being opt-in, it gets routinely abused. Overloading, even with all its restrictions in D, still gets abused to create incomprehensible code. When debugging Phobos code, often the only way I can figure out which overload is used is to insert `pragma(msg)` statements. Sumtypes and pattern matching are important issues, and I'll talk about that at the upcoming DConf online.
Feb 11
On Sunday, 11 February 2024 at 19:26:39 UTC, Walter Bright wrote:We've considered it many times, and have decided against it due to the consistently negative experience with it in C++. Despite it being opt-in, it gets routinely abused. Overloading, even with all its restrictions in D, still gets abused to create incomprehensible code. When debugging Phobos code, often the only way I can figure out which overload is used is to insert `pragma(msg)` statements. Sumtypes and pattern matching are important issues, and I'll talk about that at the upcoming DConf online.Variant is important to some of us because of its universality. If all conversions are blocked for parameter passing, can a replacement of Variant (say Any) be added to the list to be a part of the language? This would be so that any literal or value of any type can be passed to a parameter of type Any, enabling naive scripting and experimenting in any context without an import.
Feb 13
On 2/13/2024 4:38 PM, Carl Sturtivant wrote:Variant is important to some of us because of its universality. If all conversions are blocked for parameter passing, can a replacement of Variant (say Any) be added to the list to be a part of the language? This would be so that any literal or value of any type can be passed to a parameter of type Any, enabling naive scripting and experimenting in any context without an import.Isn't that what a sumtype is?
Feb 13
On Wednesday, 14 February 2024 at 04:41:45 UTC, Walter Bright wrote:On 2/13/2024 4:38 PM, Carl Sturtivant wrote:Informally Any would be the sum of all types. Would your proposal encompass this?Variant is important to some of us because of its universality. If all conversions are blocked for parameter passing, can a replacement of Variant (say Any) be added to the list to be a part of the language? This would be so that any literal or value of any type can be passed to a parameter of type Any, enabling naive scripting and experimenting in any context without an import.Isn't that what a sumtype is?
Feb 14
On 2/14/2024 9:46 AM, Carl Sturtivant wrote:Informally Any would be the sum of all types. Would your proposal encompass this?No, each type would have to be enumerated in the declaration. The usual way to do an Any type is to: 1. use Object as the common ancestor 2. use iUnknown as the common ancestor with QueryInterface 3. use void* 4. dmd internally uses its own RootObject for Any 5. use templates with a generic type T
Feb 14
On Wednesday, 14 February 2024 at 19:30:36 UTC, Walter Bright wrote:The usual way to do an Any type is to: 1. use Object as the common ancestor 2. use iUnknown as the common ancestor with QueryInterface 3. use void* 4. dmd internally uses its own RootObject for Any 5. use templates with a generic type TThe way Phobos currently does it is (essentially) by storing a void* and a TypeInfo internally.
Feb 14
On Wednesday, 14 February 2024 at 19:30:36 UTC, Walter Bright wrote:On 2/14/2024 9:46 AM, Carl Sturtivant wrote:OK, I was asking if Any could explicitly be added to the language for the reasons stated above, as it cannot be implemented in D. How is your remark about Any being a sumtype connected to providing Any in D?On Wednesday, 14 February 2024 at 04:41:45 UTC, Walter Bright wrote:No, each type would have to be enumerated in the declaration.On 2/13/2024 4:38 PM, Carl Sturtivant wrote:Informally Any would be the sum of all types. Would your proposal encompass this?This would be so that any literal or value of any type can be passed to a parameter of type Any, enabling naive scripting and experimenting in any context without an import.Isn't that what a sumtype is?
Feb 15
Further to the above, examination of the documentation of std.variant shows that it also contains the definition of an algebraic type `Algebraic` that has now been deprecated in favor of `SumType` in std.sumtype. Still, the intended purposes of std.variant are made clear both directly and by implication from what functionality is provided, and I strongly agree with those purposes. https://dlang.org/phobos/std_variant.html Specifically, `Algebraic` is (like `SumType`) for creating a type broadly speaking consisting of a finite number of alternative types, and `Variant` is a type whose alternatives are essentially the infinity of all types possible in D. It has been recognized that a sum type cannot be defined in a library in a way that a value of one of the alternative types composing it can be passed as an argument to a function with a parameter of that sum type without an explicit conversion. Similarly a function returning a value of such a sum type cannot return a value of one of the alternative types composing it without an explicit conversion. Since the decision to disallow all implicit conversions via constructors in function calls and returns has been permanently made, the only option for a simple sum type is to be a part of the D language itself. How that interacts with overloading has then to be decided. The exact same difficulties exist for Variant. So I am suggesting that for the same reasons that led to std.variant as a first shot at finite and an infinite sum type, that a replacement for Variant be added to the D language itself. I don't purport to suggest the name of this new type, but propose a working name of `Any` for discussion purposes. How `Any` interacts with overloading then has to be decided. Since it is a language feature, this can be made quite restricted. Surely there's a way that the target shot at by std.variant can at last be properly demolished?
Feb 16
On Tuesday, 6 February 2024 at 10:10:32 UTC, FeepingCreature wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something. Even per-parameter would be useful!+1 implicit Exactly what's needed under the present circumstances. Naive scripting with Variant could then work out of the box, for example, instead of being completely blocked.
Feb 06
On Tuesday, 6 February 2024 at 10:10:32 UTC, FeepingCreature wrote:I just don't think that "this feature can be abused" is a convincing argument. So what? Make it opt-in, mark it as dangerous, then when people use it it's on them. I don't want implicit conversion to always be enabled, I want it to be enabled by a keyword or UDA. implicit or something.At one point something related was viewed with approval. Probably got nixed behind the scenes. Disappointing inflexibility indeed. https://forum.dlang.org/post/lddjmh$8rn$1 digitalmars.com
Feb 09
On 2/9/2024 3:59 PM, Carl Sturtivant wrote:At one point something related was viewed with approval. Probably got nixed behind the scenes. Disappointing inflexibility indeed. https://forum.dlang.org/post/lddjmh$8rn$1 digitalmars.comOverloading is the distinguishing reason.
Feb 09
On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:C++ has this, implicit conversion of structs. It looks great with simple cases. However, as overloading gets more complex, it becomes a source of mass confusion. I've had many C++ programmers tell me that nobody actually knows how C++ overloading works - they just try random things until they get the result they want. There was a lot of talk in the 90s about implicit conversion being a mistake, but by then it was too late and C++ has had to learn to live with it.There's another way: Template parameter deduction. Just like IFTI infers template types from function call parameters, we could have a rule that if an eponymous template is assigned to something which has unambiquous type, that type is inferred for the variable, enum or function return value that is being assigned. Something like: ```D void fun(string); T enum(T) enumT = /+...+/; T funT(T)(T[]) => /+...+/; fun(enumT); // fun(enumT!string) fun(funT([])); // fun(funT(cast(string[]) [])) ``` This would, as I understand it, mean that `std.conv.to` could be called without the template parameter when it is assigned to something with an unambiguous type: `int x = 13.4.to;`.
Feb 19