Jump to page: 1 25  
Page
Thread overview
February 02
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?

February 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



February 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.


February 03
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));
}
```
February 02
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



February 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
[...]
> > 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
February 03

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

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;
}
February 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.
February 03
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?


February 03
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.

« First   ‹ Prev
1 2 3 4 5