February 09, 2020
On Sunday, 9 February 2020 at 18:32:45 UTC, Steven Schveighoffer wrote:
> On 2/8/20 11:10 PM, Jon Degenhardt wrote:
>> For Phobos this would be my expectation as well. I'm admittedly thinking of project environments with meaningful size teams more than the standard library of a major programming language. Environments where there is a fair bit of code in earlier stages of development and subject to more evolution.
>
> I think the evolution goes like this:
>
> void foo(int tmpName);
>
> ...
>
> // all callers:
>
> foo(5); // didn't use named parameters because I have no idea what "tmpName" means
>
> now, we evolve foo:
>
> void foo(int milliseconds);
>
> Now, all calls still work because nobody used the bad name as "documentation". New calls now use the name sometimes because it's helpful. My point is simply that the name itself is going to not require, but probably result in, people using or not using the name. Bad names naturally aren't going to get name usage, because they suck and aren't helpful. Good names are.
>
> I understand though, there are possible bikeshed problems here. For example, someone who implements foo might be tired of typing out "milliseconds" and change the parameter name to "ms". Now that person has to worry about what happens for callers. So instead he has to leave milliseconds in the name, and create a temporary ms in his function. At that point, you have to weigh how important it is to change that parameter name against the breakage that might occur. How much does it cost to repaint the shed? Might not be worth it.

Yes, this is representative of what I had in mind. Another is changes in the ambiguity of a name.

As an example, assume 'height' is an important element of a number of calculations in a system. Early on there is no ambiguity about what 'height' means. The word 'height' gets used in the names of functions, parameters, variables, etc. Then use cases arise where it is important to distinguish interior and exterior heights, so names like 'interiorHeight' and 'exteriorHeight' are preferable.

Over time the name 'height' by itself becomes more and more ambiguous. Any place the name 'height' is exposed in a manner that other code becomes dependent on the name is a source of technical debt.

Often this form of technical debt just happens. It's hard to predict these changes. But developers often do spend time thinking about forward compatibility of names that are part of a public API contract. This DIP will increase this surface area.

As I said, I don't think this is a deal breaker for the DIP. I think it is a positive change even with this downside. My only recommendation is to consider whether there are mechanisms that can help reduce this form technical debt, without requiring material additional developer time when initially writing code.

My thought was to allow explicit expression by the developer of whether a function was intended for named argument invocation (several possibilities). But there may be other approaches to achieving the same goal. For example, considering what Swift does:

> Note that Swift (which has parameter names as part of the function name) has a mechanism to deal with this.
>
> [... details excluded... ]
>
> Not sure this could be doable in D. But for a main language that started out with significant naming of parameters (inheriting this from objective-C), it's a good place to look for inspiration here.

This is very nice callout. Even if it cannot be done in D just putting it consideration set is worthwhile.

Also, several people have pointed out that aliasing parameter names within functions can help with these cases. I agree. It doesn't completely address the issue, but it is helpful, and a tool I hadn't thought of.

--Jon

February 10, 2020
On 10/02/2020 7:32 AM, Steven Schveighoffer wrote:
> 
> Not sure this could be doable in D. But for a main language that started out with significant naming of parameters (inheriting this from objective-C), it's a good place to look for inspiration here.

Alternatively support implicit construction of structs as parameters i.e.

void foo(Nullable!int ms);

foo(ms: 3);

You have to do some mapping internally but it means even if you deprecate a name, you can still keep the old ones.
February 10, 2020
On Monday, 10 February 2020 at 02:25:07 UTC, rikki cattermole wrote:
> On 10/02/2020 7:32 AM, Steven Schveighoffer wrote:
>> 
>> Not sure this could be doable in D. But for a main language that started out with significant naming of parameters (inheriting this from objective-C), it's a good place to look for inspiration here.
>
> Alternatively support implicit construction of structs as parameters i.e.
>
> void foo(Nullable!int ms);
>
> foo(ms: 3);
>
> You have to do some mapping internally but it means even if you deprecate a name, you can still keep the old ones.

This sounds like a good unification between struct initialization and named arguments.

So to "enable" named arguments, one would simply declare a struct like this:

struct BufferCreateInfo
{
    const(char)* type;
    size_t size;
}

and then use the struct type in a function argument list:

Buffer createBuffer(BufferCreateInfo info);

and call it like this:

auto buffer = createBuffer({ type: "BufferType", size: 16 }); // or createBuffer(type: "BufferType", size: 16) for syntax sugar

or if you want mixed named and non-named arguments:

Buffer createBuffer(BufferCreateInfo info, size_t howMany); // createBuffer(type: "BufferType", size: 16, 10);

The only change is allowing struct construction on a function parameter, then maybe take it a step further by eliminating the curly braces in function calls. But I don't know anything about compilers...
February 10, 2020
On 10/02/2020 5:31 PM, rb3 wrote:
> On Monday, 10 February 2020 at 02:25:07 UTC, rikki cattermole wrote:
>> On 10/02/2020 7:32 AM, Steven Schveighoffer wrote:
>>>
>>> Not sure this could be doable in D. But for a main language that started out with significant naming of parameters (inheriting this from objective-C), it's a good place to look for inspiration here.
>>
>> Alternatively support implicit construction of structs as parameters i.e.
>>
>> void foo(Nullable!int ms);
>>
>> foo(ms: 3);
>>
>> You have to do some mapping internally but it means even if you deprecate a name, you can still keep the old ones.
> 
> This sounds like a good unification between struct initialization and named arguments.
> 
> So to "enable" named arguments, one would simply declare a struct like this:
> 
> struct BufferCreateInfo
> {
>      const(char)* type;
>      size_t size;
> }
> 
> and then use the struct type in a function argument list:
> 
> Buffer createBuffer(BufferCreateInfo info);
> 
> and call it like this:
> 
> auto buffer = createBuffer({ type: "BufferType", size: 16 }); // or createBuffer(type: "BufferType", size: 16) for syntax sugar
> 
> or if you want mixed named and non-named arguments:
> 
> Buffer createBuffer(BufferCreateInfo info, size_t howMany); // createBuffer(type: "BufferType", size: 16, 10);
> 
> The only change is allowing struct construction on a function parameter, then maybe take it a step further by eliminating the curly braces in function calls. But I don't know anything about compilers...

That is not what I suggested.

What I suggested was given a single argument, automatically construct+call the constructor on a struct as defined in a parameter.

It is a 1 to 1 rewrite and would be very simple to do.
February 10, 2020
On Monday, 10 February 2020 at 04:36:17 UTC, rikki cattermole wrote:
> That is not what I suggested.
>
> What I suggested was given a single argument, automatically construct+call the constructor on a struct as defined in a parameter.
>
> It is a 1 to 1 rewrite and would be very simple to do.

That sounds like what I just described. Not sure what the difference is. Anyway, there's already a PR for what I described written by Wilzbach, it was opened in 2017: https://github.com/dlang/DIPs/pull/71. Is this not the same as what you suggested?
February 10, 2020
On 10/02/2020 5:47 PM, rb3 wrote:
> On Monday, 10 February 2020 at 04:36:17 UTC, rikki cattermole wrote:
>> That is not what I suggested.
>>
>> What I suggested was given a single argument, automatically construct+call the constructor on a struct as defined in a parameter.
>>
>> It is a 1 to 1 rewrite and would be very simple to do.
> 
> That sounds like what I just described. Not sure what the difference is. Anyway, there's already a PR for what I described written by Wilzbach, it was opened in 2017: https://github.com/dlang/DIPs/pull/71. Is this not the same as what you suggested?

I have commented on that PR.

The difference is:

1. in place struct initialization supports multiple arguments
2. and it requires extra syntax during construction
3. and finally is fully replaced by this DIP
February 10, 2020
On Sunday, 9 February 2020 at 21:05:43 UTC, Jon Degenhardt wrote:
> As an example, assume 'height' is an important element of a number of calculations in a system. Early on there is no ambiguity about what 'height' means. The word 'height' gets used in the names of functions, parameters, variables, etc. Then use cases arise where it is important to distinguish interior and exterior heights, so names like 'interiorHeight' and 'exteriorHeight' are preferable.
>
> Over time the name 'height' by itself becomes more and more ambiguous. Any place the name 'height' is exposed in a manner that other code becomes dependent on the name is a source of technical debt.
>
> Often this form of technical debt just happens. It's hard to predict these changes. But developers often do spend time thinking about forward compatibility of names that are part of a public API contract. This DIP will increase this surface area.
>
> As I said, I don't think this is a deal breaker for the DIP. I think it is a positive change even with this downside. My only recommendation is to consider whether there are mechanisms that can help reduce this form technical debt, without requiring material additional developer time when initially writing code.
>

