Анонимные рекурсивные функции PHP
Возможно ли иметь функцию PHP, которая является как рекурсивной, так и анонимной? Это моя попытка заставить его работать, но имя функции не передается.
$factorial = function( $n ) use ( $factorial ) {
if( $n <= 1 ) return 1;
return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Я также знаю, что это плохой способ реализовать факториал, это всего лишь пример.
4 ответа
Для того, чтобы это работало, вам нужно передать $factorial в качестве ссылки
$factorial = function( $n ) use ( &$factorial ) {
if( $n == 1 ) return 1;
return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Я знаю, что это не может быть простым подходом, но я узнал о методике под названием "исправить" из функциональных языков. fix
Функция от Haskell более широко известна как Y-комбинатор, который является одним из наиболее известных комбинаторов с фиксированной точкой.
Фиксированная точка - это значение, которое не изменяется функцией: фиксированная точка функции f - это любой x такой, что x = f(x). Комбинатор с фиксированной точкой y - это функция, которая возвращает фиксированную точку для любой функции f. Так как y(f) является неподвижной точкой f, мы имеем y(f) = f(y(f)).
По сути, комбинатор Y создает новую функцию, которая принимает все аргументы оригинала, плюс дополнительный аргумент, который является рекурсивной функцией. Как это работает, более очевидно, используя каррированную нотацию. Вместо написания аргументов в скобках (f(x,y,...)
), напишите их после функции: f x y ...
, Y комбинатор определяется как Y f = f (Y f)
; или с одним аргументом для рекурсивной функции, Y f x = f (Y f) x
,
Так как PHP автоматически не выполняет функции карри, сделать это немного fix
работа, но я думаю, что это интересно.
function fix( $func )
{
return function() use ( $func )
{
$args = func_get_args();
array_unshift( $args, fix($func) );
return call_user_func_array( $func, $args );
};
}
$factorial = function( $func, $n ) {
if ( $n == 1 ) return 1;
return $func( $n - 1 ) * $n;
};
$factorial = fix( $factorial );
print $factorial( 5 );
Обратите внимание, что это почти то же самое, что и простые решения по закрытию, опубликованные другими, но функция fix
создает закрытие для вас. Комбинаторы с фиксированной точкой немного сложнее, чем использование замыкания, но они более общие и имеют другие применения. Хотя метод замыкания больше подходит для PHP (который не очень функциональный язык), исходная проблема - скорее упражнение, чем для производства, поэтому комбинатор Y - жизнеспособный подход.
Хотя это не для практического использования, расширение C-уровня https://github.com/mpyw-junks/phpext-callee обеспечивает анонимную рекурсию без назначения переменных.
<?php
var_dump((function ($n) {
return $n < 2 ? 1 : $n * callee()($n - 1);
})(5));
// 5! = 5 * 4 * 3 * 2 * 1 = int(120)
С анонимным классом (PHP 7+) без определения переменной:
echo (new class {
function __invoke($n) {
return $n < 2 ? 1 : $n * $this($n - 1);
}
})(5);
В новых версиях PHP вы можете сделать это:
$x = function($depth = 0) {
if($depth++)
return;
$this($depth);
echo "hi\n";
};
$x = $x->bindTo($x);
$x();
Это может потенциально привести к странному поведению.
Вы можете использовать Y Combinator в PHP 7.1+, как показано ниже:
function Y
($le)
{return
(function ($f)
{return
$f($f);
})(function ($f) use ($le)
{return
$le(function ($x) use ($f)
{return
$f($f)($x);
});
});
}
$le =
function ($factorial)
{return
function
($n) use ($factorial)
{return
$n < 2 ? $n
: $n * $factorial($n - 1);
};
};
$factorial = Y($le);
echo $factorial(1) . PHP_EOL; // 1
echo $factorial(2) . PHP_EOL; // 2
echo $factorial(5) . PHP_EOL; // 120
Поиграйте с ним: https://3v4l.org/7AUn2
Исходные коды от: https://github.com/whitephp/the-little-phper/blob/master/src/chapter_9.php