Коли використовувати лямбда, коли користуватися Proc.new?


336

У Ruby 1.8 є тонкі відмінності між proc / lambda, з одного боку, і Proc.newз іншого.

  • У чому ці відмінності?
  • Чи можете ви дати рекомендації, як вирішити, який вибрати?
  • У Ruby 1.9, proc і lambda різні. Яка угода?

3
Дивіться також: Книга мови програмування Рубі Матца та Фланагана, вона всебічно висвітлювала цю тему. proc поводиться як семантика прибутковості, де як лямбда поводиться як семантика виклику методу. Також повернути, перерву тощо. всі поводяться різними у procs n lambdas
Gishu


ви прийняли відповідь, яка говорить лише про різницю між прок і лямбдою, тоді як назва вашого питання - коли використовувати ці речі
Шрі

Відповіді:


378

Ще одна важлива, але тонка відмінність між програмами, створеними за допомогою, lambdaта програмами, створеними за допомогою, Proc.new- це те, як вони обробляють returnоператор:

  • У lambda-create PRO, returnоператор повертається лише з самого proc
  • У створеному Proc.newпроцесорі returnзаява трохи дивніша : вона повертає контроль не тільки від proc, але і від методу, що додає proc!

Ось - lambdaстворений процес returnв дії. Це поводиться так, що ви, напевно, очікуєте:

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"

Тепер ось створений Proc.newпроцесор returnробить те саме. Ви збираєтесь побачити один із тих випадків, коли Рубі порушує чудовий Принцип найменшого сюрпризу:

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"

Завдяки цьому дивовижному поведінки (а також менш типізації), я , як правило , на користь використання lambdaбільш Proc.newпри прийнятті путь.


12
Тоді також є procметод. Це просто скорочення Proc.new?
панци


4
@mattdipasquale У моїх тестах, procяк lambdaі не подобається Proc.newстосовно повернень. Це означає, що рубіновий документ є неточним.
Келвін

31
@mattdipasquale Вибачте, я мав лише половину права. procдіє як lambdaу 1.8, але діє як Proc.newу 1.9. Дивіться відповідь Пітера Вагенета.
Келвін

55
Чому така "дивна" поведінка? А lambda- анонімний метод. Оскільки це метод, він повертає значення, а метод, який його викликав, може робити з ним все, що завгодно, включаючи його ігнорування та повернення іншого значення. A Proc- це як вставлення у фрагмент коду. Це не так, як метод. Отже, коли повернення відбувається в межах Proc, це лише частина коду методу, який його викликав.
Арколій

96

Для подальшого роз'яснення:

Джоуї говорить, що поведінка повернення Proc.newздивує. Однак якщо ви вважаєте, що Proc.new поводиться як блок, це не дивно, оскільки саме так ведуть себе блоки. лямби з іншого боку поводяться більше як методи.

Це насправді пояснює, чому Procs є гнучким, коли мова йде про арність (кількість аргументів), тоді як лямбда - це не так. Блоки не вимагають надання всіх їх аргументів, але методи (якщо не передбачено за замовчуванням). Хоча надання аргументу lambda за замовчуванням не є варіантом у Ruby 1.8, він тепер підтримується в Ruby 1.9 з альтернативним синтаксисом лямбда (як зазначає webmat):

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

І Мічіель де Маре (ОП) неправильно ставиться до Проків і лямбда поводиться однаково з суровістю в Рубі 1.9. Я переконався, що вони все ще підтримують поведінку від 1,8, як зазначено вище.

breakтвердження насправді не мають великого сенсу ні в Програмах, ні в лямбдах. У Procs перерва поверне вас із Proc.new, яка вже завершена. І не має сенсу відриватися від лямбда, оскільки це по суті метод, і ви ніколи не відірветесь від верхнього рівня методу.

next, redoі raiseповодитись однаково як в Програмах, так і в лямбдах. Тоді як retryце заборонено в будь-якому випадку, і це призведе до виключення.

І нарешті, procметод ніколи не слід застосовувати, оскільки він є непослідовним і має несподівану поведінку. У Ruby 1.8 він фактично повертає лямбда! У Ruby 1.9 це було виправлено, і він повертає Proc. Якщо ви хочете створити Proc, дотримуйтесь Proc.new.