I feel like this increase in surface area is actually straightforwardly a good thing. Even if the parameter is not used as a named parameter, we are seeing a shift in the meaning of the parameter as understood by the caller. With the DIP, this change in meaning will necessarily lead to a reevaluation of the callsite and clarification of whether you meant interiorHeight or exteriorHeight. With the current state of affairs, absent a change in type, there is absolutely no indication to the caller that their call has become ambiguous.

Again, the thing that is happening here is a drift in meaning, which is *already* a breaking change of the API. The language has simply previously allowed developers to obfuscate and ignore this fact. Inasmuch as this DIP makes this more difficult, it's a benefit, not a drawback.
February 10, 2020
On 2/6/20 10:33 PM, Jonathan M Davis wrote:
> Once in a while, named arguments may
> be useful, but for the most part, they're useful because a function has way
> too many parameters, in which case, the function should have been designed
> differently.

I find this assertion lacking evidence.

How does one design a constructor that initializes all the fields of a type without including all the parameters to initialize that type? If you reduce the number of parameters, what do you do with the data not specified? I suppose you can just have a minimal constructor and then just expect the user to set all the pieces up, but what if you need to use it in an expression? How do you decide which pieces are "important" enough to initialize first, and which ones get initialized later? What if the parameters are initializing things that can only be initialized in the constructor (e.g. immutable data).

I focus on constructors because structs ALREADY support named parameters in this instance (when there is a lack of constructor), and we haven't seen the problems you are asserting.

I can imagine with this DIP instead of 15 different constructors for all the different ways you want to construct said type (yes, I've seen this kind of stuff), you have one that has default arguments for all of the optional pieces (or maybe you split them up with ones that can be safe/pure and ones that can't, etc). And then the call interface is much better looking and self-explanatory. And I like that you can pick which way you want to call it, even for individual parameters.

-Steve
February 10, 2020
On Mon, Feb 10, 2020 at 01:27:49PM -0500, Steven Schveighoffer via Digitalmars-d wrote:
> On 2/6/20 10:33 PM, Jonathan M Davis wrote:
> > Once in a while, named arguments may be useful, but for the most part, they're useful because a function has way too many parameters, in which case, the function should have been designed differently.
> 
> I find this assertion lacking evidence.
> 
> How does one design a constructor that initializes all the fields of a type without including all the parameters to initialize that type?
[...]

Ostensibly, by encapsulating the parameters into a struct and passing said struct up the ctor chain. ;-)


T

-- 
English has the lovely word "defenestrate", meaning "to execute by throwing someone out a window", or more recently "to remove Windows from a computer and replace it with something useful". :-) -- John Cowan
February 10, 2020
On 2/10/20 1:36 PM, H. S. Teoh wrote:
> On Mon, Feb 10, 2020 at 01:27:49PM -0500, Steven Schveighoffer via Digitalmars-d wrote:
>> On 2/6/20 10:33 PM, Jonathan M Davis wrote:
>>> Once in a while, named arguments may be useful, but for the most
>>> part, they're useful because a function has way too many parameters,
>>> in which case, the function should have been designed differently.
>>
>> I find this assertion lacking evidence.
>>
>> How does one design a constructor that initializes all the fields of a
>> type without including all the parameters to initialize that type?
> [...]
> 
> Ostensibly, by encapsulating the parameters into a struct and passing
> said struct up the ctor chain. ;-)

So basically, for each type you need to define another type to pass parameters? Sounds... excessive ;)

And the benefit of having a POD struct to pass as a parameter to cut down on constructor parameters is... you get named parameters!

-Steve