Почему рубину нужно так много разных типов закрытия? - программирование

Почему рубину нужно так много разных типов закрытия?

Насколько я могу судить, в Ruby существуют три разных типа закрытия: методы, procs и лямбда. Я знаю, что между ними существуют различия, но разве мы не можем просто уйти от одного типа, который вмещает все возможные варианты использования?

Методы уже могут передаваться как procs и lambdas, вызывая self.method(method_name), и единственные существенные различия, которые я знаю между procs и lambdas, это то, что lambdas проверяет arity и procs делает сумасшедшие вещи, когда вы пытаетесь использовать return. Не могли бы мы просто объединить их всех в один и сделать с ним?

4b9b3361

Ответ 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)

Ответ 2

Некоторое довольно хорошее объяснение http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/, но я предполагаю, что вы хотите более глубоко философское объяснение...

Я верю в ответ на вопрос "но разве мы не можем просто уйти от одного типа, который вмещает все возможные варианты использования?" - это то, что вы можете уйти, используя только один.

Причина, по которой они существуют, заключается в том, что ruby ​​пытается сделать разработчика максимально продуктивным с использованием выражений как функциональных, так и объектно-ориентированных парадигм, что делает различные типы закрытия "синтаксического сахара".