Jump to page: 1 2
Thread overview
Template argument deduction from a function call question
Apr 01, 2015
Dzugaru
Apr 01, 2015
John Colvin
Apr 01, 2015
Dzugaru
Apr 01, 2015
John Colvin
Apr 01, 2015
Ali Çehreli
Apr 01, 2015
John Colvin
Apr 01, 2015
Ali Çehreli
Apr 01, 2015
Dzugaru
Apr 01, 2015
Ali Çehreli
Apr 01, 2015
Dzugaru
April 01, 2015
Following recent IRC discussion.

I want to write a generic list aggregate function that works with builtin types like int[] as well as custom classes/structs that define front, empty, popFront:

import std.range;

ElementType!S aggregate(alias func, S)(S list, ElementType!S accum = ElementType!S.init)
if(is(typeof(func(accum, accum)) == typeof(accum))) {	
    foreach(ref e; list) {
        accum = func(accum, e);
    }
    return accum;	
}

Now I use it:

auto min1 = aggregate!((a,  b) { return a < b ? a : b; }, int[])([2,4,1,3,5], int.max);
auto min2 = aggregate!((a,  b) { return a < b ? a : b; }, MyRange)(new MyRange(), int.max);

That works ok.

Now I don't want to specify template argument S, it can be deduced from the function call, right?

auto min1 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5], int.max);

Doesn't work! "Error: template math.aggregate cannot deduce function from argument types..."

Now try this:
//No second parameter - leave it to be default
auto sum = aggregate!((a,  b) { return a + b; })([2,4,1,3,5]);

And it deduces S just fine.


April 01, 2015
On Wednesday, 1 April 2015 at 17:57:12 UTC, Dzugaru wrote:
> Following recent IRC discussion.
>
> I want to write a generic list aggregate function that works with builtin types like int[] as well as custom classes/structs that define front, empty, popFront:
>
> import std.range;
>
> ElementType!S aggregate(alias func, S)(S list, ElementType!S accum = ElementType!S.init)
> if(is(typeof(func(accum, accum)) == typeof(accum))) {	
>     foreach(ref e; list) {
>         accum = func(accum, e);
>     }
>     return accum;	
> }
>
> Now I use it:
>
> auto min1 = aggregate!((a,  b) { return a < b ? a : b; }, int[])([2,4,1,3,5], int.max);
> auto min2 = aggregate!((a,  b) { return a < b ? a : b; }, MyRange)(new MyRange(), int.max);
>
> That works ok.
>
> Now I don't want to specify template argument S, it can be deduced from the function call, right?
>
> auto min1 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5], int.max);
>
> Doesn't work! "Error: template math.aggregate cannot deduce function from argument types..."
>
> Now try this:
> //No second parameter - leave it to be default
> auto sum = aggregate!((a,  b) { return a + b; })([2,4,1,3,5]);
>
> And it deduces S just fine.

a)
isn't this almost, if not exactly, the same as std.algorithm.reduce?

b)
you can write nice things like this:
auto min = [2,4,1,3,5].aggregate!((a,  b) => a < b ? a : b)(int.max);

c)
the deduction failure looks like a bug to me, perhaps there is a good reason why it can't work in the general case.
April 01, 2015
On 04/01/2015 10:57 AM, Dzugaru wrote:

> ElementType!S aggregate(alias func, S)(S list, ElementType!S accum =
> ElementType!S.init)
> if(is(typeof(func(accum, accum)) == typeof(accum))) {
[...]
> }

I can't explain exactly why that doesn't work.

However, I've discovered a number of times that reducing the dependency between template parameters helps. Instead of using ElementType!S in the parameter list, introduce a third one (E), which you check in the template constraint:

ElementType!S aggregate(alias func, S, E)(S list, E accum = E.init)
if(is (E == ElementType!S) &&                   // <-- ADDED
    is(typeof(func(accum, accum)) == typeof(accum))) {
        // ...
}

Now it works.

Ali

April 01, 2015
On Wednesday, 1 April 2015 at 18:13:15 UTC, Ali Çehreli wrote:
> On 04/01/2015 10:57 AM, Dzugaru wrote:
>
> > ElementType!S aggregate(alias func, S)(S list, ElementType!S
> accum =
> > ElementType!S.init)
> > if(is(typeof(func(accum, accum)) == typeof(accum))) {
> [...]
> > }
>
> I can't explain exactly why that doesn't work.
>
> However, I've discovered a number of times that reducing the dependency between template parameters helps. Instead of using ElementType!S in the parameter list, introduce a third one (E), which you check in the template constraint:
>
> ElementType!S aggregate(alias func, S, E)(S list, E accum = E.init)
> if(is (E == ElementType!S) &&                   // <-- ADDED
>     is(typeof(func(accum, accum)) == typeof(accum))) {
>         // ...
> }
>
> Now it works.
>
> Ali

Great tip. Is that in your book somewhere?
April 01, 2015
> a)
> isn't this almost, if not exactly, the same as std.algorithm.reduce?
>
> b)
> you can write nice things like this:
> auto min = [2,4,1,3,5].aggregate!((a,  b) => a < b ? a : b)(int.max);
>
> c)
> the deduction failure looks like a bug to me, perhaps there is a good reason why it can't work in the general case.

