ฟังก์ชั่น 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
(sidenote) a Lamba เป็นฟังก์ชั่นที่ไม่ระบุตัวตนในขณะที่ข้างต้นคือการปิด
Gordon

1
Lambdas and Closures ไม่ได้เกิดขึ้นพร้อมกัน ในความเป็นจริงบางคนเชื่อว่าการปิดจะต้องเป็นแลมบ์ดาเพื่อการปิด (ฟังก์ชั่นที่ไม่ระบุชื่อ) ตัวอย่างเช่น Python ที่คุณเรียงลำดับต้องให้ชื่อฟังก์ชันเป็นอันดับแรก (ขึ้นอยู่กับรุ่น) เพราะคุณต้องตั้งชื่อคุณไม่สามารถอินไลน์และบางคนก็บอกว่าตัดสิทธิ์จากการถูกปิด
Adam Gent

1
print $factorial( 0);
nickb

php คู่มือตัวอย่าง

คำตอบ:


357

เพื่อให้มันใช้งานได้คุณจะต้องผ่าน $ factorial เพื่อเป็นข้อมูลอ้างอิง

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

นั่นคือวัตถุ bc แปลก ๆ ควรถูกส่งผ่านโดยการอ้างอิงและไม่นาน ฟังก์ชั่นเป็นวัตถุ ...
ellabeauty

25
@ellabeauty ในเวลาที่ $ factorial ถูกส่งผ่านไปมันยังคงเป็นโมฆะ (ไม่ได้กำหนด) นั่นคือเหตุผลที่คุณต้องผ่านมันโดยอ้างอิง โปรดระวังว่าถ้าคุณปรับเปลี่ยน $ factorial ก่อนจะเรียกใช้ฟังก์ชันผลลัพธ์จะเปลี่ยนไปตามการอ้างอิง
Marius Balčytis

9
@ellabeauty: ไม่คุณเข้าใจผิดโดยสิ้นเชิง ทุกอย่างที่ไม่มี&คุณค่าตามตัวอักษร ทุกอย่างด้วย&เป็นการอ้างอิง "Objects" ไม่ใช่ค่าใน PHP5 และไม่สามารถกำหนดหรือส่งต่อได้ คุณกำลังจัดการกับตัวแปรที่มีค่าเป็นการอ้างอิงวัตถุ &เช่นเดียวกับตัวแปรทั้งหมดก็จะถูกจับโดยค่าหรือการอ้างอิงขึ้นอยู่กับว่ามีการ
newacct

3
ใจเป่า! ขอบคุณมาก! ฉันไม่ทราบเกี่ยวกับสิ่งนี้จนถึงขณะนี้ได้อย่างไร จำนวนแอปพลิเคชันที่ฉันมีสำหรับฟังก์ชั่นไม่ระบุชื่อแบบเรียกซ้ำมีขนาดใหญ่มาก ตอนนี้ฉันสามารถวนลูปผ่านโครงสร้างที่ซ้อนกันในเลย์เอาต์ได้โดยไม่ต้องกำหนดวิธีการอย่างชัดเจนและป้องกันไม่ให้ข้อมูลเลย์เอาต์ทั้งหมดของฉันอยู่ในคลาส
Dieter Gribnitz

เช่น @barius พูดอย่างระมัดระวังเมื่อใช้มันใน foreach $factorialจะมีการเปลี่ยนแปลงก่อนที่จะเรียกใช้ฟังก์ชันและอาจส่งผลให้เกิดพฤติกรรมที่ผิดปกติ
สไตล์

24

ฉันรู้ว่านี่อาจไม่ใช่วิธีที่ง่าย แต่ฉันเรียนรู้เกี่ยวกับเทคนิคที่เรียกว่า"แก้ไข"จากภาษาที่ใช้งานได้ fixฟังก์ชั่นจาก Haskell เป็นที่รู้จักกันมากขึ้นโดยทั่วไปเป็นCombinator Yซึ่งเป็นหนึ่งในที่สุดที่รู้จักกันดีcombinators จุดคงที่

จุดคงที่คือค่าที่ไม่เปลี่ยนแปลงโดยฟังก์ชั่น: จุดคงที่ของฟังก์ชันfคือxใด ๆที่ x = f (x) Combinator จุดคงที่yเป็นฟังก์ชั่นที่ส่งกลับจุดคงที่สำหรับฟังก์ชั่นใด ๆ f เนื่องจาก y (f) เป็นจุดคงที่ของ f เราจึงมี y (f) = f (y (f))

โดยพื้นฐานแล้ว Y combinator สร้างฟังก์ชั่นใหม่ที่รับอาร์กิวเมนต์ทั้งหมดของต้นฉบับรวมทั้งอาร์กิวเมนต์เพิ่มเติมที่เป็นฟังก์ชันเรียกซ้ำ วิธีการทำงานนี้ชัดเจนยิ่งขึ้นโดยใช้สัญกรณ์ Curried แทนการเขียนข้อโต้แย้งในวงเล็บ ( f(x,y,...)) f x y ...เขียนพวกเขาหลังจากที่ฟังก์ชั่น: Y combinator ถูกกำหนดให้เป็น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 combinator จึงเป็นแนวทางที่ทำงานได้


10
มันน่าสังเกตว่าcall_user_func_array()ช้าเหมือนคริสต์มาส
Xeoncross

11
@ Xeoncross เมื่อเทียบกับส่วนที่เหลือของ PHP ซึ่งตั้งค่าการบันทึกความเร็วของที่ดิน? : P
Kendall Hopkins

1
หมายเหตุ: ตอนนี้คุณสามารถ (5.6+) call_user_func_arrayอาร์กิวเมนต์ใช้เอาออกแทน
Fabien Sa

@KendallHopkins ทำไมข้อโต้แย้งเพิ่มเติมนี้array_unshift( $args, fix($func) );? Args เต็มไปด้วยพารามิเตอร์อยู่แล้วและการเรียกซ้ำเกิดขึ้นจริงโดย call_user_func_array () ดังนั้นบรรทัดนั้นจะทำอย่างไร
ฉันต้องการคำตอบ

5

แม้ว่ามันจะไม่ได้สำหรับการใช้งาน practial, C-ระดับขยายmpyw-เรือสำเภา / 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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.