Rewriting an anonymous function in php 7.4 - php

There is the following anonymous recursive function:
$f = function($n) use (&$f) {
return ($n == 1) ? 1 : $n * $f($n - 1);
};
echo $f(5); // 120
I try to rewrite to version 7.4, but there is an error, please tell me what I'm missing?
$f = fn($n) => ($n == 1) ? 1 : $n * $f($n - 1);
echo $f(5);
Notice: Undefined variable: f
Fatal error: Uncaught Error: Function name must be a string

Just like Barmar said, you can't use $f from the outside scope, because when the implicit binding takes place $f is still undefined.
There is nothing stopping you from passing it later as a parameter.
$f = fn($f, $n) => $n == 1 ? 1 : $n * $f($f, $n - 1);
echo $f($f, 5); // 120
The way arrow functions work, is that during definition time they will use by-value binding of the outer scope's variables.
As already mentioned, arrow functions use by-value variable binding. This is roughly equivalent to performing a use($x) for every variable $x used inside the arrow function. - https://wiki.php.net/rfc/arrow_functions_v2
The assignment of the closure to the variable $f happens after closure's definition and the variable $f is undefined prior to it.
As far as I am aware, there isn't any mechanism to bind by reference while defining arrow functions.

I don't think you can rewrite the function as an arrow function.
An arrow function will capture the value of any external variables by value at the time the function is created. But $f won't have a value until after the function is created and the variable is assigned.
The original anonymous function solves this problem by capturing a reference to the variable with use (&$f), rather than use ($f). This way, it will be able to use the updated value of the variable that results from the assignment.

I think I just found one of the legitimate (no?) uses of $GLOBALS
$f = fn ($n) =>($n == 1) ? 1 : $n * $GLOBALS['f']($n - 1);
echo $f(5); // 120
Sidenote: what if $n < 1 ?

As a slight variation of what #scorpion35 was saying, you could also apply currying:
$f = fn($f) => fn($x) => $x <= 1
? $x
: $f($f)($x-1) * $x;
$factorial = $f($f);
$factorial(7); //5040

Related

what is the difference between these two statements in php?

I was reading about arrow functions in php documentions and I came across this code
fn&($x = 42) => $x;
fn(&$x) => $x;
what is the difference between these two statements and what they do ?
in the second you use &$x and that's passing a variable by reference. it calls reference by value
so the $x out of the function will change if you do something in the function.
reference
here the final value of $a is 5:
$a = 5;
$y = fn ($x) => $x++;
$y($a);
echo $a;//5
but in reference by value, the final value of $a changed and it's 6:
$a = 5;
$y = fn (&$x) => $x++;
$y($a);
echo $a;//6
So you can see in reference by value, the original value of the variable can be changed
& sets a reference to the variable
fn($x = 42) : is a function with one or 0 parameter, if 0, default value 42
fn(&$x) : is a function with one parameter passed as reference, meaning if changed in the function, the new value of $x will be pass in the main.

Is there a clean way to use undefined variables as optional parameters in PHP?

Is there any nice way to use (potentially) undefined Variables (like from external input) as optional Function parameters?
<?php
$a = 1;
function foo($a, $b=2){
//do stuff
echo $a, $b;
}
foo($a, $b); //notice $b is undefined, optional value does not get used.
//output: 1
//this is even worse as other erros are also suppressed
#foo($a, $b); //output: 1
//this also does not work since $b is now explicitly declared as "null" and therefore the default value does not get used
$b ??= null;
foo($a,$b); //output: 1
//very,very ugly hack, but working:
$r = new ReflectionFunction('foo');
$b = $r->getParameters()[1]->getDefaultValue(); //still would have to check if $b is already set
foo($a,$b); //output: 12
the only semi-useful method I can think of so far is to not defining the default value as parameter but inside the actual function and using "null" as intermediary like this:
<?php
function bar ($c, $d=null){
$d ??= 4;
echo $c,$d;
}
$c = 3
$d ??= null;
bar($c,$d); //output: 34
But using this I still have to check the parameter twice: Once if it is set before calling the function and once if it is null inside the function.
Is there any other nice solution?
Ideally you wouldn't pass $b in this scenario. I don't remember ever running into a situation where I didn't know if a variable existed and passed it to a function anyway:
foo($a);
But to do it you would need to determine how to call the function:
isset($b) ? foo($a, $b) : foo($a);
This is kind of hackish, but if you needed a reference anyway it will be created:
function foo($a, &$b){
$b = $b ?? 4;
var_dump($b);
}
$a = 1;
foo($a, $b);
I would do something like this if this was actually a requirement.
Just testing with sum of the supplied values just for showing an example.
<?php
$x = 1;
//Would generate notices but no error about $y and t
//Therefore I'm using # to suppress these
#$sum = foo($x,$y,4,3,t);
echo 'Sum = ' . $sum;
function foo(... $arr) {
return array_sum($arr);
}
Would output...
Sum = 8
...based on the array given (unknown nr of arguments with ... $arr)
array (size=5)
0 => int 1
1 => null
2 => int 4
3 => int 3
4 => string 't' (length=1)
array_sum() only sums up 1,4 and 3 here = 8.
Even if above actually works I would not recommend it, because then whatever data can be sent to your function foo() without you having any control over it. When it comes to user input of any kind you should always validate as much as you can in your code before using the actual data from the user.

