How to Curry Method Calls in PHP

Is it possible to curry method calls in PHP?

As of php 5.3 you can store an anonymous function in a variable. This anonymous function can call the "original" function with some predefined parameters.

function foo($x, $y, $z) {
echo "$x - $y - $z";
}

$bar = function($z) {
foo('A', 'B', $z);
};

$bar('C');

edit: You can also use a closure to parametrise the creation of the anonymous function

function foo($x, $y, $z) {
echo "$x - $y - $z";
}

function fnFoo($x, $y) {
return function($z) use($x,$y) {
foo($x, $y, $z);
};
}

$bar = fnFoo('A', 'B');
$bar('C');

edit2: This also works with objects

class Foo {
public function bar($x, $y, $z) {
echo "$x - $y - $z";
}
}

function fnFoobar($obj, $x, $z) {
return function ($y) use ($obj,$x,$z) {
$obj->bar($x, $y, $z);
};
}

$foo = new Foo;
$bar = fnFoobar($foo, 'A', 'C');
$bar('B');

But the other suggestions using __call() and a wrapper class may be better if you want to "enhance" a complete class.

Currying in_array() in PHP

Well, it's relatively straightforward in PHP - almost the same as in any other language that treats functions as values. For example:

function create_search_by_array($arr) {
return function($needle) use ($arr) {
return in_array($needle, $arr);
};
}

$search_in_1_to_10 = create_search_by_array(range(1, 10));
var_dump($search_in_1_to_10(1)); // true
var_dump($search_in_1_to_10(10)); // true
var_dump($search_in_1_to_10(11)); // false

The only caveat here is use ($arr) construct: without it, the inner function won't be able to see the corresponding variable from an outer scope.

why should one prefer call_user_func_array over regular calling of function?

  1. You have an array with the arguments for your function which is of indeterminate length.

    $args = someFuncWhichReturnsTheArgs();

    foobar( /* put these $args here, you do not know how many there are */ );

    The alternative would be:

    switch (count($args)) {
    case 1:
    foobar($args[0]);
    break;
    case 2:
    foobar($args[0], $args[1]);
    break;
    ...
    }

    Which is not a solution.

The use case for this may be rare, but when you come across it you need it.

Using curry to export dependencies to foreach

Ok, figured this out. While functional::Curry, for instance, doesn't inject dependencies into the resulting curried function, purrr::partial does, apparently. Also, CodeDepends is very helpful!

How to write a function that could be called like func(a)(b)(c) in php?

What you're talking about is called Currying. The following code will require PHP 7, since it involves invoking a function returned from another one, which wasn't possible until PHP's Abstract Syntax Tree was implemented in that version.

First things first, you'll need a new sum() function that can operate on an arbitrary number of variables:

$sum = function(...$args)  { return array_sum($args); };

Secondly, the important part. A function that returns a new anonymous function, accumulating the arguments as it goes. When you finally pass it something callable (either your $sum function, or a built-in function name like pow), it'll execute it, unpacking the arguments that it's built up.

function calc($x)
{
return function($y = null) use ($x)
{
if (is_callable($y)) {
return $y(...$x);
} else {
$args = (array) $x;
$args[] = $y;
return calc($args);
}
};
}

echo calc(5)(3)(2)($sum); // 10
echo calc(1)(2)($sum); // 3
echo calc(2)(3)('pow'); // 8

See https://3v4l.org/r0emm

(Note that internal functions will be limited to operating on the number of arguments they are defined to take - calc(2)(3)(4)('pow') will raise an error.)

This isn't a particularly common pattern to use (which is probably why you've found it hard to track down), so please for everyone who reads it's sake, think carefully about where you use it.

Credit to the curryAdd answer in this question for the starting blocks.

Similar curry functions producing different results

If you change filter to use xs.filter(x => f(x)) instead of xs.filter(f) it will work -

const filter = curry((f, xs) => xs.filter(x => f(x)))

// ...

console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]

The reason for this is because Array.prototype.filter passes three (3) arguments to the "callback" function,

  • callback - Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:

    • element - The current element being processed in the array.
    • index (Optional) - The index of the current element being processed in the array.
    • array (Optional) - The array filter was called upon.

The f you are using in filter is match(/q/i), and so when it is called by Array.prototype.filter, you are getting three (3) extra arguments instead of the expected one (1). In the context of curry, that means a.length will be four (4), and since 4 === fn.length is false (where fn.length is 2), the returned value is curry(fn, a), which is another function. Since all functions are considered truthy values in JavaScript, the filter call returns all of the input strings.

// your original code:
xs.filter(f)

// is equivalent to:
xs.filter((elem, index, arr) => f(elem, index, arr))

By changing filter to use ...filter(x => f(x)), we only allow one (1) argument to be passed to the callback, and so curry will evaluate 2 === 2, which is true, and the return value is the result of evaluating match, which returns the expected true or false.

// the updated code:
xs.filter(x => f(x))

// is equivalent to:
xs.filter((elem, index, arr) => f(elem))

An alternative, and probably better option, is to change the === to >= in your "es6" curry -

const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length >= fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
)

// ...

console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]

This allows you to "overflow" function parameters "normally", which JavaScript has no problem with -

const foo = (a, b, c) => // has only three (3) parameters  console.log(a + b + c)  foo(1,2,3,4,5) // called with five (5) args
// still works// => 6

How to call function from php class in function from web page

$val is out of scope in that doSomething() function call. Pass it as a parameter to the function:

// Define the function to accept a parameter. Use a type hint to be sure it is 
// a validation object
function doSomething(validation $val) {
$val->validate();
}

if(isset($_POST['action'])) doSomething($val);

(PHP documentation on type hinting)

Alternatively, and not recommended, you may use the global keyword inside the function to reference the global $val.

function doSomething() {
// Don't do it this way. The func parameter is preferred
global $val;
$val->validate();
}

What is the difference between currying and partial application?

Currying is converting a single function of n arguments into n functions with a single argument each. Given the following function:

function f(x,y,z) { z(x(y));}

When curried, becomes:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

In order to get the full application of f(x,y,z), you need to do this:

f(x)(y)(z);

Many functional languages let you write f x y z. If you only call f x y or f(x)(y) then you get a partially-applied function—the return value is a closure of lambda(z){z(x(y))} with passed-in the values of x and y to f(x,y).

One way to use partial application is to define functions as partial applications of generalized functions, like fold:

function fold(combineFunction, accumulator, list) {/* ... */}
function sum = curry(fold)(lambda(accum,e){e+accum}))(0);
function length = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list) //returns 10


Related Topics



Leave a reply



Submit