Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:100739 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 75556 invoked from network); 21 Sep 2017 19:14:12 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 21 Sep 2017 19:14:12 -0000 X-Host-Fingerprint: 95.145.228.130 unknown Received: from [95.145.228.130] ([95.145.228.130:8362] helo=localhost.localdomain) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 07/E6-62331-38F04C95 for ; Thu, 21 Sep 2017 15:14:11 -0400 Message-ID: <07.E6.62331.38F04C95@pb1.pair.com> To: internals@lists.php.net X-Mozilla-News-Host: news://news.php.net:119 Date: Thu, 21 Sep 2017 20:14:09 +0100 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:49.0) Gecko/20100101 Firefox/49.0 SeaMonkey/2.46 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Posted-By: 95.145.228.130 Subject: Concept: built-in functions for common functional primitives (composition, partial application, etc.) From: ajf@ajf.me (Andrea Faulds) 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, 2) 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/