www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What is the reasoning behind the lack of conversions when passing

reply Carl Sturtivant <sturtivant gmail.com> writes:
```
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
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
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
next sibling parent reply bachmeier <no spam.net> writes:
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
parent Carl Sturtivant <sturtivant gmail.com> writes:
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
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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:
 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.
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.
 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.
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 Davis
Feb 02
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
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 
[...]
 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.
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.
[...] 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 Schulze
Feb 02
parent Carl Sturtivant <sturtivant gmail.com> writes:
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
prev sibling next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply Carl Sturtivant <sturtivant gmail.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 2/2/2024 10:42 PM, Carl Sturtivant wrote:
 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?
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.
Feb 03
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
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
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
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
parent Carl Sturtivant <sturtivant gmail.com> writes:
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
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
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
prev sibling next sibling parent TheTeaLady <TheTeaLady gmail.com> writes:
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
prev sibling parent reply Basile B. <b2.temp gmx.com> writes:
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:
 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.
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
parent reply Basile B. <b2.temp gmx.com> writes:
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:
 [...]
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.
If you like, the way sets are created is cristal-clear but the way a member of the set is picked is more complex ;)
Feb 05
parent Basile B. <b2.temp gmx.com> writes:
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:
 On Saturday, 3 February 2024 at 18:53:02 UTC, Carl Sturtivant 
 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.
If you like, the way sets are created is cristal-clear but the way a member of the set is picked is more complex ;)
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 ;)
Feb 05
prev sibling next sibling parent reply Atila Neves <atila.neves gmail.com> writes:
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
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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:
 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
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.
Feb 06
next sibling parent reply Atila Neves <atila.neves gmail.com> writes:
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:
 On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright 
 wrote:
 [...]
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
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!
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.
 Honestly, 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
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 06/02/2024 11:17 PM, Atila Neves wrote:
 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:
 On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright wrote:
 [...]
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
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!
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.
 Honestly, 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.
+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 } ```
Feb 06
parent ryuukk_ <ryuukk.dev gmail.com> writes:
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:
 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:
 On Saturday, 3 February 2024 at 03:19:00 UTC, Walter Bright 
 wrote:
 [...]
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
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!
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.
 Honestly, 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.
+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 } ```
Simple yet effective, i love it
Feb 07
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 08/02/2024 10:42 AM, Walter Bright wrote:
 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.
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/16161
Feb 07
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 2/7/2024 2:20 PM, Richard (Rikki) Andrew Cattermole wrote:
 Of course I'm not happy with your design
LOL
Feb 07
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 08/02/2024 11:40 AM, Walter Bright wrote:
 On 2/7/2024 2:20 PM, Richard (Rikki) Andrew Cattermole wrote:
 Of course I'm not happy with your design
LOL
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 ;)
Feb 07
prev sibling parent reply Carl Sturtivant <sturtivant gmail.com> writes:
On Wednesday, 7 February 2024 at 21:42:17 UTC, Walter Bright 
wrote:
 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.
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.
 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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
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
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
On Wednesday, 14 February 2024 at 04:41:45 UTC, Walter Bright 
wrote:
 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?
Informally Any would be the sum of all types. Would your proposal encompass this?
Feb 14
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
next sibling parent Paul Backus <snarwin gmail.com> writes:
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 T
The way Phobos currently does it is (essentially) by storing a void* and a TypeInfo internally.
Feb 14
prev sibling parent reply Carl Sturtivant <sturtivant gmail.com> writes:
On Wednesday, 14 February 2024 at 19:30:36 UTC, Walter Bright 
wrote:
 On 2/14/2024 9:46 AM, Carl Sturtivant wrote:
 On Wednesday, 14 February 2024 at 04:41:45 UTC, Walter Bright 
 wrote:
 On 2/13/2024 4:38 PM, Carl Sturtivant wrote:
 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?
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.
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?
Feb 15
parent Carl Sturtivant <sturtivant gmail.com> writes:
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
prev sibling next sibling parent Carl Sturtivant <sturtivant gmail.com> writes:
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
prev sibling parent reply Carl Sturtivant <sturtivant gmail.com> writes:
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
parent Walter Bright <newshound2 digitalmars.com> writes:
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.com
Overloading is the distinguishing reason.
Feb 09
prev sibling parent Dukc <ajieskola gmail.com> writes:
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