Jaka jest różnica między proc a lambda w Rubim?

176

A kiedy użyłbyś jednego zamiast drugiego?

Tom Lehman
źródło
Oprócz odpowiedzi jtbandes męska, istnieje również różnica w tym, co returnpowraca oświadczenie w procporównaniu lambda.
Ken Bloom,
5
Oto jeden dobry blog na tym samym awaxman11.github.io/blog/2013/08/05/…
Arup Rakshit,
2
Oto bardziej szczegółowa odpowiedź: stackoverflow.com/questions/626/…
Dan KK

Odpowiedzi:

260

Jedna różnica dotyczy sposobu, w jaki radzą sobie z argumentami. Tworzenie proc przy użyciu proc {}i Proc.new {}są równoważne. Jednak użycie lambda {}daje ci proces, który sprawdza liczbę argumentów przekazanych do niego. Od ri Kernel#lambda:

Równoważne z Proc.new , z tym wyjątkiem, że wynikowe obiekty Proc sprawdzają liczbę parametrów przekazanych podczas wywołania.

Przykład:

p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
p.call 1, 2 # => 5
p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass
p.call 1, 2, 3 # => 5
l = lambda {|a, b| puts a**2+b**2 } # => #<Proc:0x15016c@(irb):5 (lambda)>
l.call 1, 2 # => 5
l.call 1 # => ArgumentError: wrong number of arguments (1 for 2)
l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2)

Ponadto, jak podkreśla Ken, użycie returnwewnątrz lambdy zwraca wartość tej lambdy, ale użycie returnw proc zwraca z otaczającego bloku.

lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return

Więc dla większości szybkich zastosowań są takie same, ale jeśli chcesz automatycznie, ścisłe sprawdzanie argumentów (co czasami może pomóc w debugowaniu) lub jeśli potrzebujesz użyć returninstrukcji do zwrócenia wartości proc, użyj lambda.

jtbandes
źródło
8
Czy słuszne byłoby stwierdzenie, że lambdy są bardzo podobne do metod (sprawdzanie argumentów i powrót zwróci z nich), podczas gdy procesy są bardzo podobne do bloków (argumenty nie są sprawdzane, a wynik zwróci z metody zawierającej lub lambdy)?
pedz
Byłem w Bóg jeden wie, ile stron internetowych i artykułów do tej pory i wydaje się, że nikt nie mówi o użyteczności Procs, metod i lambd. Każde wyjaśnienie zawiera tylko rozszczepiające włosy szczegóły dotyczące tego, jak wartości zwracane itp. Są różne, ale nie wyjaśnia, dlaczego ma to znaczenie. Na razie muszę stwierdzić, że w Rubim jest to bałagan projektowy.
ankush981
76

Prawdziwa różnica między procesami a lambdami ma wszystko wspólnego ze słowami kluczowymi przepływu sterowania. Mówię return, raise, break, redo, retryitd. - tymi słowami kontrolnymi. Powiedzmy, że masz instrukcję return w proc. Gdy wywołasz swój proc, nie tylko wyrzuci Cię z niego, ale także wróci z otaczającej metody, np .:

def my_method
  puts "before proc"
  my_proc = Proc.new do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method

shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc

Finał putsw metodzie nigdy nie został wykonany, ponieważ kiedy wywołaliśmy nasz proces, returnjego wnętrze wyrzuciło nas z metody. Jeśli jednak zamienimy nasz proc na lambdę, otrzymamy:

def my_method
  puts "before proc"
  my_proc = lambda do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
after proc

Zwrot w lambdzie powoduje tylko wyrzucenie nas z samej lambdy, a otaczająca metoda kontynuuje wykonywanie. Sposób, w jaki słowa kluczowe przepływu sterowania są traktowane w procedurach i lambdach, jest główną różnicą między nimi

shoaib
źródło
7

Istnieją tylko dwie główne różnice.

  • Najpierw a lambdasprawdza liczbę przekazanych do niego argumentów, a procnie. Oznacza to, że a lambdazgłosi błąd, jeśli przekażesz mu niewłaściwą liczbę argumentów, podczas gdy a proczignoruje nieoczekiwane argumenty i przypisze nildo tych, których brakuje.
  • Po drugie, kiedy a lambdapowraca, przekazuje kontrolę z powrotem do metody wywołującej; kiedy a proczwraca, robi to natychmiast, bez wracania do metody wywołującej.

