2012-03-01 33 views
16

Tôi muốn có thể gọi một lambda ẩn danh từ bên trong chính nó bằng Ruby. Hãy xem xét khối đệ quy sau đây (trả về giai thừa). Tôi biết tôi có thể gán nó vào một biến, và biến đó là trong phạm vi của lambda:Tôi có thể tham khảo một lambda từ bên trong chính nó bằng cách sử dụng Ruby không?

fac = lambda { |n| n == 1 ? 1 : n * fac.call(n - 1) } 
fac.call(5) 

Nhưng, tôi muốn để có thể làm như sau (không có lý do thực tế như được nêu ra, tôi m chỉ quan tâm đến việc khám phá ngôn ngữ một số chi tiết):

(lambda { |n| n == 1 ? 1 : n * self.call(n - 1) }).call(5) 

tôi biết rằng sẽ không làm việc, bởi vì self là đối tượng main. Tôi có làm sai không? Tôi đang cố gắng làm điều gì đó không thể - và nếu không, điều này là do một số hạn chế về mặt lý thuyết hay đơn giản là nó không được triển khai trong Ruby?

+5

Bạn có quen thuộc với Y Combinator? Nó có thể không phải là giải pháp thực tế tốt nhất, nhưng từ quan điểm lý thuyết thì nó rất thú vị. Nếu bạn không, hãy xem [bài viết này] (http://nex-3.com/posts/43-fun-with-the-y-combinator-in-ruby). Hãy cẩn thận, nó có thể thổi não của bạn ra ngoài. –

Trả lời

5

Dường như chức năng ẩn danh thực sự không có bất kỳ tham chiếu nào. Bạn có thể kiểm tra nó bằng cách callee

lambda{ __callee__ }.call #=> nil 

Và không có tham chiếu bạn không thể gọi chức năng này. tôi có thể đề nghị bạn chỉ một chút biến sạch hơn:

(fac = lambda{ |n| n==1 ? 1 : n*fac.call(n-1) }).call(5) 
+1

Nó sẽ * thực sự * là một sạch hơn rất nhiều để chỉ cần tạo ra một chức năng được đặt tên cho điều đó. –

+0

Vâng, phiên bản gốc của tôi đã gói lambda trong dấu ngoặc đơn và tôi chạy một .call() trên đó. Đây không thực sự là một câu hỏi ngắn gọn về mã, mặc dù, nó chỉ là sâu hơn về lỗ hổng thỏ chức năng mà Ruby có thể đi.KL-7 có một bình luận ở trên liên kết với một bài báo mô tả bộ kết hợp Y là một bài đọc rất thú vị. –

7

Trong ví dụ sau, lambda vẫn còn vô danh, nhưng nó có một tài liệu tham khảo. (Điều đó vượt qua cho nặc danh?)

(l = lambda { l.call }).call 

(Nhờ Niklas B. để chỉ ra các lỗi trong câu trả lời ban đầu của tôi, tôi đã chỉ được thử nghiệm nó trong IRB và nó đã làm việc ở đó).

Điều này tất nhiên kết thúc bằng lỗi SystemStackError: stack level too deep, nhưng nó thể hiện mục đích.

1

Ngoài KL-7's comment, đây là một giải pháp Y Combinator:

lambda { |f| 
    lambda { |x| x.call(x) }.call(
    lambda { |x| f.call(lambda { |v| x.call(x).call(v) }) }) 
}.call(
    lambda { |f| 
    lambda { |n| n == 0 ? 1 : n * f.call(n - 1) } 
    } 
).call(5) #=> 120 

Bạn thường sẽ chia sau đây:

y = lambda { |f| 
    lambda { |x| x.call(x) }.call(
    lambda { |x| f.call(lambda { |v| x.call(x).call(v) }) }) 
} 

fac = y.call(
    lambda { |f| lambda { |n| n == 0 ? 1 : n * f.call(n - 1) } } 
) 

fac.call(5) #=> 120 

Lưu ý rằng mặc dù fac đã được phân công, nó không được sử dụng trong lambda .

Tôi muốn sử dụng -> cú pháp Ruby và .() thay vì .call():

y = ->(f) { 
    ->(x) { x.(x) }.(
    ->(x) { f.(->(v) { x.(x).(v) }) }) 
} 

fac = y.(->(f) { 
    ->(n) { n == 0 ? 1 : n * f.(n - 1) } 
}) 

fac.(5) #=> 120 

Các y gọi có thể được đơn giản hóa một chút bằng cách sử dụng curry:

y = ->(f) { 
    ->(x) { x.(x) }.(
    ->(x) { f.curry.(->(v) { x.(x).(v) }) }) 
} 

fac = y.(
    ->(f, n) { n == 0 ? 1 : n * f.(n - 1) } 
) 

fac.(5) #=> 120 
Các vấn đề liên quan