W Ruby 1.8 istnieją subtelne różnice między proc / lambda z jednej strony, a Proc.newdrugą.
- 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.newdrugą.
Odpowiedzi:
Inną ważną, ale subtelną różnicą między procs utworzonymi za pomocą lambdai procs utworzonych za pomocą Proc.newjest sposób obsługi returninstrukcji:
lambdautworzonym proc, returninstrukcja zwraca tylko od samego procProc.newutworzonym proc, returninstrukcja jest nieco bardziej zaskakująca: zwraca kontrolę nie tylko z proc, ale także z metody otaczającej proc!Oto lambdautworzone procy returnw 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.newproc returnrobi 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 lambdaponad Proc.newpodczas dokonywania procs.
procmetoda. Czy to tylko skrót Proc.new?
procjest odpowiednikiemProc.new
proczachowuje się jak lambdai nie Proc.neww odniesieniu do instrukcji return. Oznacza to, że rubinowy dokument jest niedokładny.
procdziała jak lambdaw 1.8, ale działa jak Proc.neww 1.9. Zobacz odpowiedź Petera Wageneta.
lambdajest 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 Procjest 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.newjest 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.
breakinstrukcje 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, redoi raisezachowuj się tak samo w Procs i lambdas. Chociaż retrynie jest dozwolone w żadnym z nich i spowoduje wyjątek.
I wreszcie, procnigdy 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.
breakod podbicia proca LocalJumpError, natomiast breakod lambdas zachowuje się podobnie return( tzn , return nil).
Znalazłem tę stronę, która pokazuje, jaka jest różnica między Proc.newi 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.newkonwertuje 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
proczwró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.newjest zaskakujące i mylące.
returnOświadczenie w proc utworzonego przez Proc.newnie 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.newwstawia kod do metody zamykającej, podobnie jak blok. Ale Proc.newtworzy 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.newignoruje 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, procw 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.newtworzy proces, przechodząc blok. Uważam, że lambda {...}jest to parsowane raczej jako dosłowność, niż wywołanie metody, które przechodzi przez blok. returning 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.newogóle nie wspomniano w komentarzach. Zgodnie z dokumentacją :
Proc::newmoże być wywoływany bez bloku tylko w metodzie z dołączonym blokiem, w którym to przypadku blok ten jest konwertowany naProcobiekt.
Powiedziawszy to, Proc.newpozwólmy łańcuchowi metod uzyskiwania:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&blockargumentu na def, ale bez konieczności robienia tego na liście def arg.
Warto podkreślić, że returnw 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 fooi dlatego returnkończy się foo, nie tylko foobar. Jak napisał Charles Caldwell powyżej, ma w sobie GOTO. Moim zdaniem returnjest 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 returnIMHO 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.