Các hàm đệ quy ẩn danh


197

Có thể có một hàm PHP cả đệ quy và ẩn danh? Đây là nỗ lực của tôi để làm cho nó hoạt động, nhưng nó không vượt qua trong tên hàm.

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

Tôi cũng biết rằng đây là một cách tồi để thực hiện giai thừa, nó chỉ là một ví dụ.


Tôi không có PHP 5.3.0 để kiểm tra nhưng bạn đã thử sử dụng global $factorialchưa?
kennytm

5
(sidenote) Lamba là một chức năng ẩn danh, trong khi ở trên là một Đóng cửa.
Gordon

1
Lambdas và Closures không loại trừ lẫn nhau. Trong thực tế, một số người tin rằng một bao đóng phải là lambda cho nó là một bao đóng (hàm ẩn danh). Ví dụ: Python bạn phải đặt tên cho hàm trước (tùy theo phiên bản). Bởi vì bạn phải đặt cho nó một cái tên mà bạn không thể đặt nội tuyến và một số người sẽ nói rằng nó không đủ điều kiện để trở thành một đóng cửa.
Adam Gent

1
print $factorial( 0);
nickb

Câu trả lời:


355

Để nó hoạt động, bạn cần chuyển $ factorial làm tài liệu tham khảo

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

đó là những đối tượng bc kỳ lạ nên luôn luôn được thông qua tham chiếu và anon. các chức năng là các đối tượng ...
ellabeauty

25
@ellabeauty trong thời gian $ factorial được thông qua, nó vẫn là null (không được xác định), đó là lý do tại sao bạn phải vượt qua nó bằng cách tham chiếu. Xin lưu ý rằng nếu bạn sửa đổi giai thừa $ trước khi gọi hàm, kết quả sẽ thay đổi khi được chuyển qua tham chiếu.
Marius Balčytis

9
@ellabeauty: Không, bạn hoàn toàn hiểu sai về nó. Tất cả mọi thứ mà không có &giá trị. Tất cả mọi thứ với &là bằng cách tham khảo. "Đối tượng" không phải là giá trị trong PHP5 và không thể được chỉ định hoặc thông qua. Bạn đang xử lý một biến có giá trị là tham chiếu đối tượng. Giống như tất cả các biến, nó có thể được ghi lại bằng giá trị hoặc bằng tham chiếu, tùy thuộc vào việc có &.
newacct

3
Tâm thổi! Cảm ơn rất nhiều! Làm thế nào tôi không biết về điều này cho đến bây giờ? Số lượng ứng dụng tôi có cho các hàm ẩn danh đệ quy là rất lớn. Bây giờ tôi cuối cùng có thể lặp qua các cấu trúc lồng nhau trong các bố cục mà không cần phải xác định rõ ràng một phương thức và giữ tất cả dữ liệu bố trí của tôi ra khỏi các lớp.
Dieter Gribnitz

Giống như @barius đã nói, cẩn thận khi sử dụng nó trong foreach. $factorialsẽ được thay đổi trước khi chức năng được gọi và nó có thể dẫn đến hành vi lạ.
stil

24

Tôi biết đây có thể không phải là một cách tiếp cận đơn giản, nhưng tôi đã học về một kỹ thuật gọi là "sửa chữa" từ các ngôn ngữ chức năng. Các fixchức năng từ Haskell được biết tổng quát hơn như combinator Y , đó là một trong những nổi tiếng nhất combinators điểm cố định .

Điểm cố định là một giá trị không thay đổi bởi hàm: điểm cố định của hàm f là bất kỳ x sao cho x = f (x). Một tổ hợp điểm cố định y là một hàm trả về một điểm cố định cho bất kỳ hàm f nào. Vì y (f) là một điểm cố định của f, nên ta có y (f) = f (y (f)).

Về cơ bản, bộ kết hợp Y tạo ra một hàm mới lấy tất cả các đối số của bản gốc, cộng với một đối số bổ sung đó là hàm đệ quy. Làm thế nào điều này hoạt động rõ ràng hơn bằng cách sử dụng ký hiệu curried. Thay vì viết các đối số trong ngoặc đơn (f(x,y,...) ), hãy viết chúng sau hàm : f x y .... Tổ hợp Y được định nghĩa là Y f = f (Y f); hoặc, với một đối số duy nhất cho hàm đệ quy , Y f x = f (Y f) x.

Vì PHP không tự động làm các chức năng cà ri , nên có một chút hack để thực hiện fixcông việc, nhưng tôi nghĩ nó thật thú vị.

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 );

Lưu ý điều này gần giống như các giải pháp đóng đơn giản mà người khác đã đăng, nhưng chức năng fixtạo ra bao đóng cho bạn. Các tổ hợp điểm cố định phức tạp hơn một chút so với sử dụng bao đóng, nhưng tổng quát hơn và có các ứng dụng khác. Mặc dù phương thức đóng phù hợp hơn với PHP (không phải là ngôn ngữ chức năng khủng khiếp), nhưng vấn đề ban đầu chỉ là một bài tập hơn là sản xuất, do đó, tổ hợp Y là một cách tiếp cận khả thi.


10
Đáng chú ý call_user_func_array()là chậm như Giáng sinh.
Xeoncross

11
@Xeoncross Trái ngược với phần còn lại của PHP đang thiết lập kỷ lục tốc độ đất? : P
Kendall Hopkins

1
Lưu ý, bây giờ bạn có thể (5.6+) sử dụng giải nén đối số thay vì call_user_func_array.
Fabien Sa

@KendallHopkins tại sao điều này thêm đối số array_unshift( $args, fix($func) );? Các đối số đã có đầy đủ các tham số và đệ quy thực tế được thực hiện bởi call_user_func_array (), vậy dòng đó làm gì?
Tôi muốn trả lời

5

Mặc dù nó không dành cho sử dụng thực tế, nhưng phần mở rộng cấp C mpyw-junks / phpext-callee cung cấp đệ quy ẩn danh mà không cần gán biến .

<?php

var_dump((function ($n) {
    return $n < 2 ? 1 : $n * callee()($n - 1);
})(5));

// 5! = 5 * 4 * 3 * 2 * 1 = int(120)

0

Trong các phiên bản mới hơn của PHP, bạn có thể làm điều này:

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

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

Điều này có khả năng dẫn đến hành vi lạ.


0

Bạn có thể sử dụng Y Combinator trong PHP 7.1+ như dưới đây:

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

Chơi với nó: https://3v4l.org/7AUn2

Mã nguồn từ: https://github.com/whitephp/the-little-phper/blob/master/src/ch CHƯƠNG_9.php


0

Với một lớp ẩn danh (PHP 7+), không xác định một biến:

echo (new class {
    function __invoke($n) {
        return $n < 2 ? 1 : $n * $this($n - 1);
    }
})(5);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.