Анонимные рекурсивные функции PHP

197

Возможно ли иметь функцию PHP, которая является как рекурсивной, так и анонимной? Это моя попытка заставить его работать, но имя функции не передается.

$factorial = function( $n ) use ( $factorial ) {
    if( $n <= 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );

Я также знаю, что это плохой способ реализовать факториал, это всего лишь пример.

Кендалл Хопкинс
источник
У меня нет PHP 5.3.0 для проверки, но вы пытались использовать global $factorial?
Кеннитм
5
(sidenote) Lamba - это анонимная функция, в то время как выше - Закрытие.
Гордон
1
Лямбды и затворы не являются взаимоисключающими. Фактически некоторые люди считают, что замыкание должно быть лямбда-выражением, чтобы оно было замыканием (анонимная функция). Например, Python, вы как бы должны сначала дать имя функции (в зависимости от версии). Потому что вы должны дать ему имя, которое вы не можете встроить, и некоторые скажут, что это лишает его смысла.
Адам Гент
1
print $factorial( 0);
Nickb

Ответы:

356

Для того, чтобы он работал, вам нужно передать $ factorial в качестве ссылки

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};
print $factorial( 5 );
Дерек Х
источник
Странно, что объекты bc всегда должны передаваться по ссылке и в ближайшее время. функции являются объектами ...
ellabeauty
25
@ellabeauty во время прохождения $ factorial, все равно null (не определено), поэтому вы должны передать его по ссылке. Имейте в виду, что если вы измените $ factorial перед вызовом функции, результат изменится, поскольку он передается по ссылке.
Мариус Балчитис
9
@ellabeauty: Нет, вы совершенно не понимаете это. Все без &по стоимости. Все с &помощью ссылки. «Объекты» не являются значениями в PHP5 и не могут быть назначены или переданы. Вы имеете дело с переменной, значение которой является ссылкой на объект. Как и все переменные, он может быть захвачен по значению или по ссылке, в зависимости от того, есть ли &.
newacct
3
Разум взорван! Большое спасибо! Как я не знал об этом до сих пор? Количество приложений, которые у меня есть для рекурсивных анонимных функций, огромно. Теперь я могу, наконец, просматривать циклические вложенные структуры в макетах без необходимости явно определять метод и не допускать всех моих данных макета в свои классы.
Дитер Грибниц
Как сказал @barius, будьте осторожны при использовании в foreach. $factorialбудет изменено до вызова функции, и это может привести к странному поведению.
стил
24

Я знаю, что это может быть не простой подход, но я узнал о методике под названием «исправить» из функциональных языков. 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 - жизнеспособный подход.

Кендалл Хопкинс
источник
10
Стоит отметить, что call_user_func_array()это медленно, как Рождество.
Xeoncross
11
@Xeoncross В отличие от остального PHP, который устанавливает рекорд скорости на суше? : P
Кендалл Хопкинс
1
Обратите внимание, что теперь вы можете (5.6+) использовать распаковку аргументов вместо call_user_func_array.
Фабьен Са
@KendallHopkins почему это дополнительные аргументы array_unshift( $args, fix($func) );? Аргументы уже загружены параметрами, а рекурсия выполняется call_user_func_array (), так что же делает эта строка?
Я хочу ответы
5

Хотя это не для практического использования, расширение C-уровня 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)
mpyw
источник
0

В новых версиях PHP вы можете сделать это:

$x = function($depth = 0) {
    if($depth++)
        return;

    $this($depth);
    echo "hi\n";
};
$x = $x->bindTo($x);
$x();

Это может привести к странному поведению.

jgmjgm
источник
0

Вы можете использовать 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

Лей Фан
источник
0

С анонимным классом (PHP 7+), без определения переменной:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
Ayell
источник