Является ли код Ruby таким же, как выражение С# лямбда?

Являются ли эти два по существу одинаковыми? Они очень похожи на меня.

Было ли выражение лямбда заимствовать его идею у Ruby?

Ответ 1

Ruby на самом деле имеет 4 конструкции, которые все очень похожи

Блок

Идея блоков - своего рода способ реализовать действительно легкие стратегии стратегии. Блок будет определять сопрограмму функции, функция которой может делегировать управление с ключевым словом yield. Мы используем блоки для всего, что есть в рубине, включая почти все петлевые конструкции или везде, где вы бы использовали using в С#. Все, что находится за пределами блока, находится в области действия блока, однако инверсия неверна, за исключением того, что возврат внутри блока возвращает внешнюю область. Они выглядят так:

def foo
  yield 'called foo'
end

#usage
foo {|msg| puts msg} #idiomatic for one liners

foo do |msg| #idiomatic for multiline blocks
  puts msg
end

Proc

Прок в основном принимает блок и передает его в качестве параметра. Одно чрезвычайно интересное использование этого заключается в том, что вы можете передать proc в качестве замены блока в другом методе. Ruby имеет специальный символ для принудительного принуждения, который является & и специальным правилом, что если последний параметр в сигнатуре метода начинается с &, это будет proc-представление блока для вызова метода. Наконец, существует встроенный метод под названием block_given?, который вернет true, если текущий метод имеет определенный блок. Похоже на это

def foo(&block)
  return block
end

b = foo {puts 'hi'}
b.call # hi

Чтобы немного углубиться в это, есть действительно опрятный трюк, который добавляет рельсы в Symbol (и слился с основным рубином в 1.9). В принципе, это и принуждение делает свою магию, называя to_proc тем, что рядом. Таким образом, рельсы-ребята добавили символ # to_proc, который будет называть себя тем, что передается. Это позволяет вам написать действительно очень сложный код для любой функции стиля агрегации, которая просто вызывает метод для каждого объекта в списке

class Foo
  def bar
    'this is from bar'
  end
end

list = [Foo.new, Foo.new, Foo.new]

list.map {|foo| foo.bar} # returns ['this is from bar', 'this is from bar', 'this is from bar']
list.map &:bar # returns _exactly_ the same thing

Более продвинутый материал, но imo, который действительно иллюстрирует волшебство, которое вы можете делать с procs

Лямбда

Цель лямбда почти такая же в рубине, как и в С#, способ создания встроенной функции, чтобы либо обойти, либо использовать внутренне. Подобно блокам и procs, lambdas - это закрытие, но в отличие от первых двух, оно обеспечивает arity, а возврат из лямбда выходит из лямбда, а не в область содержимого. Вы создаете его, передавая блок методу лямбда или в → в ruby ​​1.9

l = lambda {|msg| puts msg} #ruby 1.8
l = -> {|msg| puts msg} #ruby 1.9

l.call('foo') # => foo

Методы

Только серьезные гнилые рубины действительно понимают это:) Метод - это способ превратить существующую функцию в то, что вы можете поместить в переменную. Вы получаете метод, вызывая функцию method и передавая символ как имя метода. Вы можете повторно привязать метод, или вы можете принудить его к процессу, если хотите показать. Способ перезаписи предыдущего метода был бы

l = lambda &method(:puts)
l.call('foo')

Что здесь происходит, так это то, что вы создаете метод puts, принуждая его к proc, передавая это как замену блоку для лямбда-метода, который, в свою очередь, возвращает вам лямбда


Не стесняйтесь спрашивать о чем-то непонятном (писать это очень поздно в неделю без irb, надеюсь, это не чистая тарабарщина)

РЕДАКТОР: Чтобы задать вопросы в комментариях

list.map &: bar Могу ли я использовать этот синтаксис с блоком кода, который занимает больше, чем один аргумент? Скажем, у меня hash = {0 = > "привет", 1 = > "мир" }, и я хочу выберите элементы, которые имеют 0 в качестве ключ. Может быть, не очень хороший пример. - Брайан Шен

Пойдем сюда глубоко, но чтобы понять, как это работает, нужно понять, как работает метод ruby.

В принципе, у Ruby нет понятия вызова метода, происходит то, что объекты передают сообщения друг другу. Синтаксис obj.method arg, который вы используете, - это просто сахара вокруг более явной формы, которая obj.send :method, arg, и функционально эквивалентна первому синтаксису. Это фундаментальная концепция в языке, и поэтому такие вещи, как method_missing и respond_to?, имеют смысл, в первом случае вы просто обрабатываете нераспознанное сообщение, второе - вы проверяете, прослушивает ли оно это сообщение.

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