a) Had a look at std.algorithm.reduce, looks like this is what I did, but there are 2 overloads inside a template block instead of parameter with a default value (also less strictly typed):

template reduce(fun...) {
auto reduce(R)(R r)
auto reduce(S, R)(S seed, R r)
}

The code there though is mindboggling :)

b) Thanks, forgot about that

April 01, 2015
On 04/01/2015 11:15 AM, John Colvin wrote:

>> Instead of using
>> ElementType!S in the parameter list, introduce a third one (E), which
>> you check in the template constraint:
>>
>> ElementType!S aggregate(alias func, S, E)(S list, E accum = E.init)
>> if(is (E == ElementType!S) &&                   // <-- ADDED
>>     is(typeof(func(accum, accum)) == typeof(accum))) {
>>         // ...
>> }
>>
>> Now it works.
>>
>> Ali
>
> Great tip. Is that in your book somewhere?

I don't think so; not that one... (Noted though; I may get to it.)

I had carefully tried to avoid adding "too much" information. However, as the book progressed added more and more information like that, especially after Luís Marques's recommendations. I am very grateful for the effort he has put into reviewing and editing the book.

Ali

April 01, 2015
On Wednesday, 1 April 2015 at 18:13:15 UTC, Ali Çehreli wrote:
> On 04/01/2015 10:57 AM, Dzugaru wrote:
>
> > ElementType!S aggregate(alias func, S)(S list, ElementType!S
> accum =
> > ElementType!S.init)
> > if(is(typeof(func(accum, accum)) == typeof(accum))) {
> [...]
> > }
>
> I can't explain exactly why that doesn't work.
>
> However, I've discovered a number of times that reducing the dependency between template parameters helps. Instead of using ElementType!S in the parameter list, introduce a third one (E), which you check in the template constraint:
>
> ElementType!S aggregate(alias func, S, E)(S list, E accum = E.init)
> if(is (E == ElementType!S) &&                   // <-- ADDED
>     is(typeof(func(accum, accum)) == typeof(accum))) {
>         // ...
> }
>
> Now it works.
>
> Ali

This code does work when you provide second (non-default) argument to function, and doesn't if you do not (no way it can deduce E solely from checks I assume).

My version, in constract, works when you do not provide second argument and doesn't if you do.
April 01, 2015
On Wednesday, 1 April 2015 at 18:20:41 UTC, Dzugaru wrote:
>> a)
>> isn't this almost, if not exactly, the same as std.algorithm.reduce?
>>
>> b)
>> you can write nice things like this:
>> auto min = [2,4,1,3,5].aggregate!((a,  b) => a < b ? a : b)(int.max);
>>
>> c)
>> the deduction failure looks like a bug to me, perhaps there is a good reason why it can't work in the general case.
>
> a) Had a look at std.algorithm.reduce, looks like this is what I did, but there are 2 overloads inside a template block instead of parameter with a default value (also less strictly typed):
>
> template reduce(fun...) {
> auto reduce(R)(R r)
> auto reduce(S, R)(S seed, R r)
> }
>
> The code there though is mindboggling :)

Yeah it's not the easiest to understand at first glance. The big differences are that it works with multiple functions in one pass, has it's seed and range the wrong way around for UFCS (grrrr!!!) and works with any seed type that can have the result of the function(s) assigned to it, which is a lot more flexible than insisting on using the element type.*

*One really obvious example is doing a sum in to a larger variable so as to avoid overflow.
April 01, 2015
On 04/01/2015 11:27 AM, Dzugaru wrote:

> This code does work when you provide second (non-default) argument to
> function, and doesn't if you do not (no way it can deduce E solely from
> checks I assume).
>
> My version, in constract, works when you do not provide second argument
> and doesn't if you do.

Another attempt:

import std.range;

ElementType!S
aggregate(alias func, S, E = ElementType!S)(S list, E accum = E.init)
    if(is(typeof(func(accum, accum)) == typeof(accum)))
{
    foreach(ref e; list) {
        accum = func(accum, e);
    }

    return accum;
}

void main()
{
    auto min1 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5], int.max);
    auto min2 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5]);

    assert(min1 == 1);
    assert(min2 == 0);
}

Ali

April 01, 2015
On Wednesday, 1 April 2015 at 18:37:24 UTC, Ali Çehreli wrote:
> On 04/01/2015 11:27 AM, Dzugaru wrote:
>
> > This code does work when you provide second (non-default)
> argument to
> > function, and doesn't if you do not (no way it can deduce E
> solely from
> > checks I assume).
> >
> > My version, in constract, works when you do not provide
> second argument
> > and doesn't if you do.
>
> Another attempt:
>
> import std.range;
>
> ElementType!S
> aggregate(alias func, S, E = ElementType!S)(S list, E accum = E.init)
>     if(is(typeof(func(accum, accum)) == typeof(accum)))
> {
>     foreach(ref e; list) {
>         accum = func(accum, e);
>     }
>
>     return accum;
> }
>
> void main()
> {
>     auto min1 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5], int.max);
>     auto min2 = aggregate!((a,  b) { return a < b ? a : b; })([2,4,1,3,5]);
>
>     assert(min1 == 1);
>     assert(min2 == 0);
> }
>
> Ali

This is perfect, many thanks. Didn't know I can use "=" in template parameter list.
« First   ‹ Prev
1 2