Aby zobaczyć, jak to działa, spójrz na poniższy kod. Nasza pierwsza metoda wywołuje a proc; druga nazywa a lambda.

def batman_ironman_proc
  victor = Proc.new { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_proc # prints "Batman will win!"

def batman_ironman_lambda
  victor = lambda { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_lambda # prints "Iron Man will win!"

Zobacz, jak procmówi „Batman wygra!”, Ponieważ wraca natychmiast, bez powrotu do metody batman_ironman_proc.

Nasz lambdajednak wraca do metody po wywołaniu, więc metoda zwraca ostatni kod, który ocenia: „Iron Man wygra!”

Rajkaran Mishra
źródło
5

# Przykłady procesów

p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              # The '&' tells ruby to turn the proc into a block 

proc = Proc.new { puts "Hello World" }
proc.call

# Przykłady lambda

lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call           

Różnice między procesami a lambdami

Zanim przejdę do różnic między procesami a lambdami, ważne jest, aby wspomnieć, że oba są obiektami Proc.

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }

proc.class # returns 'Proc'
lam.class  # returns 'Proc'

Jednak lambdy to inny „smak” procesów. Ta niewielka różnica jest widoczna podczas zwracania obiektów.

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'

1. Lambdy sprawdzają liczbę argumentów, podczas gdy procesy nie

lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
lam.call(2)                    # prints out 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)

W przeciwieństwie do tego, procy nie przejmują się, jeśli przekazano im złą liczbę argumentów.

proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2)                   # prints out 2
proc.call                      # returns nil
proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments

2. Lambdy i procesy różnie traktują słowo kluczowe „return”

„return” wewnątrz wyrażenia lambda wyzwala kod bezpośrednio poza kodem lambda

def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # calling lambda_test prints 'Hello World'

'return' wewnątrz proca wyzwala kod poza metodą, w której proc jest wykonywany

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # calling proc_test prints nothing

A żeby odpowiedzieć na Twoje inne pytanie, którego użyć i kiedy? Będę śledził @jtbandes, jak wspomniał

Więc dla większości szybkich zastosowań są takie same, ale jeśli chcesz automatycznie, ścisłe sprawdzanie argumentów (co czasami może pomóc w debugowaniu) lub jeśli musisz użyć instrukcji return, aby zwrócić wartość proc, użyj lambda.

Pierwotnie opublikowane tutaj

Arvind singh
źródło
1

Mówiąc ogólnie, lambdy są bardziej intuicyjne niż procesy, ponieważ są bardziej podobne do metod. Są dość surowe, jeśli chodzi o arity, i po prostu wychodzą, gdy wywołujesz zwrot. Z tego powodu wielu Rubyistów używa lambd jako pierwszego wyboru, chyba że potrzebują specyficznych cech procs.

Procesy: Obiekty klasy Proc. Podobnie jak bloki, są oceniane w zakresie, w którym zostały zdefiniowane. Lambdy: Również obiekty klasy, Procale nieznacznie różnią się od zwykłych procesów. Są domknięciami, takimi jak bloki i procesy, i jako takie są oceniane w zakresie, w którym zostały zdefiniowane.

Tworzenie proc

a = Proc.new { |x| x 2 }

Tworzenie lambdy

b = lambda { |x| x 2 }

spirito_libero
źródło
a = proc { |x| x 2 }jest taki sam jaka = Proc.new { |x| x 2 }
lacostenycoder
1

Oto inny sposób, aby to zrozumieć.

Blok to fragment kodu dołączony do wywołania metody na obiekcie. W poniższym przykładzie self jest instancją anonimowej klasy dziedziczącej po ActionView :: Base we frameworku Rails (który sam zawiera wiele modułów pomocniczych). karta to metoda, którą nazywamy siebie. Przekazujemy argument do metody, a następnie zawsze dołączamy blok na końcu wywołania metody:

self.card :contacts do |c|
  // a chunk of valid ruby code    
end

