Request for Comments: Currying
- Version: 0.9.1
- Date: 2011-06-11
- Author: Lars Strojny lstrojny@php.net
- Status: Draft
Introduction
Currying is a way to transform a function taking multiple arguments into nested functions so that the outmost function takes a single argument.
Currying, Schmurrying?
Think about passing a callback to a function. Let's find out if a list of characters are part of “foo”
:
$chars = array("o", "f", "b"); array_map($chars, function($char) { return strpos('foo', $char); });
A shorter form (with currying) would be:
$chars = array("o", "f", "b"); array_map($chars, curry_left('strpos', 'foo'));
The imaginative function curry_left()
here returns an anonymous function which takes a single parameter:
<?php function curry_left($callable) { $outerArguments = func_get_args(); array_shift($outerArguments); return function() use ($callable, $outerArguments) { return call_user_func_array($callable, array_merge($outerArguments, func_get_args())); }; } function curry_right($callable) { $outerArguments = func_get_args(); array_shift($outerArguments); return function() use ($callable, $outerArguments) { return call_user_func_array($callable, array_merge(func_get_args(), $outerArguments)); }; }
Let's demonstrate the use of curry_right()
on a slightly different example: find out if character “f”
is in a list of word:
$words = array('foo', 'bar', 'baz'); array_map($words, curry_right('strpos', 'a'));
Proposal
PHP has a long standing tradition of not providing a purists implementation of a certain feature. This is why I propose currying as a syntax enhancement in a way I feel even people new to functional'ish programming can understand what's going on. The proposed syntax is not strictly currying but partial function application.
The words example from aboe with currying as a syntax:
$words = array('foo', 'bar', 'baz'); array_map($words, curry strpos(..., 'a'));
The char example:
$chars = array("o", "f", "b"); array_map($chars, curry strpos(..., 'foo'));
We may spot, that two new tokens are introduced, T_CURRY
and T_FILL
(“...”). The approach here is to make currying as explicit as possible (syntax wise) and not fall on the purists edge. For example we allow curry to return functions with two parameters. Example:
$func = curry str_replace(..., ..., "foobar"); $func('foo', 'bar'); // barbar $func('foo', 'baz'); // bazbar
Curried functions can be curried again:
$func = curry str_replace(..., ..., "foobar"); $func = curry $func(..., "baz"); $func("foo"); // bazbar $func("bar"); // foobaz
Implementation details
Keyword curry
would be replaced during parsing stage with an closure. A few examples how this transformation would work:
$func = curry strpos(..., "f");
Would result in this code:
$func = function($arg1) { return strpos($arg1, "f"); };
Another example with a variable function name:
$funcName = "strpos"; $func = curry $funcName(..., "f");
This would result in:
$funcName = "strpos"; $func = function($arg1) use ($funcName) { return $funcName($arg1, "f"); };
Example with variable arguments:
$char = "f"; $func = curry strpos(..., $char);
$char = "f"; $func = function($arg1) use ($char) { return strpos($arg1, $char); };
Pitfalls and criticism
Performance
How is performance affected because of the heavy use of Closure objects? I don’t know yet, any guesses?
Error Handling
Error messages could be misleading. E.g. not passing an argument to $func()
would result in a warning for a missing argument when calling $func()
without any mention of strpos()
. One way to overcome this problem would be to have class CurriedFunction extends Closure
. This subclass would contain additional properties for a nicer the error message. It would even be possible to override error handling for CurriedFunction::__invoke()
to make it more specific.
- Bad error message:
Missing argument 1 for {closure}(), called in <file> on line <line> and defined in <file> on line <line>
- Better error message:
Missing argument 1 for curried strpos(..., “f”), called in <file> on line <line> and defined in <file> on line <line>
Error handling for curried functions should be a little more strict in terms of “too many parameters”. If somebody passed more parameters than defined in the curry statement, a warning should be thrown stating, that the additional parameters are ignored.
Notes
- I could use some helping hand with the parser work, if you are interested, drop me a message
References
- Wikipedia: http://en.wikipedia.org/wiki/Currying
- PEP-0309: http://www.python.org/dev/peps/pep-0309/
Changelog
- 0.9: Initial draft
- 0.9.1: RFC changed based on feedback from php-internals and #pecl.php
- Removed alias
schoenfinkel
:( - Discussing implementation details and error handling