www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Template argument deduction from a function call question

reply "Dzugaru" <dzugaru gmail.com> writes:
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.
Apr 01 2015
next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
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.
Apr 01 2015
parent reply "Dzugaru" <dzugaru gmail.com> writes:
 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
Apr 01 2015
parent "John Colvin" <john.loughran.colvin gmail.com> writes:
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.
Apr 01 2015
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
Apr 01 2015
next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
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?
Apr 01 2015
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
Apr 01 2015
prev sibling parent reply "Dzugaru" <dzugaru gmail.com> writes:
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.
Apr 01 2015
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
Apr 01 2015
parent reply "Dzugaru" <dzugaru gmail.com> writes:
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.
Apr 01 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/1/15 2:46 PM, Dzugaru wrote:
 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.
I'd still put the template constraint in: if(is(E == ElementType!S)), because using = in the template parameter list does not constrain it to that type, it just gives a default. However, you could also change the function signature to allow more flexibility: auto aggregate(alias func, S, E = ElementType!S)(S list, E accum = E.init) if(is(typeof(accum = func(accum, ElementType!S.init)))) But then again, you can use reduce ;) -Steve
Apr 02 2015