June 17, 2013
On 06/17/2013 02:32 AM, TommiT wrote:

> On Monday, 17 June 2013 at 07:20:23 UTC, Ali Çehreli wrote:
>>
>> The following does not answer the question of expanding but at least
>> foo() receives [30, 70, 110] :)
>>
>> import std.stdio;
>> import std.algorithm;
>> import std.array;
>> import std.range;
>>
>> int[] arr = [ 1, 3, 5, 7, 11 ];
>>
>> void foo(T)(T[] values...)
>> {
>>     writeln(values);
>> }
>>
>> void bar(T)(T[] values...)
>> {
>>     foo(arr
>>         .indexed(values)
>>         .map!(a => a * 10)
>>         .array);
>> }
>>
>> void main()
>> {
>>     bar(1, 3, 4);
>> }
>>
>> Ali
>
> Yeah, that would work. I'd hate the overhead though.

There is no inherent overhead though. I called .array only because I thought that the C++ version was eager. If we stay lazy and D-like, we can make foo() take a range:

import std.stdio;
import std.algorithm;
import std.range;

int[] arr = [ 1, 3, 5, 7, 11 ];

void foo(R)(R range)          // <-- now takes range
{
    writeln(range);
}

void bar(T)(T[] values...)
{
    foo(arr
        .indexed(values)
        .map!(a => a * 10));  // <-- no .array anymore
}

void main()
{
    bar(1, 3, 4);
}

I hear that ldc (and perhaps gdc) perform pretty magical compiler optimizations. There is no reason for the code above to be slower than the hand-written equivalent.

Ali

June 17, 2013
On 06/17/13 16:20, bearophile wrote:
> Artur Skawina:
> 
>> Yes, this is not as concise as '...' would be. But, with a bit more tuple support in the language, the '.tuple' part wouldn't be
>> necessary,
> 
> Implicit things are dangerous in languages.

Not sure what you mean.

"A bit more tuple support" means aot being able to return /real/ tuples from functions. This just needs to be specced; can be handled like I did in the ForEach example - by wrapping it in a struct, only internally. Then this becomes possible:

   auto ForEach(alias MAP, TS...)(TS ts) {
      NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;
      foreach (i, ref v; values)
         tuple[i] = MAP(v);
      return tuple;
   }
   void bar(T...)(T values) {
      foo(ForEach!(a=>arr[a]*10)(values));
   }

which is already much more readable. And 100% explicit.
While /it's only syntax sugar/, it does remove a lot of noise.

> ".tuple" can also be written "[]".

No idea what you mean by this.

artur
June 17, 2013
Artur Skawina:

>> ".tuple" can also be written "[]".
>
> No idea what you mean by this.

If in the code you wrote you replace the first ".tuple" with "[]" the code keeps working.

Bye,
bearophile
June 17, 2013
On 06/17/13 23:11, bearophile wrote:
> Artur Skawina:
> 
>>> ".tuple" can also be written "[]".
>>
>> No idea what you mean by this.
> 
> If in the code you wrote you replace the first ".tuple" with "[]" the code keeps working.

It does not - I really have no idea what you mean; slicing a struct
does not (and can not) produce an auto-expanding tuple, unless I'm missing
some recent language change...

artur
June 18, 2013
On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
>
> Another solution would be to have the following hidden in some lib:
>
>    struct _ForEach(alias MAP, TS...) {
>       NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;
>
>       this(TS values) {
>          foreach (i, ref v; values)
>             tuple[i] = MAP(v);
>       }
>    }
>
>    auto ForEach(alias MAP, TS...)(TS ts) {
>       return _ForEach!(MAP, TS)(ts);
>    }
> 
> Then 'bar' becomes just:
>
>    void bar(T...)(T values) {
>       foo(ForEach!(a=>arr[a]*10)(values).tuple);
>    }
>
> Yes, this is not as concise as '...' would be. But, with a bit more
> tuple support in the language, the '.tuple' part wouldn't be
> necessary, and then it's just
>
>       foo(ForEach!(a=>arr[a]*10)(values));
> vs
>       foo((arr[values] * 10)...);
>
> artur

Now, this is pretty cool. But I wonder a couple of things:
1) What kind of an impact does this have on compilation times compared to having this new ellipsis syntax which would allow the compiler to do a simple rewrite.
2) I wonder if the compiler can optimize that _ForEach struct away.
June 18, 2013
On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
> [..]

Your setup has a pretty serious issue with correctness though. It's because all the types of _ForEach.tuple are the same as the first element of TS...

import std.stdio;

template NTup(size_t N, T...)
{
    static if (N > 1)
        alias NTup = NTup!(N-1, T, T[$-1]);
    else
        alias NTup = T;
}

struct _ForEach(alias MAP, TS...)
{
    NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;

    this(TS values)
    {
        foreach (i, ref v; values)
            tuple[i] = MAP(v);
    }
}

auto ForEach(alias MAP, TS...)(TS ts)
{
    return _ForEach!(MAP, TS)(ts);
}

void foo(T...)(T values)
{
    foreach (v; values)
        writeln(v);
}

