W Ruby 1.8 istnieją subtelne różnice między proc / lambda z jednej strony, a Proc.new
drugą.
- Jakie są te różnice?
- Czy możesz podać wskazówki, jak zdecydować, który wybrać?
- W Ruby 1.9 proc i lambda są różne. O co chodzi?
W Ruby 1.8 istnieją subtelne różnice między proc / lambda z jednej strony, a Proc.new
drugą.
Odpowiedzi:
Inną ważną, ale subtelną różnicą między procs utworzonymi za pomocą lambda
i procs utworzonych za pomocą Proc.new
jest sposób obsługi return
instrukcji:
lambda
utworzonym proc, return
instrukcja zwraca tylko od samego procProc.new
utworzonym proc, return
instrukcja jest nieco bardziej zaskakująca: zwraca kontrolę nie tylko z proc, ale także z metody otaczającej proc!Oto lambda
utworzone procy return
w akcji. Zachowuje się w sposób, którego prawdopodobnie się spodziewasz:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
Teraz tutaj-stworzony Proc.new
proc return
robi to samo. Za chwilę zobaczysz jeden z przypadków, w których Ruby łamie bardzo chwaloną zasadę najmniejszej niespodzianki:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
Dzięki tej zaskakującej zachowania (jak również mniej pisania), ja faworyzują przy użyciu lambda
ponad Proc.new
podczas dokonywania procs.
proc
metoda. Czy to tylko skrót Proc.new
?
proc
jest odpowiednikiemProc.new
proc
zachowuje się jak lambda
i nie Proc.new
w odniesieniu do instrukcji return. Oznacza to, że rubinowy dokument jest niedokładny.
proc
działa jak lambda
w 1.8, ale działa jak Proc.new
w 1.9. Zobacz odpowiedź Petera Wageneta.
lambda
jest anonimową metodą. Ponieważ jest to metoda, zwraca wartość, a metoda, która ją wywołała, może zrobić z nią, co chce, włączając w to zignorowanie jej i zwrócenie innej wartości. A Proc
jest jak wklejanie fragmentu kodu. To nie działa jak metoda. Więc kiedy zwrot nastąpi w obrębie Proc
, to tylko część kodu metody, która go wywołała.
Aby udzielić dalszych wyjaśnień:
Joey mówi, że zachowanie powrotu Proc.new
jest zaskakujące. Jednak biorąc pod uwagę, że Proc.new zachowuje się jak blok, nie jest to zaskakujące, ponieważ dokładnie tak zachowują się bloki. z drugiej strony lambas zachowują się bardziej jak metody.
To faktycznie tłumaczy, dlaczego Procs są elastyczni, jeśli chodzi o arity (liczba argumentów), podczas gdy lambdas nie. Bloki nie wymagają podania wszystkich argumentów, ale wymagają tego metody (chyba że podano wartość domyślną). Chociaż podanie domyślnego argumentu lambda nie jest opcją w Ruby 1.8, jest ono obecnie obsługiwane w Ruby 1.9 z alternatywną składnią lambda (jak zauważył webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
A Michiel de Mare (OP) nie ma racji co do tego, że Procs i lambda zachowują się tak samo z arity w Ruby 1.9. Sprawdziłem, że nadal zachowują zachowanie z wersji 1.8, jak określono powyżej.
break
instrukcje faktycznie nie mają większego sensu ani w Procs, ani w lambdach. W Procs przerwa spowoduje powrót z Proc.new, który został już ukończony. I nie ma sensu zerwać z lambda, ponieważ jest to w zasadzie metoda i nigdy nie zerwałbyś z najwyższego poziomu metody.
next
, redo
i raise
zachowuj się tak samo w Procs i lambdas. Chociaż retry
nie jest dozwolone w żadnym z nich i spowoduje wyjątek.
I wreszcie, proc
nigdy nie należy stosować tej metody, ponieważ jest niespójna i ma nieoczekiwane zachowanie. W Ruby 1.8 faktycznie zwraca lambda! W Ruby 1.9 zostało to naprawione i zwraca Proc. Jeśli chcesz utworzyć Proc, trzymaj się Proc.new
.
Aby uzyskać więcej informacji, gorąco polecam The Ruby Programming Language O'Reilly'ego, który jest moim źródłem większości tych informacji.
break
od podbicia proca LocalJumpError
, natomiast break
od lambdas zachowuje się podobnie return
( tzn , return nil
).
Znalazłem tę stronę, która pokazuje, jaka jest różnica między Proc.new
i lambda
. Według strony jedyną różnicą jest to, że lambda jest ścisła pod względem liczby argumentów, które akceptuje, podczas gdy Proc.new
konwertuje brakujące argumenty na nil
. Oto przykładowa sesja IRB ilustrująca różnicę:
irb (główny): 001: 0> l = lambda {| x, y | x + y} => # <Proc: 0x00007fc605ec0748 @ (irb): 1> irb (main): 002: 0> p = Proc. new {| x, y | x + y} => # <Proc: 0x00007fc605ea8698 @ (irb): 2> irb (main): 003: 0> l.call "hello", "world" => „helloworld” irb (main): 004: 0> p.call „hello”, „world” => „helloworld” irb (main): 005: 0> l.call "hello" ArgumentError: zła liczba argumentów (1 na 2) z (irb): 1 z (irb): 5: w `call ' z (irb): 5 od: 0 irb (main): 006: 0> p.call "hello" TypeError: nie można przekonwertować wartości zero na String z (irb): 2: w `+ ' z (irb): 2 z (irb): 6: w `call ' z (irb): 6 od: 0
Strona zaleca także użycie lambda, chyba że chcesz zachowania tolerancji na błędy. Zgadzam się z tym sentymentem. Stosowanie lambda wydaje się nieco bardziej zwięzłe, a przy tak nieznacznej różnicy wydaje się lepszym wyborem w przeciętnej sytuacji.
Jeśli chodzi o Ruby 1.9, przepraszam, nie zagłębiłem się jeszcze w 1.9, ale nie sądzę, że zmieniliby to aż tak bardzo (nie wierz mi na to, wydaje się, że słyszałeś o niektórych zmianach, Prawdopodobnie się tam mylę).
Proc jest starszy, ale semantyka powrotu jest dla mnie wysoce sprzeczna z intuicją (przynajmniej kiedy uczyłam się języka), ponieważ:
Lambda jest funkcjonalnie bezpieczniejsza i łatwiejsza do uzasadnienia - zawsze używam jej zamiast proc.
Nie mogę wiele powiedzieć o subtelnych różnicach. Jednak mogę zauważyć, że Ruby 1.9 pozwala teraz na opcjonalne parametry lambd i bloków.
Oto nowa składnia stabby lambdas poniżej 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 nie miał tej składni. Tradycyjny sposób deklarowania bloków / lambdas nie wspierał również opcjonalnych argumentów:
# under 1.8
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
Ruby 1.9 obsługuje jednak opcjonalne argumenty nawet przy starej składni:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
Jeśli chcesz zbudować Ruby1.9 dla Leoparda lub Linuksa, sprawdź ten artykuł (bezwstydna autopromocja).
Krótka odpowiedź: ważne jest to, co return
robi: lambda wraca z siebie, a proc wraca z siebie ORAZ funkcję, która ją wywołała.
Mniej jasne jest, dlaczego chcesz z nich korzystać. lambda jest tym, czego oczekujemy, że rzeczy powinny robić w sensie programowania funkcjonalnego. Jest to w zasadzie anonimowa metoda z automatycznie powiązanym bieżącym zakresem. Z tych dwóch lambda jest tym, którego prawdopodobnie powinieneś używać.
Z drugiej strony Proc jest naprawdę przydatny do implementacji samego języka. Na przykład możesz zaimplementować z nimi pętle „if” lub „for”. Wszelkie zwroty znalezione w proc zwrócą się z metody, która je wywołała, a nie tylko z instrukcji „if”. Tak działają języki, jak działają zdania „jeśli”, więc przypuszczam, że Ruby używa tego pod przykrywkami i po prostu je ujawniło, ponieważ wydawało się to potężne.
Będzie to naprawdę potrzebne, jeśli tworzysz nowe konstrukcje językowe, takie jak pętle, konstrukcje if-else itp.
Nie zauważyłem żadnych komentarzy na temat trzeciej metody w queston, „proc”, która jest przestarzała, ale inaczej potraktowano ją w 1.8 i 1.9.
Oto dość szczegółowy przykład, który ułatwia dostrzeżenie różnic między trzema podobnymi wywołaniami:
def meth1
puts "method start"
pr = lambda { return }
pr.call
puts "method end"
end
def meth2
puts "method start"
pr = Proc.new { return }
pr.call
puts "method end"
end
def meth3
puts "method start"
pr = proc { return }
pr.call
puts "method end"
end
puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
proc
zwrócił lambda w 1.8; teraz naprawiono zwracanie proc w wersji 1.9 - jest to jednak przełomowa zmiana; dlatego nie zaleca się już używania
Zamknięcia w Ruby to dobry przegląd tego, jak działają bloki, lambda i proc w Rubim, z Ruby.
lambda działa zgodnie z oczekiwaniami, podobnie jak w innych językach.
Przewodowe Proc.new
jest zaskakujące i mylące.
return
Oświadczenie w proc utworzonego przez Proc.new
nie tylko zwrócić sterowanie tylko od siebie, ale także od sposobu zamykania go .
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Można argumentować, że Proc.new
wstawia kod do metody zamykającej, podobnie jak blok. Ale Proc.new
tworzy obiekt, podczas gdy blok jest częścią obiektu.
I jest jeszcze jedna różnica między lambda i Proc.new
, która polega na ich (błędnych) argumentach. lambda narzeka na to, a Proc.new
ignoruje dodatkowe argumenty lub uważa brak argumentów za zero.
irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):21:in `block in irb_binding'
from (irb):25:in `call'
from (irb):25
from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
from (irb):47:in `block in irb_binding'
from (irb):49:in `call'
from (irb):49
from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"
BTW, proc
w Ruby 1.8 tworzy lambda, podczas gdy w Ruby 1.9+ zachowuje się jak Proc.new
, co jest naprawdę mylące.
Aby rozwinąć odpowiedź na temat Accordion Guy:
Zauważ, że Proc.new
tworzy proces, przechodząc blok. Uważam, że lambda {...}
jest to parsowane raczej jako dosłowność, niż wywołanie metody, które przechodzi przez blok. return
ing od wewnątrz bloku dołączonego do wywołania metody powróci z metody, a nie z bloku, iProc.new
przypadek jest tego przykładem.
(To jest 1.8. Nie wiem, jak to się tłumaczy na 1.9.)
Trochę się spóźniam, ale jest jedna wspaniała, ale mało znana rzecz, o której w Proc.new
ogóle nie wspomniano w komentarzach. Zgodnie z dokumentacją :
Proc::new
może być wywoływany bez bloku tylko w metodzie z dołączonym blokiem, w którym to przypadku blok ten jest konwertowany naProc
obiekt.
Powiedziawszy to, Proc.new
pozwólmy łańcuchowi metod uzyskiwania:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&block
argumentu na def
, ale bez konieczności robienia tego na liście def arg.
Warto podkreślić, że return
w proc powraca z metody obejmującej leksykalnie, tj. Z metody, w której proc został utworzony , a nie z metody, która wywołała proc. Jest to konsekwencja właściwości zamknięcia procs. Poniższy kod nic nie wyświetla:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
Mimo że proces wykonuje się foobar
, został utworzony foo
i dlatego return
kończy się foo
, nie tylko foobar
. Jak napisał Charles Caldwell powyżej, ma w sobie GOTO. Moim zdaniem return
jest dobrze w bloku, który jest wykonywany w kontekście leksykalnym, ale jest znacznie mniej intuicyjny, gdy jest używany w proc, który jest wykonywany w innym kontekście.
Różnica w zachowaniu z return
IMHO jest najważniejszą różnicą między 2. Ja też wolę lambda, ponieważ jest mniej pisania niż Proc. Nowe :-)
proc {}
. Nie jestem pewien, kiedy to zadziałało, ale jest (nieco) łatwiejsze niż wpisywanie Proc. New.