def foo(bar, *baz)

В вызове метода, если он является последним параметром, splat сделает этот параметр glob вверх по всем дополнительным параметрам, переданным функции (вроде как params в С#)

obj.foo(bar, *[biz, baz])

Когда в вызове метода (или что-либо еще, что принимает списки аргументов), он превратит массив в список открытых аргументов. Ниже приведенный ниже фрагмент эквивалентен фрагменту выше.

obj.foo(bar, biz, baz)

Теперь, имея в виду send и *, Symbol#to_proc в основном реализуется как

class Symbol
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end

Итак, &:sym собирается создать новый proc, который вызывает .send :sym по первому аргументу, переданному ему. Если любые дополнительные аргументы передаются, они всплывают в массив с именем args, а затем помещаются в вызов метода send.

Я замечаю это и используется в трех места: def foo (& блок), list.map &: bar и l = lambda & method (: puts). Имеют ли они одно и то же значение? - Брайан Шен

Да, да. А и будет называть to_proc тем, что когда-либо было рядом. В случае определения метода он имеет особое значение, когда по последнему параметру, где вы вытягиваете совместную процедуру, определенную как блок, и превращая это в proc. Определения методов на самом деле являются одной из самых сложных частей языка, существует огромное количество трюков и специальных значений, которые могут быть в параметрах, и размещения параметров.

b = {0 = > "df", 1 = > "kl" } p b.select {| ключ, значение | key.zero? } Я попытался преобразуйте это в p b.select &: zero?, но это провалилось. Я предполагаю, что количество параметров для кода блок равен двум, но &: ноль? может только возьмите один параметр. Есть ли способ, которым я могу сделай это? - Брайан Шен

Это должно быть рассмотрено ранее, к сожалению, вы не можете сделать это с помощью этого трюка.

"Метод - это способ превратить существующий функции в нечто, что вы можете переменная". почему l = метод (: puts) не достаточно? Что лямбда & означает в этом контексте? - Брайан Шен

Этот пример был исключительно надуман, я просто хотел показать эквивалентный код для примера перед ним, где я передавал proc методу lambda. Я займу некоторое время позже и перезапишу этот бит, но вы правы, method(:puts) вполне достаточно. Я пытался показать, что вы можете использовать &method(:puts) где угодно, чтобы взять блок. Лучшим примером будет этот

['hello', 'world'].each &method(:puts) # => hello\nworld

l = → {| msg | puts msg} #ruby 1.9: это не работает для меня. После того как я проверял ответ Йорг, я думаю, это должен быть l = → (msg) {puts msg}. Или возможно, я использую неверную версию Руби? Шахта рубиновая 1.9.1p738 - Брайан Шен

Как я сказал в сообщении, у меня не было доступного irb, когда я писал ответ, и вы правы, я сделал это (потратил большую часть своего времени в 1.8.7, так что я не используется для нового синтаксиса)

Между бит stabby и parens нет пробела. Попробуйте l = ->(msg) {puts msg}. Было действительно много сопротивления этому синтаксису, поскольку он настолько отличается от всего остального на языке.

Ответ 2

С# против Руби

Эти два по сути одно и то же? Они выглядят очень похожими на меня.

Они очень разные.

Прежде всего, лямбды в С# делают две совершенно разные вещи, только одна из которых имеет эквивалент в Ruby. (И этот эквивалент, сюрприз, лямбды, а не блоки.)

В С# литеральные лямбда-выражения перегружены. (Интересно, что, насколько я знаю, они являются единственными перегруженными литералами.) И они перегружены по своему типу результата. (Опять же, они являются единственной вещью в С#, которая может быть перегружена на его тип результата, методы могут быть перегружены только на их типы аргумента.)

Литералы лямбда-выражения С# могут быть либо анонимным фрагментом исполняемого кода, либо абстрактным представлением анонимного фрагмента исполняемого кода, в зависимости от того, является ли их тип результата Func/Action или Expression.

Ruby не имеет эквивалента для последней функциональности (ну, есть специфичные для интерпретатора непереносимые нестандартизированные расширения). И эквивалент для прежней функциональности - лямбда, а не блок.

Синтаксис Ruby для лямбды очень похож на С#:

->(x, y) { x + y }           # Ruby
(x, y) => { return x + y; } // C#

В С# вы можете отбросить return, точку с запятой и фигурные скобки, если у вас есть только одно выражение в качестве тела:

->(x, y) { x + y }  # Ruby
(x, y) => x + y    // C#

Вы можете оставить скобки, если у вас есть только один параметр:

-> x { x }  # Ruby
x => x     // C#

В Ruby вы можете оставить список параметров, если он пуст:

-> { 42 }  # Ruby
() => 42  // C#

Альтернативой использованию буквального синтаксиса лямбды в Ruby является передача аргумента блока в метод Kernel#lambda:

->(x, y) { x + y }
lambda {|x, y| x + y } # same thing

Основное различие между этими двумя заключается в том, что вы не знаете, что делает lambda, поскольку она может быть переопределена, перезаписана, перенесена или иным образом изменена, тогда как поведение литералов не может быть изменено в Ruby.

В Ruby 1.8 вы также можете использовать Kernel#proc хотя вам, вероятно, следует избегать этого, поскольку в 1.9 этот метод работает по-другому.

Еще одно различие между Ruby и С# заключается в синтаксисе вызова лямбды:

l.()  # Ruby
l()  // C#

Т.е. в С# вы используете тот же синтаксис для вызова лямбды, который вы использовали бы для вызова чего-либо еще, тогда как в Ruby синтаксис для вызова метода отличается от синтаксиса для вызова любого другого типа вызываемого объекта.

Другое отличие состоит в том, что в С# () встроен в язык и доступен только для определенных встроенных типов, таких как методы, делегаты, Action и Func, тогда как в Ruby .() Является просто синтаксическим сахаром для .call() и может таким образом, можно заставить работать с любым объектом, просто реализовав метод call.

Procs vs. Lambdas

Итак, что же такое лямбды? Ну, они экземпляры класса Proc. За исключением небольшого осложнения: на самом деле есть два разных типа экземпляров класса Proc которые слегка различаются. (ИМХО, класс Proc должен быть разделен на два класса для двух разных типов объектов.)

В частности, не все Proc являются лямбдами. Вы можете проверить, является ли Proc лямбда, вызвав Proc#lambda? метод. (Обычное соглашение заключается в том, чтобы называть лямбда- Proc "лямбдами", а не лямбда- Proc просто "процами".)

Не лямбда-проки создаются путем передачи блока в Proc.new или в Kernel#proc. Однако обратите внимание, что до Ruby 1.9 Kernel#proc создает лямбду, а не proc.

Какая разница? По сути, лямбды ведут себя больше как методы, а проки - как блоки.

Если вы следили за некоторыми обсуждениями в списках рассылки Project Lambda для Java 8, вы могли столкнуться с проблемой того, что не совсем понятно, как нелокальный поток управления должен вести себя с лямбдами. В частности, есть три возможных разумных поведения для return (хорошо, три возможных, но только два действительно разумны) в лямбде:

  • возврат из лямбды
  • возврат из метода, из которого была вызвана лямбда
  • возврат из метода лямбда была создана в

Последний вариант немного ненадежен, поскольку в целом метод уже будет возвращен, но два других имеют смысл, и ни один из них не является более правильным или более очевидным, чем другой. Текущее состояние Project Lambda для Java 8 состоит в том, что они используют два разных ключевых слова (return и yield). Ruby использует два разных типа Proc:

  • Procs возвращаются из вызывающего метода (как блоки)
  • лямбды возвращаются из лямбды (как методы)

Они также отличаются тем, как они обрабатывают привязку аргументов. Опять же, лямбды ведут себя больше как методы, а проки ведут себя больше как блоки:

  • вы можете передать больше аргументов в процесс, чем есть параметры, в этом случае лишние аргументы будут игнорироваться
  • вы можете передать меньше аргументов в процесс, чем есть параметры, и в этом случае избыточные параметры будут связаны с nil
  • если вы передаете один аргумент, который является Array (или отвечает на to_ary), и процедура имеет несколько параметров, массив будет распакован, а элементы привязаны к параметрам (точно так же, как и в случае деструктурирующего присваивания)

Блоки: легкие проки

Блок по сути легкий процесс. Каждый метод в Ruby имеет ровно один параметр блока, который фактически не отображается в списке параметров (подробнее об этом позже), то есть неявно. Это означает, что при каждом вызове метода вы можете передавать аргумент блока независимо от того, ожидает ли метод этого или нет.

Поскольку блок не отображается в списке параметров, нет имени, которое вы можете использовать для ссылки на него. Итак, как вы используете это? Ну, единственные две вещи, которые вы можете сделать (не совсем, но об этом позже), это неявно вызвать его через ключевое слово yield и проверить, был ли блок передан через block_given? , (Поскольку имени нет, вы не можете использовать методы call или nil?. На что бы вы их вызвали?)

Большинство реализаций Ruby реализуют блоки очень легким способом. В частности, они на самом деле не реализуют их как объекты. Однако, поскольку у них нет имени, вы не можете ссылаться на них, поэтому на самом деле невозможно определить, являются ли они объектами или нет. Вы можете просто думать о них как о процах, что облегчает эту задачу, поскольку есть одна менее важная концепция, о которой следует помнить. Просто учтите тот факт, что они на самом деле не реализованы как блоки, как оптимизация компилятора.

to_proc и &

На самом деле есть способ сослаться на блок: оператор & sigil/modifier/unary prefix. Он может появляться только в списках параметров и списках аргументов.

В списке параметров это означает "заключить неявный блок в процесс и связать его с этим именем". В списке аргументов это означает "развернуть этот Proc в блок".

def foo(&bar)
end

Внутри метода bar теперь привязан к объекту proc, который представляет блок. Это означает, например, что вы можете сохранить его в переменной экземпляра для последующего использования.

baz(&quux)

В этом случае baz - это метод, который принимает нулевые аргументы. Но, конечно, он принимает неявный аргумент блока, который принимают все методы Ruby. Мы quux содержимое переменной quux, но quux его в блок.

Эта "развертка" на самом деле работает не только для Proc. & to_proc вызывает to_proc для объекта, чтобы преобразовать его в процедуру. Таким образом, любой объект может быть преобразован в блок.

Я считаю, что наиболее широко используемым примером является Symbol#to_proc, который впервые появился где-то в конце 90-х годов. Он стал популярным, когда его добавили в ActiveSupport, откуда он распространился на Facets и другие библиотеки расширений. Наконец, он был добавлен в базовую библиотеку Ruby 1.9 и перенесен в 1.8.7. Это довольно просто:

class Symbol
  def to_proc
    ->(recv, *args) { recv.send self, *args }
  end
end

%w[Hello StackOverflow].map(&:length) # => [5, 13]

Или, если вы интерпретируете классы как функции для создания объектов, вы можете сделать что-то вроде этого:

class Class
  def to_proc
    -> *args { new *args }
  end
end

[1, 2, 3].map(&Array) # => [[nil], [nil, nil], [nil, nil, nil]]

Method и UnboundMethod с

Другим классом, представляющим часть исполняемого кода, является класс Method. Объекты Method - это расширенные прокси для методов. Вы можете создать объект Method, вызвав Object#method для любого объекта и передав имя метода, который вы хотите изменить:

m = 'Hello'.method(:length)
m.() #=> 5

или используя оператор ссылки на метод .: ::

m = 'Hello'.:length
m.() #=> 5

Method отвечает на to_proc, поэтому вы можете передавать их везде, где можете передать блок:

[1, 2, 3].each(&method(:puts))
# 1
# 2
# 3

UnboundMethod - это прокси для метода, который еще не был связан с получателем, т.е. метод, для которого self еще не определено. Вы не можете вызвать UnboundMethod, но вы можете bind его к объекту (который должен быть экземпляром модуля, из которого вы получили метод), который преобразует его в Method.

Объекты UnboundMethod создаются путем вызова одного из методов из семейства Module#instance_method, передавая имя метода в качестве аргумента.

u = String.instance_method(:length)

u.()
# NoMethodError: undefined method 'call' for #<UnboundMethod: String#length>

u.bind(42)
# TypeError: bind argument must be an instance of String

u.bind('Hello').() # => 5

Обобщенные вызываемые объекты

Как я уже намекал выше: в Proc и Method s особого нет. Любой объект, который отвечает на call может быть вызван, и любой объект, который отвечает на to_proc может быть преобразован в Proc и, таким образом, развернут в блок и передан методу, который ожидает блок.

история

Лямбда-выражение позаимствовало эту идею у Руби?

Возможно нет. Большинство современных языков программирования имеют некоторую форму анонимного буквального блока кода: Lisp (1958), Scheme, Smalltalk (1974), Perl, Python, ECMAScript, Ruby, Scala, Haskell, C++, D, Objective-C, даже PHP (!). И, конечно, вся идея восходит к церкви Алонзо λ -calculus (1935 и даже ранее).

Ответ 3

Не совсем. Но они очень похожи. Наиболее очевидное отличие состоит в том, что в С# лямбда-выражение может идти куда угодно, где у вас может быть значение, которое оказывается функцией; в Ruby у вас есть только один блок кода на вызов метода.

Они оба позаимствовали эту идею у Lisp (языка программирования, датируемого концом 1950-х годов), который, в свою очередь, заимствовал лямбда-концепцию из церковного лямбда-исчисления, изобретенного в 1930-х годах.