2012-03-01 18 views
16

Chcę móc wywołać anonimową lambdę z wewnątrz używając Ruby. Rozważmy następujący blok rekurencyjny (zwraca silnię). Wiem, że można przypisać ją do zmiennej, a zmienna jest w zakresie lambda:Czy mogę odwoływać się do lambda z wewnątrz używając Ruby?

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

Ale chcę, aby być w stanie wykonać następujące czynności (bez rozumu praktycznego, jak dotąd, I” m tylko zainteresowani badaniem języka niektóre więcej):

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

wiem że nie będzie działać, ponieważ self jest przedmiotem main. Czy robię to źle? Czy próbuję zrobić coś, co nie jest możliwe - a jeśli nie, czy to z powodu pewnych teoretycznych ograniczeń, czy też nie jest po prostu zaimplementowane w Ruby?

+5

Czy znasz Y Combinator? To może nie być najlepsze praktyczne rozwiązanie, ale z teoretycznego punktu widzenia jest bardzo interesujące. Jeśli nie, spójrz na [ten artykuł] (http://nex-3.com/posts/43-fun-with-the-y-combinator-in-ruby). Bądź ostrożny, może wydmuchać ci mózg. –

Odpowiedz

5

Wygląda na to, że anonimowa funkcja naprawdę nie ma żadnego odniesienia. Można to sprawdzić przez callee

lambda{ __callee__ }.call #=> nil 

I bez odniesienia nie można wywołać tę funkcję. mogę zaproponować wam tylko trochę bardziej czysty Wariant:

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

Byłoby * naprawdę * dużo czystsze, aby po prostu utworzyć nazwaną funkcję do tego. –

+0

Tak, moja oryginalna wersja zawinęła lambdę w nawiasie i uruchomiłem tam funkcję .call(). Nie jest to jednak kwestia zwięzłości kodu, ale bardziej chodzi o to, jak głęboko może funkcjonować królicza nora Ruby.KL-7 ma komentarz powyżej linku do artykułu opisującego kombinator Y, który jest bardzo interesującą lekturą. –

7

W poniższym przykładzie lambda jest nadal anonimowe, ale ma odniesienie. (Czy to uchodzić za anonimowy?)

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

(dzięki Niklas B. za wskazanie błędu w moim pierwotnym odpowiedź; ja tylko testowałem to w IRB i pracował tam).

To oczywiście kończy się błędem SystemStackError: stack level too deep, ale demonstruje cel.

1

Oprócz KL-7's comment, oto rozwiązanie 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 

byś normalnie podzielić te:

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 

Należy zauważyć, że chociaż fac jest przypisany, nie jest używany wewnątrz lambda .

użyję -> składni Ruby i .() zamiast .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 

y inwokacja można uprościć nieco za pomocą 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 
Powiązane problemy