October 14, 2017
Sorry for waiting so long to respond, I had to think about this a lot...

On 10/12/17 3:05 PM, Timon Gehr wrote:
> On 10.10.2017 17:05, Steven Schveighoffer wrote:
>> On 10/9/17 11:22 AM, Timon Gehr wrote:
>>> On 09.10.2017 01:20, Steven Schveighoffer wrote:
>>>>
>>>> My questioning comes with this:
>>>>
>>>> void bar(int a);
>>>> void bar((int,) x);
>>>>
>>>> To me, it is confusing or at least puzzling that these two aren't the same.
>>>> ...
>>>
>>> Well, to me it is a bit confusing that this is puzzling to you. Why should int be the same as (int,)? It does not make sense to index an integer, but (int,) can be indexed with 0 to get an integer.
>>
>> I understand why (int,) is different from int. What I meant was, why can't I *call* a function that takes a single int tuple with a single int value?
>> ...
> 
> Because this would require a special-case rule that I had not considered so far. This is up to discussion though.
> 
> I interpreted your question to be: "Why do your proposed rules not lead to my expected behaviour?", and not: "Why do your rules not allow this?", but it seems I have misinterpreted your question. Sorry for the confusion! :)

Not a problem!

>> Again, I go back to the 2-parameter version. I can call it with 2 values, or a tuple of 2 values.
> 
> With the caveat that those two cases are actually identical, yes.
> 
> auto x = (1,"2"); // construct value
> f(x); // now call f with value
> 
> and
> 
> f(1,"2"); // construct tuple and call f with the resulting value
> 
> It is like:
> 
> auto x = [1,2];
> f(x);
> 
> and
> 
> f([1,2]);
> 
> Except that redundant parentheses are optional:
> f(1,"2") is exactly equivalent to f((1,"2")) after parsing.
> 
> The second expression just adds an additional pair of parentheses around f. f(((1,"2"))) is also the same expression for the same reason.
> 
> Note that this does not compile:
> 
> void f(int a,int b,int c){}
> f(1,(2,3));

Right, there is a structural difference here. The 2 and 3 being tied together is a piece of information that is lost or different than is expected. I get that, and agree with it.

> 
> The reason is that I tried to call f with an argument of type (int,(int,int)), while it expected an argument of type (int,int,int).
> 
>> It makes no difference to the callee how I call it, as long as I put 2 values on the stack.
>> ...
> 
> Well, I think it should maybe not be possible to conflate e.g. (int,int) and ((int,),(int,)).

This, I'm not 100% sure on. In my mind, the difference between a value and a tuple of one element is trivial and negligible. I think they should be implicitly convertible between each other. Perhaps they would still be mangled differently, but you could still call both with the different forms. Sort of like const(int) and int have different semantics, but I can call a function with one or the other.

In that sense, perhaps foo(int) and foo((int,)) are different manglings, but you can still call either form with either form.

>> I don't see why it should be different for a single parameter function.
>> ...
> 
> I think you are making a strong point here.
> 
>> To put it another way, in your scheme, what is the benefit to overloading a single value function call with a function call that takes a single element tuple? When would this be useful?
>> ...
> 
> I agree that this is actually not useful.
> 
> Note that this means the following code will be accepted also:
> 
> void foo(int x,int y,int z){}
> 
> foo((1,2,3),);
> 
> Does this match your expectation?

Yes, that seems reasonable to me.

> 
>>>> Currently, I can call this:
>>>>
>>>> foo(T...)(T t) if (T.length == 1) // function that takes a single element tuple
>>>>
>>>> like this:
>>>>
>>>> foo(1);
>>>>
>>>> Why is this disallowed in your tuple scheme?
>>>> ...
>>>
>>> I take this to mean, why does the following code not compile:
>>>
>>> void foo(T)(T t) if(T.length == 1) { ... }
>>>
>>> foo(1);
>>
>> Nope, I meant my original. A "tuple" as D currently uses it, can have exactly one element, and I can call that function with exactly one value. I don't have to call it as:
>>
>> AliasSeq!(int) v;
>> v[0] = 1;
>> foo(v);
>>
>> Which is analogous to your requirements (obviously, D is missing the syntax for tuple literals, which is why it's complicated).
>>
>> Note that if foo is:
>>
>> foo(int x);
>>
>> I can still call it with v. I don't see why we can't keep these kinds of allowances.
>> ...
> 
> I see. Well, we can't keep them to the extent AliasSeq has them. AliasSeq always auto-expands. Auto-expansion for tuples can become a problem, especially in generic code, because it forgets structure information. For example:
> 
> void printElements(T)(T[] arr){
>      foreach(x;enumerate(a)){
>          print("at index ",x[0]," we have ",x[1]);
>      }
> }
> 
> auto a = [(1,2),(3,4),(5,6)];
> printElements(a);
> 
> With auto-expansion, this prints:
> at index 0, we have 1
> at index 1, we have 3
> at index 2, we have 5
> 
> However, it is quite clear from the definition of printElements that the programmer wanted it to print:
> 
> at index 0, we have (1,2)
> at index 1, we have (3,4)
> at index 2, we have (5,6)
> AliasSeq does not have this specific problem, because it cannot be put into an array without expanding.

Right, I was specifically focusing on what I understood -- the existing mechanism of "singleton tuple" in D.

But really, I think implicit expanding or tupling of values when needed would fix all the problems I had, fits into the current expectations of D users, and allows your scheme to work.

So to recap my thoughts:

1. I think T and (T,) can be different types, probably should be mangled differently, but implicitly cast to one another, similar to T and const(T) when T is a value type.
2. I'm OK with fun(T,T) and fun((T,T)) being equivalent.
3. I'm still not in love with (T,) as a singleton tuple type, it looks like you accidentally put in a trailing comma. But I don't have a better syntax to suggest.
4. I'm still convinced that having a template type match a tuple when multiple parameters are passed as in:

foo(T)(T t) {...}

foo(1, 2); // OK, T = (int, int)

is not going to go well with existing code.

On point 4, as a compromise, would it be possible to opt-in to such things via a new syntax? For example something like:

foo((T,...))(T t) // T is a tuple of 0 or more items.

One other thing, what does an empty tuple look like? Is it (,)?

-Steve
October 15, 2017
On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
> On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:
>> Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]).
>
> But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2] is a tuple literal or a dynamic array literal.

