8

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

Dharman
  • 30,962
  • 25
  • 85
  • 135
wnull
  • 217
  • 6
  • 21
  • Does this answer your question? [How to use arrow functions in PHP?](https://stackoverflow.com/questions/56657135/how-to-use-arrow-functions-in-php) – Dharman Nov 15 '19 at 18:46
  • You need the `use()` modifier so you can refer to the external variable `$f`. – Barmar Nov 15 '19 at 18:46
  • I don't know whether this should be closed as dup or answered, so I actually appreciate your feedback. Please mark as accepted if it helped. There is also this post: https://stackoverflow.com/a/56658110/1839439 – Dharman Nov 15 '19 at 18:48
  • 1
    As stated in the linked question, arrow functions will capture outside variables *by value*. But the the original anonymous function is is capturing by reference. I don't think there's an equivalent with arrow functions. – Barmar Nov 15 '19 at 18:49
  • @Barmar, that is, without `use()` it will not be possible to use in the new syntax? – wnull Nov 15 '19 at 18:51

4 Answers4

14

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.

scorpion35
  • 944
  • 2
  • 12
  • 30
Dharman
  • 30,962
  • 25
  • 85
  • 135
4

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.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thanks for the explanation. It is a pity, whether it is possible to consider that it is a defect of functions of an arrow? Hope in soon anything with this will make. – wnull Nov 15 '19 at 18:56
  • Arrow functions are a simplification of anonymous functions. – Barmar Nov 15 '19 at 19:02
2

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 ?

Rain
  • 3,416
  • 3
  • 24
  • 40
  • Oh, my God, that's a crazy idea. – wnull Jan 09 '20 at 21:13
  • there is no `n < 1` in my problem. Thanks for the answer :) – wnull Jan 09 '20 at 21:18
  • @Theartofbeingalive I know, but if someone passed 0 or a negative number to the function `$f(0)` the function will call itself for EVER. – Rain Jan 09 '20 at 21:23
  • this shouldn't happen, because it doesn't make sense to use this function when the value is zero. And if the value tends to one, the function will give it the same value. – wnull Jan 09 '20 at 21:27
  • @Theartofbeingalive It actually makes sense. Because AFAIK this a factorial function and the `0!` factorial equals `1` – Rain Jan 09 '20 at 21:32
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
Dawid Dahl
  • 129
  • 1
  • 8