web-dev-qa-db-ja.com

なぜ「use」識別子を使用するのですか?

私はいくつかのPHP 5.3.0機能をチェックしていて、サイト上で非常におもしろそうに見えるいくつかのコードに出くわしました:

public function getTotal($tax)
{
    $total = 0.00;

    $callback =
        /* This line here: */
        function ($quantity, $product) use ($tax, &$total)
        {
            $pricePerItem = constant(__CLASS__ . "::PRICE_" .
                strtoupper($product));
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };

    array_walk($this->products, $callback);
    return round($total, 2);
}

無名関数 の例の1つとして。

誰もがこれについて知っていますか?ドキュメントはありますか?そしてそれは悪そうに見えます、それは使われるべきですか?

368
SeanDowney

これがPHPが クロージャ を表す方法です。これは決して悪ではありません、そして実際、それは非常に強力で有用です。

基本的にこれが意味するのは、無名関数がその範囲外のローカル変数(この場合は$taxおよび$totalへの参照)を「キャプチャー」し、その値を保持すること(または$totalの場合は$total自体への参照)を許可することです。 )無名関数自体の中の状態として。

327
Andrew Hare

もっと簡単な答えです。

function ($quantity) use ($tax, &$total) { .. };

  1. クロージャは変数に代入された関数なので、それを渡すことができます
  2. クロージャは別の名前空間です。通常、この名前空間の外側で定義された変数にアクセスすることはできません。 useキーワードがあります。
  3. useを使用すると、クロージャ内の後続の変数にアクセス(使用)できます。
  4. useは早期バインディングです。つまり、クロージャを定義すると変数値がコピーされます。そのため、クロージャ内で$taxを変更しても、オブジェクトのようにポインタでない限り、外部からの影響はありません。
  5. &$totalの場合のように、変数をポインタとして渡すことができます。このようにして、$totalの値を変更すると外部効果があり、元の変数の値が変わります。
  6. クロージャの内側で定義された変数は、クロージャの外側からもアクセスできません。
  7. クロージャーと機能は同じ速度です。はい、あなたはあなたのスクリプトの至るところでそれらを使うことができます。

@Mytskineとして 指摘された おそらく最も詳細な説明は クロージャのためのRFC です。 (彼にこれを支持しなさい)

433
zupa

閉鎖は美しいです!それらは無名関数に付随する多くの問題を解決し、(少なくともphpについて話す限りでは)本当に優雅なコードを可能にします。

javascriptプログラマは、束縛された変数が明示的に定義されていないため、クロージャを常に、時には知らなくても使用します - これがphpでの使用です。

上記の例よりも優れた実例があります。多次元配列をサブ値でソートする必要があるとしましょうが、キーが変わります。

<?php
    function generateComparisonFunctionForKey($key) {
        return function ($left, $right) use ($key) {
            if ($left[$key] == $right[$key])
                return 0;
            else
                return ($left[$key] < $right[$key]) ? -1 : 1;
        };
    }

    $myArray = array(
        array('name' => 'Alex', 'age' => 70),
        array('name' => 'Enrico', 'age' => 25)
    );

    $sortByName = generateComparisonFunctionForKey('name');
    $sortByAge  = generateComparisonFunctionForKey('age');

    usort($myArray, $sortByName);

    usort($myArray, $sortByAge);
?>

警告:テストされていないコード(私はatmをインストールしたphp5.3を持っていません)が、それはそのようなもののように見えるはずです。

1つ欠点があります。多くのphp開発者は、クロージャと対決するのであれば少し無力かもしれません。

より良い閉鎖の理解を深めるために、私はあなたに別の例を挙げる - 今度はJavaScriptで。問題の1つは、スコープとブラウザ固有の非同期性です。特にwindow.setTimeout();(または-interval)に関してはそうです。そのため、setTimeoutに関数を渡しますが、パラメータを指定するとコードが実行されるため、実際にはパラメータを指定することはできません。

function getFunctionTextInASecond(value) {
    return function () {
        document.getElementsByName('body')[0].innerHTML = value; // "value" is the bound variable!
    }
}

var textToDisplay = Prompt('text to show in a second', 'foo bar');

// this returns a function that sets the bodys innerHTML to the prompted value
var myFunction = getFunctionTextInASecond(textToDisplay);

window.setTimeout(myFunction, 1000);

myFunctionはある種の定義済みパラメータを持つ関数を返します。

正直に言うと、私は5.3以降、PHPと匿名関数/クロージャーが大好きです。名前空間はより重要かもしれませんしかし、それらはずっとセクシーではありません

51
stefs

function () use () {}はPHPのクロージャのようなものです。

useがないと、関数は親スコープ変数にアクセスできません

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

use変数の値は、呼び出されたときではなく、関数が定義されたときのものです。

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$obj = "how are you?";
$f(); // hello

use変数&による参照による参照

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?
41
Steely Wing

Zupaは、 'use'によるクロージャーとEarlyBindingと 'used'である変数の参照の違いを説明する素晴らしい仕事をしました。

それで、私は変数の早期束縛(=コピー)でコード例を作りました:

<?php

$a = 1;
$b = 2;

$closureExampleEarlyBinding = function() use ($a, $b){
    $a++;
    $b++;
    echo "Inside \$closureExampleEarlyBinding() \$a = ".$a."<br />";
    echo "Inside \$closureExampleEarlyBinding() \$b = ".$b."<br />";    
};

echo "Before executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "Before executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";  

$closureExampleEarlyBinding();

echo "After executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "After executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";

/* this will output:
Before executing $closureExampleEarlyBinding() $a = 1
Before executing $closureExampleEarlyBinding() $b = 2
Inside $closureExampleEarlyBinding() $a = 2
Inside $closureExampleEarlyBinding() $b = 3
After executing $closureExampleEarlyBinding() $a = 1
After executing $closureExampleEarlyBinding() $b = 2
*/

?>

変数を参照する例(変数の前の '&'文字に注意してください)。

<?php

$a = 1;
$b = 2;

$closureExampleReferencing = function() use (&$a, &$b){
    $a++;
    $b++;
    echo "Inside \$closureExampleReferencing() \$a = ".$a."<br />";
    echo "Inside \$closureExampleReferencing() \$b = ".$b."<br />"; 
};

echo "Before executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "Before executing \$closureExampleReferencing() \$b = ".$b."<br />";   

$closureExampleReferencing();

echo "After executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "After executing \$closureExampleReferencing() \$b = ".$b."<br />";    

/* this will output:
Before executing $closureExampleReferencing() $a = 1
Before executing $closureExampleReferencing() $b = 2
Inside $closureExampleReferencing() $a = 2
Inside $closureExampleReferencing() $b = 3
After executing $closureExampleReferencing() $a = 2
After executing $closureExampleReferencing() $b = 3
*/

?>
14
joronimo

ごく最近まで、PHPはASTを定義し、PHPインタープリターはパーサーを評価部分から分離していました。クロージャが導入されている間、PHPのパーサーは評価と高度に結びついています。

したがって、クロージャーが最初にPHPに導入されたとき、インタープリターには、どの変数がクロージャーで使用されるかを知る方法がありません。したがって、ユーザーは明示的なインポートによってzendエンジンを満足させ、zendが行うべき宿題をしなければなりません。

これは、PHPでいわゆるシンプルな方法です。

1
Zhu Jinxuan