www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why does compose from std.functional return a templated function

reply Jan =?UTF-8?B?SMO2bmln?= <hrominium gmail.com> writes:
I have toyed with the compose template in std.functional and ran 
into some problems.
rikki_cattermole on discord helped me a lot to solve my problem.

However, what still remains (for me) to understand is why.

Source code for `compose`: 
https://github.com/dlang/phobos/blob/master/std/functional.d#L1161



`compose` pipes together given functions. And returns a TEMPLATED 
function.
Why does it return a templated function? At compile-time, compose 
definitly knows, what kinds of function it composes together. So 
with std.traits, it could put there a definitve type, depending 
on the given function(s).

I somewhat see, that inside compose itself, this probably solves 
some issues with casting (maybe).
However, the last composition, i.e. the one which is returned, 
does not need to be templated, since it is known, what parameter 
has the last function.

In my case, I failed to understand, that it returns a 
non-initialized template function, which lead into compile 
problems.

In general I can imagine that this leads to weird compile errors, 
which are hard to understand. (types, casting, etc.)


My main question is why? Is there something, which I am missing, 
that explains, why it is beneficial to return a templated 
function?

(maybe, because I might want to compose together templated 
non-initialized functions?)
Sep 16 2020
next sibling parent reply Daniel Kozak <kozzi11 gmail.com> writes:
On Wed, Sep 16, 2020 at 12:00 PM Jan H=C3=B6nig via Digitalmars-d-learn <
digitalmars-d-learn puremagic.com> wrote:

 ...

 My main question is why? Is there something, which I am missing,
 that explains, why it is beneficial to return a templated
 function?

 (maybe, because I might want to compose together templated
 non-initialized functions?)
It has to be templated because than you can alias it and use it many times something like import std.stdio; import std.functional : compose; import std.algorithm.comparison : equal; import std.algorithm.iteration : map; import std.array : split, array; import std.conv : to; alias StrArrToIntArr =3D compose!(array,map!(to!int), split); void main() { auto str1 =3D "2 4 8 9"; int[] intArr =3D StrArrToIntArr(str1); } If compose would not be template it would need to store functions addresses so it would need to have some array of functions, this would be ineffective and need to use GC
Sep 16 2020
parent Jan =?UTF-8?B?SMO2bmln?= <hrominium gmail.com> writes:
On Wednesday, 16 September 2020 at 10:50:06 UTC, Daniel Kozak 
wrote:
 On Wed, Sep 16, 2020 at 12:00 PM Jan Hönig via 
 Digitalmars-d-learn < digitalmars-d-learn puremagic.com> wrote:

 ...

 My main question is why? Is there something, which I am 
 missing, that explains, why it is beneficial to return a 
 templated function?

 (maybe, because I might want to compose together templated
 non-initialized functions?)
It has to be templated because than you can alias it and use it many times something like import std.stdio; import std.functional : compose; import std.algorithm.comparison : equal; import std.algorithm.iteration : map; import std.array : split, array; import std.conv : to; alias StrArrToIntArr = compose!(array,map!(to!int), split); void main() { auto str1 = "2 4 8 9"; int[] intArr = StrArrToIntArr(str1); } If compose would not be template it would need to store functions addresses so it would need to have some array of functions, this would be ineffective and need to use GC
Right, if i give it non-initialized templated functions, it makes a lot of sense to return a template function as well. But for functions without templates? Probably not a frequent usecase I guess.
Sep 16 2020
prev sibling next sibling parent Daniel Kozak <kozzi11 gmail.com> writes:
On Wed, Sep 16, 2020 at 12:50 PM Daniel Kozak <kozzi11 gmail.com> wrote:

 On Wed, Sep 16, 2020 at 12:00 PM Jan H=C3=B6nig via Digitalmars-d-learn <
 digitalmars-d-learn puremagic.com> wrote:

 ...

 My main question is why? Is there something, which I am missing,
 that explains, why it is beneficial to return a templated
 function?

 (maybe, because I might want to compose together templated
 non-initialized functions?)
(maybe, because I might want to compose together templated non-initialized functions?) Yes
Sep 16 2020
prev sibling parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, 16 September 2020 at 09:59:59 UTC, Jan Hönig wrote:
 I have toyed with the compose template in std.functional and 
 ran into some problems.
 rikki_cattermole on discord helped me a lot to solve my problem.

 However, what still remains (for me) to understand is why.

 Source code for `compose`: 
 https://github.com/dlang/phobos/blob/master/std/functional.d#L1161



 `compose` pipes together given functions. And returns a 
 TEMPLATED function.
 Why does it return a templated function? At compile-time, 
 compose definitly knows, what kinds of function it composes 
 together. So with std.traits, it could put there a definitve 
 type, depending on the given function(s).

 I somewhat see, that inside compose itself, this probably 
 solves some issues with casting (maybe).
 However, the last composition, i.e. the one which is returned, 
 does not need to be templated, since it is known, what 
 parameter has the last function.

 In my case, I failed to understand, that it returns a 
 non-initialized template function, which lead into compile 
 problems.

 In general I can imagine that this leads to weird compile 
 errors, which are hard to understand. (types, casting, etc.)


 My main question is why? Is there something, which I am 
 missing, that explains, why it is beneficial to return a 
 templated function?

 (maybe, because I might want to compose together templated 
 non-initialized functions?)
It's perfectly possible to compose templated functions without wanting to specify the template parameters, and not allowing this would significantly hamper compose's usability. The other complication is overloads. Here's a version of compose that generates the correct overloads: template getOverloads(alias fn) { static if (__traits(compiles, __traits(getOverloads, __traits(parent, fn), __traits(identifier, fn), true))) { alias getOverloads = __traits(getOverloads, __traits(parent, fn), __traits(identifier, fn), true); } else { alias getOverloads = fn; } } template compose(funs...) if (funs.length > 0) { static foreach (overload; getOverloads!(funs[$-1])) { static if (__traits(isTemplate, overload)) { auto compose(Args...)(Args args) { static if (funs.length == 1) { return overload(args); } else { return funs[0](.compose!(funs[1..$])(args)); } } } else { import std.traits : Parameters; auto compose(Parameters!overload args) { static if (funs.length == 1) { return overload(args); } else { return funs[0](.compose!(funs[1..$])(args)); } } } } } As you can see, this is *a lot* more complex than the version in std.functional. The benefit is you can take the address of it easily. Here's how you can do the same with std.functional.compose: import std.functional : compose; import std.meta : Instantiate; unittest { auto sun = &Instantiate!(compose!(fun, gun), int); sun(3); } void fun(T)(T t) { } int gun(int t) { return t; } I will argue the latter is an acceptable cost of avoiding the dense, bug-prone monstrosity of the former. -- Simen
Sep 16 2020