Hi everyone,
Something I've long wanted in PHP has been built-in versions of some
common functional primitives, functions that operate on functions and
create new functions for you. I've finally gotten round to bringing this
up because my operator functions RFC could really benefit from them.
To be specific in what I mean by “functional primitives”, here are some
examples:
Function composition: a function that takes a function f($x) and a
function g($y) and returns a new function($x) { return g(f($x)); }. Thus
compose("trim", "strtoupper") returns a function that strips whitespace
from and capitalises a string. In practice it would make sense if f()
could any number of arguments.
Partial application: a function that takes a function and a set of
arguments, and returns a new function that calls that function with
those arguments, but also with any additional arguments passed. Thus, to
take an example from my operator functions RFC, papply("*", 2) returns a
function that multiplies a number by two. A question here is how to
decide the positions of the arguments: can I partially apply, say,
intdiv()
and set the second argument, then have the resulting function
be called with intdiv()
's first argument? Maybe it could take an array
specifying argument indicies, so papply("/", [1 => 2]) returns a
function that divides by two.
Currying: a function that takes a function and returns a new function
that takes the function's first parameter and returns another function
to take its second, which returns another function, and so on, until
eventually all the function's arguments have been taken and the
function's result is returned. So, curry("intdiv")(6)(2) is equivalent
to intdiv(6, 2), and curry("intdiv")(6) is equivalent to
papply("intdiv", 6).
Reversing the order of the arguments: a function that takes a function
and returns a new function that calls the function with whatever
arguments it is passed, but in reverse order. So, reverse("intdiv")(4,
- behaves like intdiv(2, 4).
The identity function: returns its argument. This function essentially
does nothing, but that's exactly the point, it can go in place of a
callback that could do something more sophisticated.
Constant function: a function that takes a value and returns a function
that returns that value. Like the identity function, the function this
returns is useless when called directly, but can have some use as a
callback.
This might not be an exhaustive list. There might be some other
functions of this kind that make sense, and suggestions would be
appreciated. Note that PHP has some functional primitives already
(array_map, array_reduce), so in a sense I'm just looking for what it's
missing.
If these functions were to be added, most of them would make sense as
both global functions and methods on Closure (think mysqli_* vs
$mysqli->*). The former makes sense when using callables which aren't
(neccessarily) closures, e.g. when composing built-in PHP functions
together. Of course, they could be just methods on Closure and have no
global function counterparts, but I think compose("trim", "strtoupper")
is much more appealing in practice than
Closure::fromCallable("trim")->compose(Closure::fromCallable("strtoupper")).
At that point you might as well just write the composition yourself as a
closure.
I'd like to acknowledge that of course almost all of these can be easily
written in plain PHP as userland functions, and indeed have been.
However, if PHP includes common operations like these in its standard
library, it increases the base language's expressive power. It also
means we can provide faster and edge-case-complete versions. For
example, while a basic implementation of function composition is five
lines or so of PHP code, a version that handles reflection and
references correctly is significantly longer and slower. If PHP has this
as a built-in function, it can be implemented more efficiently. This is
particularly important given this kind of higher-order function is often
used with operations like array_map()
which have a multiplicative effect
that can make their speed significant.
Please tell me your thoughts on this idea.
Thanks!
--
Andrea Faulds
https://ajf.me/