Ответ 1
Насколько я могу судить, в Ruby существуют три разных типа закрытия: методы, procs и lambdas.
Нет, есть два: методы не являются закрытием, только procs и lambdas. (Или, по крайней мере, может быть, большинство из них не являются.)
Существует два способа упаковки части исполняемого кода для повторного использования в Ruby: методы и блоки. Строго говоря, блоки не нужны, вы можете обойтись просто методами. Но блоки должны быть чрезвычайно легкими, концептуально, семантически и синтаксически. Это неверно для методов.
Поскольку они предназначены для легкого и простого в использовании, блоки ведут себя по-разному от методов в некоторых отношениях, например. как аргументы связаны с параметрами. Параметры блока привязаны больше, чем левая часть задания, чем аналогичные параметры метода.
Примеры:
Передача одного массива в несколько параметров:
def foo(a, b) end
foo([1, 2, 3]) # ArgumentError: wrong number of arguments (1 for 2)
a, b = [1, 2, 3]
# a == 1; b == 2
[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b ==2
Передача меньше аргументов, чем параметры:
def foo(a, b, c) end
foo(1, 2) # ArgumentError
a, b, c = 1, 2
# a == 1; b == 2; c == nil
[[1, 2]].each {|a, b, c| puts "a == #{a}; b == #{b}; c == #{c}" }
# a == 1; b == 2; c ==
Передача большего количества аргументов, чем параметры:
def foo(a, b) end
foo(1, 2, 3) # ArgumentError: wrong number of arguments (3 for 2)
a, b = 1, 2, 3
# a == 1; b == 2
[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b == 2
[Кстати: ни один из вышеперечисленных блоков не является закрытием.]
Это позволяет, например, протоколу Enumerable
, который всегда дает одному элементу блоку работать с Hash
es: вы просто делаете единственный элемент a Array
из [key, value]
и полагаетесь на неявный массив деструктурирования блока:
{one: 1, two: 2}.each {|k, v| puts "#{key} is assigned to #{value}" }
гораздо легче понять, чем то, что вам пришлось бы написать иначе:
{one: 1, two: 2}.each {|el| puts "#{el.first} is assigned to #{el.last}" }
Другая разница между блоками и методами заключается в том, что методы используют ключевое слово return
для возврата значения, тогда как в блоках используется ключевое слово next
.
Если вы согласны с тем, что имеет смысл использовать оба метода и блоки в языке, то это всего лишь небольшой шаг, чтобы также принять существование как procs, так и lambdas, потому что они ведут себя как блоки и методы соответственно:
- procs
return
из встроенного метода (точно так же, как блоки), и они связывают аргументы точно так же, как блоки do - lambdas
return
от себя (подобно методам), и они связывают аргументы точно так же, как и методы.
IOW: дихотомия proc/lambda просто отражает дихотомию блока/метода.
Обратите внимание, что на самом деле существует достаточно много дел. Например, что означает self
? Означает ли это
- любой
self
находился в точке, в которой был записан блок - независимо
self
находится в точке, в которой выполняется блок - сам блок
А как насчет return
? Означает ли это
- Возврат из метода, в котором блок написан в
- Возврат из метода, в котором выполняется блок
- вернуться из самого блока?
Это уже дает вам девять возможностей, даже без учета специфичных для Ruby особенностей привязки параметров.
Теперь, по причинам инкапсуляции, № 2 выше - это действительно плохие идеи, поэтому мы немного уменьшаем наши варианты.
Как всегда, это вопрос вкуса дизайнера языка. В Ruby существуют и другие подобные сокращения: зачем вам нужны как переменные экземпляра, так и локальные переменные? Если лексические области были объектами, то локальные переменные были бы просто переменными экземпляра лексической области, и вам не понадобятся локальные переменные. И зачем вам нужны переменные и методы экземпляра? Одного из них достаточно: пара методов getter/setter может заменить переменную экземпляра (см. "Newspeak" для примера такого языка), а первоклассные процедуры, назначенные переменным экземпляра, могут заменить методы (см. Self, Python, JavaScript). Зачем вам нужны как классы, так и модули? Если вы разрешите смешивать классы, вы можете избавиться от модулей и использовать классы как классы, так и mixins. И зачем вам вообще нужны микшины? Если все это вызов метода, классы все равно становятся миксинами (опять же, например, см. "Новости" ). И, конечно, если вы разрешаете наследование непосредственно между объектами, вам вообще не нужны классы (см. Self, Io, Ioke, Seph, JavaScript)