void bar(T...)(T values)
{
    foo(ForEach!(a => a + 1)(values).tuple);
}


void main()
{
    bar(10, 3_000_000_000u);
}
-----
Prints:
11
-1294967295

Change the call to bar(1, 3L); and it wouldn't even compile.
June 18, 2013
On Tuesday, 18 June 2013 at 02:37:46 UTC, TommiT wrote:
> It's because all the types of _ForEach.tuple are the same as the first element of TS...

I mean... the same as the type of MAP(TS[0])
June 18, 2013
On 06/18/13 03:51, TommiT wrote:
> On Monday, 17 June 2013 at 13:59:34 UTC, Artur Skawina wrote:
>>
>>    struct _ForEach(alias MAP, TS...) {
>>       NTup!(TS.length, typeof(MAP(TS[0].init))) tuple;
>>
>>       this(TS values) {
>>          foreach (i, ref v; values)
>>             tuple[i] = MAP(v);
>>       }
>>    }
>>
>>    auto ForEach(alias MAP, TS...)(TS ts) {
>>       return _ForEach!(MAP, TS)(ts);
>>    }
>>
>>    void bar(T...)(T values) {
>>       foo(ForEach!(a=>arr[a]*10)(values).tuple);
>>    }

> Now, this is pretty cool. But I wonder a couple of things:
> 1) What kind of an impact does this have on compilation times compared to having this new ellipsis syntax which would allow the compiler to do a simple rewrite.

Not a significant one, I'd expect; for simple cases like these it shouldn't make much difference.

> 2) I wonder if the compiler can optimize that _ForEach struct away.

Yes.


> Change the call to bar(1, 3L); and it wouldn't even compile.

> It's because all the types of _ForEach.tuple are the same as the first element of TS...
> 
> I mean... the same as the type of MAP(TS[0])

That was enough to handle the original problem, iirc.
Making the code work for heterogeneous args (and mapping functions) is
trivial, though:

   import std.stdio;

   template _TypeMap(alias MAP, size_t N, TS...) {
       static if (N<TS.length)
           alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], typeof(MAP(TS[N].init), TS[N..$]));
       else
           alias _TypeMap = TS;
   }
   template TypeMap(alias MAP, TS...) {
      alias TypeMap = _TypeMap!(MAP, 0, TS);
   }

   struct _ForEach(alias MAP, TS...)
   {
       TypeMap!(MAP, TS) tuple;

       this(TS values)
       {
           foreach (i, ref v; values)
               tuple[i] = MAP(v);
       }
   }

   auto ForEach(alias MAP, TS...)(TS ts)
   {
       return _ForEach!(MAP, TS)(ts);
   }

   void foo(T...)(T values)
   {
       foreach (v; values)
           writeln(v);
   }

   void bar(T...)(T values)
   {
       foo(ForEach!(a => a + 1)(values).tuple);
   }

   void main()
   {
       bar(10, 3_000_000_000u, 2.14, -43L);
   }


artur
June 18, 2013
Artur Skawina:

> slicing a struct
> does not (and can not) produce an auto-expanding tuple, unless I'm missing
> some recent language change...

Sorry, I have misunderstood the type, the [] works on typetuples.

Bye,
bearophile
June 18, 2013
On Tuesday, 18 June 2013 at 10:57:00 UTC, Artur Skawina wrote:
> On 06/18/13 03:51, TommiT wrote:
>> Change the call to bar(1, 3L); and it wouldn't even compile.
>> It's because all the types of _ForEach.tuple are the same as the first element of TS...
>> 
>> I mean... the same as the type of MAP(TS[0])
>
> That was enough to handle the original problem, iirc.
> Making the code work for heterogeneous args (and mapping functions) is
> trivial, though:
>
>    import std.stdio;
>
>    template _TypeMap(alias MAP, size_t N, TS...) {
>        static if (N<TS.length)
>            alias _TypeMap = _TypeMap!(MAP, N+1, TS[0..N], typeof(MAP(TS[N].init), TS[N..$]));
>        else
>            alias _TypeMap = TS;
>    }
>    template TypeMap(alias MAP, TS...) {
>       alias TypeMap = _TypeMap!(MAP, 0, TS);
>    }
>
>    struct _ForEach(alias MAP, TS...)
>    {
>        TypeMap!(MAP, TS) tuple;
>
>        this(TS values)
>        {
>            foreach (i, ref v; values)
>                tuple[i] = MAP(v);
>        }
>    }
>
>    auto ForEach(alias MAP, TS...)(TS ts)
>    {
>        return _ForEach!(MAP, TS)(ts);
>    }
>
>    void foo(T...)(T values)
>    {
>        foreach (v; values)
>            writeln(v);
>    }
>
>    void bar(T...)(T values)
>    {
>        foo(ForEach!(a => a + 1)(values).tuple);
>    }
>
>    void main()
>    {
>        bar(10, 3_000_000_000u, 2.14, -43L);
>    }
>
>
> artur

Okay, you really seem to have figured this stuff out. I think I have difficulty seeing the solution because I'm used to C++ parameter packs, which have much less functionality than parameter tuples in D + there's no static if. But I learned a lot from this, thanks for that.