匿名の再帰的な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か?
kennytm 2010年

5
(サイドノート) Lambaは匿名関数ですが、上記はクロージャです。
Gordon

1
ラムダとクロージャは相互に排他的ではありません。実際、一部の人々は、クロージャーがクロージャー(無名関数)であるためにはラムダでなければならないと信じています。たとえば、Pythonでは、最初に関数に名前を付ける必要があります(バージョンによって異なります)。あなたはそれに名前を付けなければならないのでインラインにすることはできません、そしてそれはクロージャーであることからそれを失格と言う人もいます。
アダム・ゲント

1
print $factorial( 0);
nickb 2013

回答:


355

それが機能するためには、参照として$ factorialを渡す必要があります

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

それは奇妙なbcオブジェクトは常に参照渡しである必要があります。関数はオブジェクト...
ellabeauty 2012

25
$ factorialが渡されたときの@ellabeautyはnull(未定義)であるため、参照で渡す必要があります。関数を呼び出す前に$ factorialを変更すると、参照によって渡されるときに結果が変わることに注意してください。
MariusBalčytis2012

9
@ellabeauty:いいえ、完全に誤解しています。ないもの&はすべて価値によるものです。のすべて&は参照によるものです。「オブジェクト」はPHP5では値ではないため、割り当てたり受け渡したりすることはできません。値がオブジェクト参照である変数を扱っています。すべての変数と同様に、があるかどうかに応じて、値または参照によってキャプチャできます&
newacct 2013

3
マインドブローン!どうもありがとう!どうして今までこのことを知らなかったのですか?再帰的な匿名関数を使用するアプリケーションの量は膨大です。これで、メソッドを明示的に定義してすべてのレイアウトデータをクラスから除外する必要なく、レイアウト内のネストされた構造を最終的にループできます。
Dieter Gribnitz、2014

@bariusが言ったように、foreachで使用する場合は注意してください。$factorial関数が呼び出される前に変更され、奇妙な動作を引き起こす可能性があります。
2015年

24

これは簡単なアプローチではないかもしれませんが、関数型言語から「修正」と呼ばれる手法について学びました。fixHaskell の関数は、Yコンビネーターとしてより一般的に知られています。これは、最もよく知られている固定小数点コンビネーターの 1つです。

固定小数点は、関数によって変更されない値です。関数fの固定小数点は、x = f(x)のような任意の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

実用的な使用法ではありませんが、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)

0

PHPの新しいバージョンでは、これを行うことができます。

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

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

これは、奇妙な動作につながる可能性があります。


0

以下のように、PHP 7.1以降でY Combinatorを使用できます。

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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.