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?
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
How to Display a PHP Variable into HTML
PHP Curl with Curlopt_Followlocation Error
Pdo Mssql Server - Driver Not Found
Import Class Conditionally with the Keyword 'Use'
PHP Dynamic Db Page Rewrite Url
Best Debug Tool to Debug Ajax Request in PHP
Zend Framework 2 - Removed Form Element Causes Validation to Fail
Convert Hex Code into Readable String in PHP
Regular Expression to Remove CSS Comments
Adjusting Time Zone in PHP with Datetime/Datetimezone
Un-Encrypting/Re-Encrypting a Coldfusion Encrypted String in PHP
How to Convert a Single Array into a Multidimensional Array in PHP