Яка різниця між блюдом і лямбда в Рубі?


176

А коли б ти скористався одним, а не іншим?


Окрім відповіді jtbandes, є також різниця в тому, від чого returnповертається твердження procпроти lambda.
Кен Блум

5
Ось один хороший блог у тому ж самому awaxman11.github.io/blog/2013/08/05/…
Arup Rakshit

2
Ось більш детальна відповідь: stackoverflow.com/questions/626/…
Dan KK

Відповіді:


260

Одна відмінність полягає в тому, як вони обробляють аргументи. Створення закупівлі за допомогою proc {}та Proc.new {}еквівалент. Однак, використовуючи, lambda {}ви отримуєте Proc, який перевіряє кількість аргументів, переданих йому. Від ri Kernel#lambda:

Еквівалентно Proc.new , за винятком об'єктів Proc, що перевіряють кількість параметрів, переданих при виклику .

Приклад:

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)

Крім того, як вказує Кен, використовуючи returnвсередині лямбда повертає значення цієї лямбда, але використовуючи returnв програмі, повертається з блоку, що додається.

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

Отже, для більшості швидких застосувань вони однакові, але якщо ви хочете автоматичну строгу перевірку аргументів (що також іноді може допомогти при налагодженні), або якщо вам потрібно використовувати returnоператор для повернення значення proc, використовуйте lambda.


8
Чи було б точно сказати, що лямбдаси дуже схожі на методи (перевірити аргументи і повернення повернеться з них), а програми дуже схожі на блоки (аргументи не перевіряються, а повернення повернеться з методу, що містить або лямбда)?
pedz

Я хотів, щоб Бог знав, скільки веб-сайтів та статей на сьогоднішній день, і, здається, ніхто не говорить про корисність Procs проти методів проти лямбда. Кожне пояснення лише містить детальну інформацію про розбиття волосся, як повертаються значення тощо, але жодне питання про те, чому це важливо. Поки що я маю зробити висновок, що це дизайнерський безлад у Рубі.
ankush981

76

Справжня різниця між програмами та лямбдами пов'язана з ключовими словами управління потоком. Я говорю про те return, raise, break, redo, і retryт.д. - тих керуючих словах. Скажімо, у вас є заява про повернення в проц. Коли ви зателефонуєте на ваш прок, він не тільки скине вас з нього, але й повернеться із способу, що додає, наприклад:

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

Фінал putsу методі ніколи не був виконаний, оскільки коли ми зателефонували нашому процесору, returnвсередині нього викинуло нас із методу. Якщо, однак, ми перетворимо наш Pro в лямбда, ми отримаємо наступне:

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

Повернення в межах лямбда лише витісняє нас із самої лямбда, і метод укладання продовжує виконувати. Те, як ключові слова управління потоком обробляються в документах і лямбдах, є основною відмінністю між ними


7

Є лише дві основні відмінності.

  • По-перше, lambdaперевіряє кількість переданих йому аргументів, а a proc- ні. Це означає, що алієтика lambdaвидасть помилку, якщо ви передасте їй неправильну кількість аргументів, тоді як procархів буде ігнорувати несподівані аргументи та призначати nilбудь-які відсутні.
  • По-друге, коли lambdaповертається, він передає управління назад методу виклику; коли procповертається, це робить це негайно, не повертаючись до методу виклику.

Щоб побачити, як це працює, подивіться на код нижче. Наш перший метод називає a proc; другий дзвінки а 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!"

Подивіться, як procговорить "Бетмен переможе!", Це тому, що він повертається негайно, не повертаючись до методу batman_ironman_proc.

Наша lambda, однак, повертається в метод після виклику, тому метод повертає код останнього він оцінює: «Залізна людина переможе»


5

# Приклади програми

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

# Приклади лямбда

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

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

Відмінності між Проксом і Лямбда

Перш ніж я потрапляю на відмінності між документами та лямбдами, важливо згадати, що вони обидва об'єкти Proc.

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

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

Однак лямбда - це інший "аромат" програм. Ця незначна різниця проявляється при поверненні предметів.

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

1. Лямбда перевіряє кількість аргументів, тоді як протоколи - ні

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)

На противагу цьому, програмі не важливо, чи їм передано неправильну кількість аргументів.

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. Лямбди та програми трактують ключове слово "повернення" по-різному

'return' всередині лямбда запускає код прямо поза кодом лямбда

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

lambda_test                 # calling lambda_test prints 'Hello World'

'return' всередині proc запускає код поза методом, у якому виконуються proc

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

proc_test                 # calling proc_test prints nothing