php Anonymous function

When I was reading questions for Zend Certified PHP Engineer 5.5 I saw question about anonymous function but I need to explan how it work.
function z($x)
{
return function($y) use ($x)
{
return str_repeat( $y , $x );
};
}
$a = z(2);
$b = z(3);
echo $a(3).$b(2);
The output for this code is:
33222
But in function header there is only $x parameter from where $y got there value!
Function z creates and returns a new function, but an anonymous one. That new function is defined so that it has one argument - $y. However, this anonymous function also uses argument $x from a function z.
To make it simple, function z basically creates a function which can repeat any string, but a fixed number of times. The number of times a string is repeated is determined by the value of argument $x in z.
So, calling z(2) creates a new function which is functionally equivalent to writing
function repeat_two_times($y) {
return str_repeat($y, 2);
}
In you example, hard coded value 2 is determined by the value of $x.
You can read more about this in the documentation. The principle displayed by the example can be quite useful for creating partial functions like add5, inc10, ...
Firstly, you initialize function z:
$a = z(2);
$x in the example is set to 2, so the returned function (anonymous function, also called closure) can now be read as (because $x is used):
$a = function($y) {
return str_repeat($y, 2);
}
When invoking this function:
echo $a(3);
You are supplying this return function with the parameter 3 ($y).
The output is: 33
Anonymous functions are also known as Closures.
You ask where $y gets its value. The code example is difficult to decipher because you use 2s and 3s everywhere. Things would be clearer if your last lines were
$a = z(2);
$b = z(3);
echo $a('A').$b('B');
That would result in:
AABBB
But let's follow your code. Notice that there are two related function calls
$a = z(2);
and
echo $a(3);
calling function z() with argument 2 returns a function (that is assigned name $a) where line
return str_repeat($y, $x);
is in reality :
return str_repeat($y, 2);
now, you call that function $a() with argument 3. That 3 (value of $y) is repeated two times
The same analysis applies to the other related function calls:
$b = z(3);
...
echo ... $b(2);
But in this case 2 is repeated 3 times
function z($x)
{
return function($y) use ($x)
{
return str_repeat( $y , $x );
};
}
$a = z(2);// here you are setting value of x by 2
$b = z(3);// here you are setting value of x by 3
echo $a(3).$b(2);// here $a(3) 3 is value of y so it becomes str_repeat( 3 , 2 ); which is 33

Why is the second static variable assignment takes effect not the first one?

function track_times() {
static $i = 0;
$i++;
static $i = 5;
return $i;
}
echo track_times() . "\n";
echo track_times() . "\n";
The result is:
6
7
I know people don't use static variables in this way, just can't explain the result. The result implies the second assignment takes effect, but $i increments itself before the assignment, so why the first invocation of the function returns 6?
Static declarations are resolved in compile-time. You are incrementing it during runtime. Therefore you are incrementing it after it's already declared as 5. See also http://www.php.net/manual/en/language.variables.scope.php

set value when not empty via ternary expression

I have a really quick question for you:
I read data from an Excel sheet and want to transform it into an assoc array. But sometimes there are no values given in some cells. So if this occurs I want to set the value of the array to 0.
right now I do it like that with the ternary operator and I'm glad I discovered that today:
(isset($excel->sheet[0]['cells'][$row][$value]) ? $excel->sheet[0]['cells'][$row][$value] : 0)
Is there a whay to shorten the repitition in this case? It works but it ain't that pretty :(
Although this is not recommended, I would go the following way (PHP 5.3):
(#$excel->sheet[0]['cells'][$row][$value] ? : 0);
Error suppression operator is a mess, but in this case the only thing you suppress is a well-known notice about undefined variable.
Another option (as stated by Álvaro G. Vicario) could be a simple cast to int (as NULL casts to 0):
(int)#$excel->sheet[0]['cells'][$row][$value];
Another option is making a function to check the existence of such variable – maybe it's a little over-engineering, overkill or just too much –:
function iset($array, $output) {
$args = func_get_args();
$val = $array;
for ($i = 1; $i < count($args) - 1; $i++) {
if (!isset($val[func_get_arg($i)])) {
return func_get_arg(func_num_args() - 1);
}
$val = $val[func_get_arg($i)];
}
return $val;
}
Then use the function like this:
$var = iset($excel->sheet, 0, 'cells', $row, $value, "DEFAULT_VALUE");

Categories