Для отримання додаткової інформації я настійно рекомендую мову програмування The Ruby O'Reilly, яка є моїм джерелом для більшості цієї інформації.


1
Однак, якщо ви вважаєте, що Proc.new веде себе як блок, це не дивно, оскільки саме так ведуть себе блоки. "" <- блок є частиною об'єкта, тоді як Proc.new створює об'єкт. І лямбда, і Proc.new створюють об'єкт, клас якого Proc, чому розрізнення?
слабкий

1
У рубін 2.5, breakз Procs підвищується LocalJumpError, в той час як breakз лямбда поводяться так само , як return( тобто , return nil).
Маса Сакано

43

Я знайшов цю сторінку, яка показує, у чому різниця між Proc.newі в чому lambdaполягає. За словами сторінки, різниця полягає лише в тому, що лямбда суворо до кількості аргументів, які вона приймає, тоді як Proc.newперетворює відсутні аргументи на nil. Ось приклад сеансу IRB, що ілюструє різницю:

irb (основний): 001: 0> l = лямбда {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (основна): 002: 0> p = Proc.new {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (main): 003: 0> l.call "привіт", "world"
=> "helloworld"
irb (main): 004: 0> p.call "привіт", "world"
=> "helloworld"
irb (main): 005: 0> l.call "привіт"
ArgumentError: неправильна кількість аргументів (1 для 2)
    від (irb): 1
    від (irb): 5: у `call '
    від (irb): 5
    від: 0
irb (main): 006: 0> p.call "привіт"
TypeError: не вдається перетворити нуль у String
    від (irb): 2: у `+ '
    від (irb): 2
    від (irb): 6: у `call '
    від (irb): 6
    від: 0

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

Що стосується Ruby 1.9, вибачте, я ще не заглянув у 1.9, але не думаю, що вони все б так змінили (не сприймайте мого слова, хоча, здається, ви чули про якісь зміни, тож Я, мабуть, помиляюся там).


2
документи також повертаються інакше, ніж лямбда.
Кем

"" "Proc.new перетворює відсутні аргументи в нуль" "" Proc.new також ігнорує зайві аргументи (звичайно, лямбда скаржиться на це помилково).
слабкий

16

Програма є старшою, але семантика повернення для мене є дуже протиконтактною (принаймні, коли я вивчав мову), оскільки:

  1. Якщо ви використовуєте Proc, ви, швидше за все, використовуєте якусь функціональну парадигму.
  2. Proc може повернутись із сфери, що додається (див. Попередні відповіді), що в основному є гото і дуже нефункціональним за своєю суттю.

Лямбда функціонально безпечніше і простіше міркувати - я завжди використовую її замість проц.


11

Я не можу сказати багато про тонкі відмінності. Однак можу зазначити, що Ruby 1.9 тепер дозволяє додаткові параметри для лямбда і блоків.

Ось новий синтаксис для стабільних лямбдів під 1.9:

stabby = ->(msg='inside the stabby lambda') { puts msg }

У Ruby 1.8 цього синтаксису не було. Звичайний спосіб оголошення блоків / лямбдашів не підтримував додаткові аргументи:

# 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, однак, підтримує необов'язкові аргументи навіть із старим синтаксисом:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

Якщо ви хочете створити Ruby1.9 для Leopard або Linux, ознайомтеся з цією статтею (безсоромне самореклама).


Необов'язкові парами в лямбда були дуже потрібні, я радий, що вони додали його в 1.9. Я припускаю, що блоки також можуть мати необов'язкові параметри тоді (в 1.9)?
mpd

ти не демонструєш параметри за замовчуванням у блоках, лише лямбдас
iconoclast

11

Коротка відповідь: Важливо те, що returnробить: лямбда повертається із себе, а прок повертається із себе ТА функції, яка її викликала.

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

Proc, з іншого боку, дійсно корисний для реалізації самої мови. Наприклад, ви можете реалізувати з ними оператори "if" або "for". Будь-яке повернення, знайдене в proc, повернеться із методу, який його викликав, а не з просто "if". Ось як працюють мови, як працюють висловлювання "якщо", тож я здогадуюсь, що Рубі використовує це під прикриттями, і вони просто викрили це, тому що це здалося потужним.

Це вам справді знадобиться лише в тому випадку, якщо ви створюєте нові мовні конструкції, такі як цикли, конструкції if-else тощо.


1
"лямбда повертається із себе, а прок повертається із себе І функція, яка її назвала" - це явна помилка і дуже поширене непорозуміння. Процес - це закриття і повертається з методу, який його створив. Дивіться мою повну відповідь в іншому місці на сторінці.
ComDubh

10

Хороший спосіб переконатися в тому, що лямбда виконуються у власному обсязі (як би це був виклик методу), тоді як Програми можуть розглядатися як виконані в порядку з методом виклику, принаймні, це хороший спосіб вирішити, який використовувати. у кожному випадку.


8

Я не помітив жодних коментарів до третього методу в квестоні, "proc", який застарілий, але обробляється по-різному в 1.8 і 1.9.

Ось досить багатослівний приклад, який дозволяє легко побачити відмінності між трьома подібними викликами:

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

1
Матц заявив, що він планує знехтувати його, оскільки це заплутано для того, щоб Pro та Proc.new повертали різні результати. У 1.9 вони поводяться так само (Proc є псевдонімом Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Дейв Рапін

@banister: procповернув лямбда в 1,8; тепер було зафіксовано повернення прок у 1.9 - однак це є надзвичайною зміною; тому більше не рекомендується використовувати
Gishu

Я думаю, що пікакс десь йде у виносці, що Proc фактично втрачається чи щось. У мене немає точного номера сторінки.
дертоні

7

Закриття в Ruby - це хороший огляд того, як в Ruby працюють Ruby, лямбда і Proc з Ruby.


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

5

лямбда працює як слід, як і в інших мовах.

Провід Proc.newдивовижний і заплутаний.

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

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Ви можете стверджувати, що Proc.newвставляє код у метод укладання, як і блок. Але Proc.newстворює об'єкт, тоді як блок є частиною об'єкта.

І є ще одна відмінність між лямбда і тим Proc.new, що це обробка (неправильних) аргументів. лямбда скаржиться на це, при цьому Proc.newігнорує зайві аргументи або вважає відсутність аргументів нульовими.

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"

До речі, procу Ruby 1.8 створюється лямбда, тоді як у Ruby 1.9+ поводиться так Proc.new, що насправді заплутано.


3

Щоб детальніше ознайомитись з відповіддю хлопця-акордеона

Зауважте, що Proc.newстворюється проклейка, передаючи блок. Я вважаю, що lambda {...}це аналізується як якийсь буквальний, а не виклик методу, який передає блок. returnЯкщо внутрішній блок, приєднаний до виклику методу, повернеться з методу, а не з блоку, і Proc.newвипадок є прикладом цього при програванні.

(Це 1,8. Я не знаю, як це означає 1.9.)


3

Я трохи запізнююсь на цьому, але є одна чудова, але маловідома річ про те, що Proc.newвзагалі не згадується в коментарях. Як за документацією :

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

Це сказало, Proc.newдавайте ланцюжок урожайних методів:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

Цікаво, що він робить те саме, що оголошувати &blockаргумент у def, але без цього потрібно робити у списку def arg.
jrochkind

2

Варто підкреслити, що returnв proc повертається із лексично огороджувального методу, тобто методу, де створено proc , а не методу, який називав proc. Це наслідок властивості закриття проектів. Отже, наступний код нічого не видає:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Хоча Proc виконується в foobar, він був створений в fooі тому returnвиходи foo, а не просто foobar. Як Чарльз Колдвелл писав вище, це відчуває GOTO. На мою думку, returnпрекрасно в блоці, який виконується в його лексичному контексті, але набагато менш інтуїтивно зрозумілий при використанні в програмі, який виконується в іншому контексті.


1

Різниця в поведінці з returnIMHO є найважливішою відмінністю між 2. Я також віддаю перевагу лямбда, тому що вона менш типізована, ніж Proc.new :-)


2
Для оновлення: тепер можна створити програми за допомогою proc {}. Я не впевнений, коли це набуло чинності, але це (трохи) простіше, ніж вводити Proc.new.
aceofbassgreg
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.