Ok, więc przekazujemy fragment kodu do metody. Ale jak wykorzystujemy ten blok? Jedną z opcji jest przekonwertowanie fragmentu kodu na obiekt. Ruby oferuje trzy sposoby na przekształcenie fragmentu kodu w obiekt

# lambda
> l = lambda { |a| a + 1 }
> l.call(1)
=> 2 

# Proc.new
> l2= Proc.new { |a| a + 1 }
> l2.call(1)
=> 2 

# & as the last method argument with a local variable name
def add(&block)
end

W powyższej metodzie & konwertuje blok przekazany do metody na obiekt i zapisuje ten obiekt w bloku zmiennej lokalnej. W rzeczywistości możemy pokazać, że zachowuje się tak samo jak lambda i Proc.new:

def add(&block)
  block
end

l3 = add { |a| a + 1 }
l3.call(1)
=> 2

To jest ważne. Kiedy przekazujesz blok do metody i konwertujesz ją za pomocą &, tworzony przez niego obiekt używa Proc.new do wykonania konwersji.

Zauważ, że unikałem użycia "proc" jako opcji. To dlatego, że jest to Ruby 1.8, jest taki sam jak lambda, aw Ruby 1.9 to to samo, co Proc.new i we wszystkich wersjach Rubiego należy tego unikać.

Więc pytasz jaka jest różnica między lambda i Proc.new?

Po pierwsze, jeśli chodzi o przekazywanie parametrów, lambda zachowuje się jak wywołanie metody. Zgłosi wyjątek, jeśli podasz niewłaściwą liczbę argumentów. Natomiast Proc.new zachowuje się jak przypisanie równoległe. Wszystkie nieużywane argumenty są zamieniane na zero:

> l = lambda {|a,b| puts "#{a} + #{b}" }
 => #<Proc:0x007fbffcb47e40@(irb):19 (lambda)> 
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)

> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21> 
> l2.call(1)
1 + 

Po drugie, lambda i Proc.new inaczej obsługują słowo kluczowe return. Kiedy wykonujesz powrót wewnątrz Proc.new, w rzeczywistości wraca on z metody otaczającej, czyli z otaczającego kontekstu. Kiedy wracasz z bloku lambda, po prostu wraca z bloku, a nie z otaczającej metody. Zasadniczo wychodzi z wywołania do bloku i kontynuuje wykonywanie z resztą otaczającej metody.

> def add(a,b)
  l = Proc.new { return a + b}
  l.call
  puts "now exiting method"
end
> add(1,1)
=> 2  # NOTICE it never prints the message "now exiting method"

> def add(a,b)
  l = lambda { return a + b }
  l.call
  puts "now exiting method"
end
> add(1,1)
=> now exiting method  # NOTICE this time it prints the message "now exiting method"

Skąd więc ta różnica w zachowaniu? Powodem jest to, że dzięki Proc.new możemy używać iteratorów w kontekście otaczających metod i wyciągać logiczne wnioski. Spójrz na ten przykład:

> def print(max)
  [1,2,3,4,5].each do |val|
    puts val
    return if val > max
  end
end
> print(3)
1
2
3
4

Oczekujemy, że gdy wywołasz return wewnątrz iteratora, zwróci on z otaczającej metody. Pamiętaj, że bloki przekazywane do iteratorów są konwertowane na obiekty przy użyciu Proc.new i dlatego kiedy używamy return, zakończy on otaczającą metodę.

Możesz myśleć o lambdach jako o metodach anonimowych, izolują one poszczególne bloki kodu w obiekt, który można traktować jak metodę. Ostatecznie pomyśl, że lambda zachowuje się jak anomiczna metoda, a Proc.new zachowuje się jak kod wbudowany.

Donato
źródło
0

Pomocny post o przewodnikach po Ruby: blokach, procesach i lambdach

Procenty zwracają się z bieżącej metody, podczas gdy lambdy wracają z samej lambdy.

Procesy nie przejmują się poprawną liczbą argumentów, podczas gdy lambdy będą zgłaszać wyjątek.

Sindhu Shree
źródło
-3

różnica między proc a lambda polega na tym, że proc jest po prostu kopią kodu z zastępowanymi argumentami po kolei, podczas gdy lambda jest funkcją podobną do innych języków. (zachowanie zwrotu, sprawdzenie argumentów)

Nighon
źródło