Why does compose from std.functional return a templated function
Simen Kjærås
simen.kjaras at gmail.com
Wed Sep 16 11:26:09 UTC 2020
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
More information about the Digitalmars-d-learn
mailing list