Skip to content

Instantly share code, notes, and snippets.

@syntacticsugar
Forked from stanistan/gist:3809177
Created December 12, 2012 23:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save syntacticsugar/4272674 to your computer and use it in GitHub Desktop.
Save syntacticsugar/4272674 to your computer and use it in GitHub Desktop.
<?php
/**
* Higher order functions in PHP
*
* Please let me know of any differences in the the uses and actual definitions of partial v. curry.
*/
/**
* Returns a partially applied function.
* Simplest usage:
* $map_trim = partial('array_map', 'trim');
* $map_trim([' a ', ' b']);
*
* @param callable $fn
* @param mixed ...
* @return callable
* @throws InvalidArgumentException
*/
function partial($fn)
{
if (!is_callable($fn)) {
throw new InvalidArgumentException('Given arg is not callable.');
}
$partial_args = array_slice(func_get_args(), 1);
return function() use($fn, $partial_args) {
return call_user_func_array($fn, array_merge($partial_args, func_get_args()));
};
}
/**
* Returns a curried function.
* If the function has indeterminate arguments, or optional arguments, the first arg
* to curry should be the number of arguments the function takes.
*
* For example, array_map potentially takes an infinite number of arguments,
* and using reflection we get: 2 required, 3 optional, but it can potentially be more.
*
* Example:
* $mapper = curry(2, 'array_map');
* $mapper = $mapper(); // will return a partially applied function (still awaiting args)
* $mapper = $mapper(); // will return a partially applied function (still awaiting args)
* $mapper = $mapper($some_func); // will return a partially applied function
* // with $some_func bound
* $mapper = $mapper(); // $some_func bound, still a partially applied function
* $mapped = $mapper($some_array); // evaluated map
*
* This is still pretty ugly in PHP, although if the feature to implement function
* dereferencing is added, the syntax will be nicer.
* @see https://wiki.php.net/rfc/fcallfcall
* Equivalent to above:
* $mapped = curry(2, 'array_map')()()($some_func)()($some_array);
*
* @param ... $num_args or $fn
* @param callable $fn
* @param ... mixed (args to bind)
* @return callable
* @throws InvalidArgumentException
*/
function curry()
{
$args = func_get_args();
$i = call_user_func(function() use($args) {
foreach ($args as $key => $a) {
if (is_callable($a)) {
return $key;
}
}
throw new InvalidArgumentException('No callable given to curry()');
});
$fn = $args[$i];
unset($args[$i]);
$args = array_values($args);
$get_fn_no_args = function($fn) {
$re = new ReflectionFunction($fn);
list($p, $r) = array(
$re->getNumberOfParameters(),
$re->getNumberOfRequiredParameters()
);
if ($p !== $r) {
throw new InvalidArgumentException(
'Specify the number of arguments expected in the first argument. '
. 'The given function has optional args.'
);
}
return $p;
};
$pars = ($i === 0) ? $get_fn_no_args($fn) : array_shift($args);
return function() use($fn, $pars, $args)
{
$args = array_merge($args, func_get_args());
return (count($args) >= $pars)
? call_user_func_array($fn, $args)
: call_user_func_array('curry', array_merge(array($pars, $fn), $args));
};
}
/**
* Returns a function which applies the given callables left to right
* Example:
* $print_trim = compose('trim', 'print_r');
* $print_trim('something');
*
* @param ... callable
* @return callable
* @throws InvalidArgumentException
*/
function compose()
{
$fns = func_get_args();
$cfns = array_filter($fns, 'is_callable');
if (!$fns || count($fns) !== count($cfns)) {
throw new InvalidArgumentException('
Invalid arguments to compose(). Excpeted callables.'
);
}
return function() use($fns) {
$args = func_get_args();
$re = call_user_func_array(array_shift($fns), $args);
foreach ($fns as $f) {
$re = $f($re);
}
return $re;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment