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


198

Чи можлива функція 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?
kennytm

5
(сторонне позначення ) а ламба - це анонімна функція, тоді як вище - закриття.
Гордон

1
Лямбди і закриття не є взаємовиключними. Насправді деякі люди вважають, що закриття повинно бути лямбда, щоб воно було закриттям (анонімна функція). Наприклад, Python, який ви, начебто, повинен спершу дати ім'я функції (залежно від версії). Тому що ви повинні дати йому ім'я, яке ви не можете вписати, і дехто сказав, що це позбавляє його від закриття.
Адам Гент

1
print $factorial( 0);
nickb

Відповіді:


357

Для того, щоб він працював, вам потрібно передати $ factorial як орієнтир

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

це дивні bc-об'єкти завжди повинні передаватися шляхом посилання та anon. функції - це об'єкти ...
ellabeauty

25
@ellabeauty в момент передачі $ factorial все ще є нульовим (не визначено), тому вам доведеться передати його за посиланням. Майте на увазі, що якщо ви модифікуєте $ factorial перед тим, як викликати функцію, результат зміниться, коли він передається посиланням.
Маріус Бальчітіс

9
@ellabeauty: Ні, ви абсолютно неправильно розумієте це. Все без &вартості. Все з &посиланням. "Об'єкти" не є значеннями в PHP5 і не можуть бути призначені або передані. Ви маєте справу зі змінною, значення якої є посиланням на об'єкт. Як і всі змінні, вона може бути відображена за значенням або за посиланням, залежно від того, чи є &.
newacct

3
Розум роздутий! Дуже дякую! Як я досі про це не знав? Кількість додатків, які я маю для рекурсивних анонімних функцій, величезна. Тепер я можу нарешті пройти цикл кріплення вкладених структур у макетах, не маючи ясного визначення методу, а також зберігати всі мої дані макета поза моїми класами.
Дітер Грібніц

Як сказав @barius, будьте обережні, використовуючи його в foreach. $factorialбуде змінено до виклику функції, і це може призвести до дивної поведінки.
стиль

24

Я знаю, що це може бути не простий підхід, але я дізнався про техніку під назвою "виправити" з функціональних мов. fixФункція з Haskell відома в більш загальному випадку в якості Y комбінатора , який є одним з найвідоміших точкових комбінаторів фіксованих .

Фіксована точка - це значення, яке не змінюється функцією: фіксованою точкою функції f є будь-який х, такий, що 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.
Fabien Sa

@KendallHopkins, чому це додаткові аргументи array_unshift( $args, fix($func) );? Args вже навантажений параметрами, а фактична рекурсія виконується call_user_func_array (), так що ж робить цей рядок?
Я хочу відповіді

5

Хоча це не для практичного використання, розширення на рівні С 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)

0

У нових версіях PHP ви можете це зробити:

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

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

Це потенційно може призвести до дивної поведінки.


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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.