Kiedy stosować lambda, kiedy stosować Proc. Nowe?

336

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?
Michiel de Mare
źródło
3
Zobacz także: książka Ruby Programming Language autorstwa Matza i Flanagana, obszernie omówiła ten temat. proc zachowuje się jak semantyka blokowo-wydajna, gdzie jak lambda zachowuje się jak semantyka wywołania metody. Również return, break, i. wszyscy zachowują się inaczej w procs n lambdas
Gishu
1
Zobacz także szczegółowy post na temat Różnic w przepływie kontroli między Ruby Procs a Lambdasem
Akshay Rawat
zaakceptowałeś odpowiedź, która mówi tylko, jaka jest różnica między proc a lambda, a tytuł twojego pytania brzmi, kiedy używać tych rzeczy
Shri

Odpowiedzi:

378

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:

  • W lambdautworzonym proc, returninstrukcja zwraca tylko od samego proc
  • W Proc.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.

Joey deVilla
źródło
12
Jest też procmetoda. Czy to tylko skrót Proc.new?
panzi
6
@panzi, tak, procjest odpowiednikiemProc.new
ma11hew28,
4
@mattdipasquale W moich testach proczachowuje się jak lambdai nie Proc.neww odniesieniu do instrukcji return. Oznacza to, że rubinowy dokument jest niedokładny.
Kelvin
31
@mattdipasquale Przepraszam, miałem tylko połowę racji. procdziała jak lambdaw 1.8, ale działa jak Proc.neww 1.9. Zobacz odpowiedź Petera Wageneta.
Kelvin
55
Dlaczego to „zaskakujące” zachowanie? A 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.
Arcolye
96

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.

Peter Wagenet
źródło
1
„” „Jednak jeśli weźmiesz pod uwagę, że Proc.new zachowuje się jak blok, nie jest to zaskakujące, ponieważ dokładnie tak zachowują się bloki.” „” <- blok jest częścią obiektu, podczas gdy Proc.new tworzy obiekt. Zarówno lambda, jak i Proc.new tworzy obiekt, którego klasą jest Proc, dlaczego diff?
słaby
1
Jako Ruby 2.5, breakod podbicia proca LocalJumpError, natomiast breakod lambdas zachowuje się podobnie return( tzn , return nil).
Masa Sakano
43

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ę).

Mike Stone
źródło
2
procs również powracają inaczej niż lambdas.
Cam
„” „Proc.new konwertuje brakujące argumenty na zero” ”„ Proc.new ignoruje również dodatkowe argumenty (oczywiście lambda narzeka na to z błędem).
słaby
16

Proc jest starszy, ale semantyka powrotu jest dla mnie wysoce sprzeczna z intuicją (przynajmniej kiedy uczyłam się języka), ponieważ:

  1. Jeśli używasz proc, najprawdopodobniej używasz jakiegoś funkcjonalnego paradygmatu.
  2. Proc może powrócić z otaczającego zakresu (patrz poprzednie odpowiedzi), co jest zasadniczo goto i wysoce niefunkcjonalne z natury.

Lambda jest funkcjonalnie bezpieczniejsza i łatwiejsza do uzasadnienia - zawsze używam jej zamiast proc.

Charles Caldwell
źródło
11

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

webmat
źródło
Opcjonalne parametry w lambda były bardzo potrzebne, cieszę się, że dodały to w wersji 1.9. Zakładam, że bloki mogą mieć również opcjonalne parametry (w 1.9)?
MPD
nie wyświetlasz domyślnych parametrów w blokach, tylko lambdas
iconoclast
11

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.

Evan Moran
źródło
1
„lambda powraca z siebie, a proc powraca z siebie ORAZ funkcja, która ją wywołała” jest po prostu błędna i bardzo powszechnym nieporozumieniem. Proc to zamknięcie i powrót z metody, która go utworzyła. Zobacz moją pełną odpowiedź w innym miejscu na stronie.
ComDubh
10

Dobrym sposobem, aby to zobaczyć, jest to, że lambd są wykonywane we własnym zakresie (jak gdyby było to wywołanie metody), podczas gdy Procs może być postrzegany jako wykonywany zgodnie z metodą wywołującą, przynajmniej to dobry sposób na decyzję, którego użyć w każdej sprawie.

krusty.ar
źródło
8

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
Dave Rapin
źródło
1
Matz oświadczył, że planuje go wycofać, ponieważ mylące jest, że proc i nowe zwracają różne wyniki. W wersji 1.9 zachowują się tak samo (proc to alias do Proc. New). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin
@banister: 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
Gishu
Myślę, że kilof mówi gdzieś w przypisie, że proc jest skutecznie osłabiony czy coś. Nie mam dokładnego numeru strony.
dertoni
7

Zamknięcia w Ruby to dobry przegląd tego, jak działają bloki, lambda i proc w Rubim, z Ruby.

swrobel
źródło
Przestałem to czytać po przeczytaniu „funkcja nie może zaakceptować wielu bloków - naruszając zasadę, że zamknięcia mogą być swobodnie przekazywane jako wartości”. Bloki nie są zamknięciami. Procesory są, a funkcja może akceptować wiele procesów.
ComDubh
5

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.

słaby
źródło
3

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

Peeja
źródło
3

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 naProc obiekt.

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!
Aleksei Matiushkin
źródło
Co ciekawe, robi to samo, co deklarowanie &blockargumentu na def, ale bez konieczności robienia tego na liście def arg.
jrochkind
2

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.

ComDubh
źródło
1

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

Orion Edwards
źródło
2
Aby zaktualizować: procs można teraz tworzyć za pomocą proc {}. Nie jestem pewien, kiedy to zadziałało, ale jest (nieco) łatwiejsze niż wpisywanie Proc. New.
aceofbassgreg