Dec 01

В чем разница между Block, Proc и Lambda в Ruby?

# Block 
[1,2,3].each { |x| puts x*2 }   # block все то, что между фигурными скобками
[1,2,3].each do |x|
  puts x*2                    # block между do и end
end
# Proc              
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              #  знак '&' говорит руби обернуть proc в блок
proc = Proc.new { puts "Hello World" }
proc.call                     # Тело экземпляра Proc выполнится, когда будем вызывать proc

# Lambda             
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)
lam = lambda { puts "Hello World" }
lam.call

Выглядят похоже, но есть тонкие различия, о которых я расскажу ниже.

Разница между Block и Proc

  1. Procs это объекты, а Blocks - нет.
  2. proc - это объект класса Proc.
p = Proc.new { puts "Hello World" }

Это позволяет нам вызывать методы на нем и присваивать их переменным. Procs также могут возвращать сами себя.

p.call  # prints 'Hello World'
p.class # возвращает 'Proc'
a = p   # a сейчас равно p, a Proc объекту
p       # возвращает proc объект '#<Proc:0x007f96b1a60eb0@(irb):46>'

Блоки всего лишь часть "синтаксиса" вызова метода. Он ничего не значит сам по себе.

{ puts "Hello World"}       # syntax error  
a = { puts "Hello World"}   # syntax error
[1,2,3].each {|x| puts x*2} 

2. Только один блок  может появиться в списке аргументов метода

В отличие от Proc, объекты которого можно передавать в качестве аргументов не один раз

def multiple_procs(proc1, proc2)
  proc1.call
  proc2.call
end
a = Proc.new { puts "First proc" }
b = Proc.new { puts "Second proc" }
multiple_procs(a,b)

Различие между Procs и Lambdas

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

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }
proc.class # returns 'Proc'
lam.class  # returns 'Proc'

Однако lambdas имеют отличие от procs. Можно это увидеть, когда возвращаем объект.

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

Запись lambda  - это напоминание, хотя procs и lambdas очень похожи и даже являются объектами одного класса Proc, они также имеют различия. Ниже ключевые моменты:

  1. Lambdas важно количество передаваемых аргументов, в отличие от procs
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.Lambdas и procs исполняют ‘return’ по-разному

‘return’ внутри lambda не помешает выполнить остальной код в методе
def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end
lambda_test                 # вызвав lambda_test выведет 'Hello World'
    ‘return’ внутри proc выходит из метода, где был вызван
    def proc_test
     proc = Proc.new { return }
     proc.call
     puts "Hello world"
    end
    proc_test # вызвав proc_test ничего не выведет

Что же такое замыкания?

Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своём контексте.

Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.

По аналогии с чемоданом, это группа кода, который при открытии (т.е. вызове), содержит все, что было в нем, когда вы упаковали его (т.е. создали его).

# Пример объектов Proc
def counter
  n = 0
  return Proc.new { n+= 1 }
end
a = counter
a.call            # returns 1
a.call            # returns 2
b = counter
b.call            # returns 1
a.call            # returns 3

В заключении 1: Лямбда-Исчисление и анонимные функции

Лямбда получила свое название от типа исчисления, введенного в 1930-е годы, чтобы помочь изучить основы математики. Лямбда-исчисление помогает сделать вычислимые функции легче изучаемыми за счет упрощения его семантики. Наиболее важными из этих упрощений является то, что обращается к функции "анонимно", а это означает, что никакие явные имена не приводятся. 

sqsum(x,y) = x*x + y*y  #<-- normal function
(x,y) -> x*x + y*y #<-- anonymous function

Вообще говоря, в программировании термин лямбда относится к анонимным функциям. Эти анонимные функции очень явны в некоторых языках (например JavaScript) и подразумевается в других (то есть Ruby).

В заключении 2: откуда взялось наименование Proc

Proc сокращение от процедуры, которая представляет собой набор инструкций, упакованных в качестве единицы для выполнения конкретной задачи. На разных языках они могут быть названы функции, процедуры, методы. Они обычно создаются, чтобы быть вызванными несколько или с нескольких мест в программе. 

Кратко различия

  1. Procs - объекты, Blocks нет
  2. Блок - это обособленный набор инструкций, который может обращаться к переменным контекста, из которого он создан.
  3. Только один блок может быть в качестве аргумента
  4. Proc - это объект, содержащий блок. Данный объект можно копировать, передавать в различные функции и выполнять.
  5. Lambdas проверяет количество  аргументов, а procs- нет
  6. Lambdas и procs выполняют ‘return’ по-разному
  7.  Lambda-функции больше похожи на обычный метод и поэтому накладывают дополнительные ограничения на входные параметры:
    1. если lambda функция объявлена с двумя параметрами - то при вызове на вход должно быть передано именно 2 параметра, в противном случае будет сгенерировано иключение ArgumentError,
    2. в случае Proc-а лишние параметры будут отброшены, а недостающие заполнены значением nil
    3. lambda-функции похожи на обычные еще и тем, что вызов конструкции return приводит к выходу из lambda-функции, а не из родительской
Tags:
Share:

Comments

comments powered by HyperComments
Comment