І щоб відповісти на ваш інший запит, який з них використовувати і коли? Я піду за @jtbandes, як він уже згадував

Отже, для більшості швидких застосувань вони однакові, але якщо ви хочете автоматичну строгу перевірку аргументів (що також іноді може допомогти при налагодженні), або якщо вам потрібно використовувати оператор return, щоб повернути значення proc, використовуйте лямбда.

Спочатку розміщено тут


1

Взагалі кажучи, лямбди більш інтуїтивні, ніж програми, тому що вони більш схожі на методи. Вони досить суворо ставляться до сваволі, і вони просто виходять, коли ви дзвоните. З цієї причини багато рубіністів використовують лямбда в якості першого вибору, якщо їм не потрібні специфічні особливості програм.

Документи: Об’єкти класу Proc. Як і блоки, вони оцінюються в області, де вони визначені. Лямбда: також об'єкти класу, Procале тонко відрізняються від звичайних проектів. Вони закриваються як блоки та документи, і як такі вони оцінюються в області, де вони визначені.

Створення Proc

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

Створення лямбда

b = lambda { |x| x 2 }


a = proc { |x| x 2 }те саме, щоa = Proc.new { |x| x 2 }
лакостенікодер

1

Ось ще один спосіб зрозуміти це.

Блок - це фрагмент коду, приєднаний до виклику до виклику методу на об'єкті. У наведеному нижче прикладі self - це екземпляр анонімного класу, успадкованого від ActionView :: Base в рамках Rails (який сам включає багато допоміжних модулів). картка - це метод, який ми закликаємо до себе. Ми передаємо аргумент методу, а потім завжди прикріплюємо блок до кінця виклику методу:

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

Гаразд, тому ми передаємо шматок коду методу. Але як ми можемо використовувати цей блок? Один із варіантів - перетворити фрагмент коду в об'єкт. Рубі пропонує три способи перетворення фрагмента коду в об'єкт

# 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

У вищеописаному методі & перетворює переданий блок методу в об'єкт і зберігає цей об'єкт у локальному блоці змінної. Насправді ми можемо показати, що вона має таку саму поведінку, як лямбда та Proc.new:

def add(&block)
  block
end

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

Це ВАЖЛИВО. Коли ви передаєте блок методу та перетворюєте його за допомогою &, об'єкт, який він створює, використовує Proc.new для перетворення.

Зауважте, що я уникав використання "proc" як опції. Це тому, що це Ruby 1.8, це те саме, що і лямбда, а у Ruby 1.9 - це те саме, що і Proc.new, і у всіх версіях Ruby цього слід уникати.

Тоді ви запитаєте, в чому різниця між лямбда і Proc.new?

По-перше, що стосується передачі параметрів, лямбда поводиться як виклик методу. Це призведе до виключення, якщо ви передасте неправильну кількість аргументів. Навпаки, Proc.new поводиться як паралельне призначення. Усі невикористані аргументи перетворюються на нуль:

> 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 + 

По-друге, лямбда та Proc.new по-різному обробляють ключове слово повернення. Коли ви робите повернення всередину Proc.new, воно фактично повертається із методу, що додає, тобто оточуючого контексту. Коли ви повертаєтесь з лямбда-блоку, він просто повертається з блоку, а не методу укладання. В основному, він виходить з виклику до блоку і продовжує виконання з рештою способу, що додає.

> 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"

То чому ця поведінкова різниця? Причина полягає в тому, що за допомогою Proc.new ми можемо використовувати ітератори всередині контексту методів, що складаються укладаючи, та робити логічні висновки. Подивіться на цей приклад:

> 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

Ми очікуємо, що коли ми будемо викликати повернення всередині ітератора, він повернеться із методу, що додає. Пам'ятайте, що блоки, передані ітераторам, перетворюються на об'єкти за допомогою Proc.new, і саме тому, коли ми будемо використовувати return, він вийде з методу укладання.

Ви можете вважати лямбдаси як анонімні методи, вони виділяють окремі блоки коду в об’єкт, який можна розглядати як метод. Зрештою, подумайте про лямбду як про аномічний метод, а Proc.new поводиться як вбудований код.


0

Корисна публікація про рубінові путівники: блоки, програми та лямбда

Програми повертаються з поточного методу, в той час як лямбда повертаються з самої лямбда.

Програми не переймаються правильною кількістю аргументів, в той час як лямбди будуть винятком.


-3

Відмінність proc і lambda полягає в тому, що proc - це лише копія коду з аргументами, заміненими по черзі, тоді як lambda - функція, як і в інших мовах. (поведінка повернення, перевірка аргументів)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.