It would be a tuple if that's the best match, otherwise conversion to int[] is tried. Even today, [1, 2] is ambiguous: Is it a static or a dynamic array of int? Is it of type int[2] or int[]? The spec says, it depends what you do with it! We can progress that and enlarge the int[2] version to [int, int] -- a special case of a 2-tuple. It remains the same: If [1, 2] can be used as a dynamic array, it will be. If not, the compiler tries a static array. With tuples, it would try a tuple. If f has an overload taking int[] or something similar, it will treat [1, 2] as a dynamic array with homogeneus types. If the objects are not compatible, an error occurs like "tuple [..contents..] cannot be implicitly converted to T[]". Else, if it has an overload for a compatible (length, implicit conversion) tuple, that will be taken. Consider
   void f(int[2] v) { } // (1)
   void f(int[ ] v) { } // (2)
Here, f([1, 2]) calls (1) as it is the better match. Yet with
   auto x = [1, 2];
f(x) calls (2) because of strict typing. So while [1, 2] is of type int[2] or [int, int] as a tuple, typeof([1, 2]) will still yield int[]. You cannot ask the one and only correct type of []-literals as they have more than one type. Even if the values are incompatible like [1, "a"], asking typeof([1, "a"]) will result in an error, because in typeof deduction, []-literals must result in dynamic arrays. This holds for auto, because auto has the same rules.
   auto tup = [1, "a"];
must fail. You'd need
   [auto, auto] tup = [1, "a"];
or maybe some shorthand syntax that lowers to this.

> You'd need to use a prefix or something to the bracket syntax.
> [snip]

I just argued, you don't!

The reason there is no such prefix and not even a function in Phobos, is it is a trivial task to make one.
    T[n] s(T, size_t n)(T[n] elem ...) { return elem; }
    static assert(is(typeof(s(1, 2, 3)) == int[3]));
    static assert(is(typeof([1, 2, 3].s) == int[3]));
    auto x = s(1, 2, 3);
    static assert(is(typeof(x) == int[3]));
    auto x = s(1, 2.0, 3);
    static assert(is(typeof(x) == double[3]));
Try it yourself. It works fine. Instead of s one would use t or tuple to allow incompatible types.
October 16, 2017
On Sunday, 15 October 2017 at 15:19:21 UTC, Q. Schroll wrote:
> On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
>> On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:
>>> Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]).
>>
>> But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2] is a tuple literal or a dynamic array literal.
>
> It would be a tuple if that's the best match, otherwise conversion to int[] is tried.
> ...
>> You'd need to use a prefix or something to the bracket syntax.
>> [snip]
>
> I just argued, you don't!

But have you thought through all the implications?

Take this code:

void main(string[] args)
{
    import std.stdio : writeln;
    writeln([1, 3.14]);
}

As you're probably 100% aware, this is totally valid D code today.  [1, 3.14] becomes a double[] because 1 gets converted to a double.  If this kind of behaviour changes, code will break, so you'll need a bunch of exceptions to the "it would be a tuple if that's the best match" rule.  Also, for the same backwards compatibility reasons, it would be impractical in most cases to add any tuple overloads to most existing standard library functions that currently accept slices or arrays, but presumably new functions would be meant to take advantage of the new syntax (else there wouldn't be much point creating a new syntax).

So, a literal like [1, 3.14] would basically be a tuple, but would be converted to double[] in a bunch of special cases for historical reasons.

If you're not sure if this is really a problem, take a look at the confusion caused by the magic in {} syntax:

https://forum.dlang.org/thread/ecwfiderxbfqzjcyymkg@forum.dlang.org
https://forum.dlang.org/thread/ihsmxiplprxwlqkgwswc@forum.dlang.org
https://forum.dlang.org/thread/qsayoktyffczskrnmgxu@forum.dlang.org

To be totally honest, I still don't see what's wrong with just creating a new bracket syntax, instead of adding more magic to [] (or () for that matter).
October 29, 2017
On Monday, 16 October 2017 at 23:29:46 UTC, sarn wrote:
> On Sunday, 15 October 2017 at 15:19:21 UTC, Q. Schroll wrote:
>> On Saturday, 14 October 2017 at 23:20:26 UTC, sarn wrote:
>>> On Saturday, 14 October 2017 at 22:20:46 UTC, Q. Schroll wrote:
>>>> Therefore, and because of brackets, you can distinguish f(1, 2) from f([1, 2]).
>>>
>>> But in f([1, 2]), it's ambiguous (just by parsing) whether [1, 2] is a tuple literal or a dynamic array literal.
>>
>> It would be a tuple if that's the best match, otherwise conversion to int[] is tried.
>> ...
>>> You'd need to use a prefix or something to the bracket syntax.
>>> [snip]
>>
>> I just argued, you don't!
>
> But have you thought through all the implications?

Yes. No weirdness is being introduced that is not there already. Maybe I have overseen something; I will not give you or anyone else a guarantee for the solution to work perfectly. I've thought through the case very long.
An open question is allowing partly const/immutable/shared (cis) tuples. As for now, I didn't care. Even c/i/s-homogeneus tuples (the tuple is c/i/s as a whole or not) would be a win in my opinion. One rarely needs tuples with one component immutable but the other one mutable. This is what a named struct is for. On the other hand, I don't know of any issues having a to partly c/i/s std.typecons.Tuple.

> Take this code:
>
> void main(string[] args)
> {
>     import std.stdio : writeln;
>     writeln([1, 3.14]);
> }
>
> As you're probably 100% aware, this is totally valid D code today.  [1, 3.14] becomes a double[] because 1 gets converted to a double.

Right conclusion with insufficient explanation. [1, 3.14] is a static array in the first place. It occupies a fully inferred template parameter position. I don't know the implementation, but every time I tested, it behaves as if typeof(expr) is being used after the bang to set the template argument manually (even for Voldemort types etc. where typeof is sometimes impossible due to missing frame pointers). typeof returns "dynamic array of T" for array literals. This is all the weirdness going on here. It is present today and would remain present if you interpret [1, 3.14] as a tuple.

> If this kind of behaviour changes, code will break, so you'll need a bunch of exceptions to the "it would be a tuple if that's the best match" rule.

The only exception is typeof and (therefore, I don't know...) template inference.

> Also, for the same backwards compatibility reasons, it would be impractical in most cases to add any tuple overloads to most existing standard library functions that currently accept slices or arrays, but presumably new functions would be meant to take advantage of the new syntax (else there wouldn't be much point creating a new syntax).

You don't have to as long as you don't want to support tuples explicitly; otherwise you have to. If you have a void f(int, double), you cannot plug in [1, 3.14]. You can use some expand to do it. You wouldn't want to either. If you have something *explicitly typed* as a tuple, e.g.
    [int, double] tup = [1, 3.14];
you can make the call f(tup) because auto expansion does its job. This is the use case. If you have void f([int, double]), you can plug in tuple literals.
If you use a tuple literal for a function call, the compiler will search for explicit matches for tuples. If it cannot find any, conversion to a dynamic array happens.

> So, a literal like [1, 3.14] would basically be a tuple, but would be converted to double[] in a bunch of special cases for historical reasons.

Yes. It would be converted in almost all cases -- the same with static arrays -- because the best match doesn't occur very often and typeof never returns static arrays or tuples for literals.

> If you're not sure if this is really a problem, take a look at the confusion caused by the magic in {} syntax:
>
> https://forum.dlang.org/thread/ecwfiderxbfqzjcyymkg@forum.dlang.org
> https://forum.dlang.org/thread/ihsmxiplprxwlqkgwswc@forum.dlang.org
> https://forum.dlang.org/thread/qsayoktyffczskrnmgxu@forum.dlang.org

This is completely unrelated. Concerning the issues people have with (..) => { .. }, I've filed an enhancement request to deprecate it in that specific case: https://issues.dlang.org/show_bug.cgi?id=17951

> To be totally honest, I still don't see what's wrong with just creating a new bracket syntax, instead of adding more magic to [] (or () for that matter).

It's not adding any magic to [] that isn't there already. The other proposals are adding magic to (). Even some mathematicians use chevrons (angle brackets) for tuples as they see parentheses as indicators of precedence. I'd vote against angle brackets, see C++ templates for reasons. Logicians and haskellers even don't need parentheses for function calls.

Could I convince you?
1 2 3 4
Next ›   Last »