Кодовый блок Ruby такой же, как лямбда-выражение C #? - PullRequest
29 голосов
/ 02 декабря 2010

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

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

Ответы [ 3 ]

45 голосов
/ 02 декабря 2010

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

Блок

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

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

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

def foo(&block)
  return block
end

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

Если немного углубиться в это, есть действительно изящный трюк, добавляющий рельсы в Symbol (и слитый в основной рубин в 1.9).По сути, это & ​​принуждение делает свое волшебство, вызывая to_proc на то, что рядом с ним.Так что ребята из rails добавили Symbol # 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, который действительно иллюстрирует магию, которую вы можете делать с проками

Лямбды

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

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

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

Methods

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

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

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


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

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

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

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

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

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

def foo(bar, *baz)

При вызове метода, если это последний параметр, splat заставит этот параметр объединить все дополнительные параметры, передаваемые в функцию (вроде params в C #)

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 собирается создать новый процесс, который вызывает .send :sym для первого переданного ему аргумента. Если передаются какие-либо дополнительные аргументы, они объединяются в массив с именем args, а затем разделяются на вызов метода send.

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

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

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

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

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

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

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

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

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

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

33 голосов
/ 02 декабря 2010

C # против Ruby

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

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

Во-первых, лямбды в C # выполняют две очень разные вещи, только одна из которых имеет эквивалент в Ruby. (И этот эквивалент, сюрприз, лямбды, а не блоки.)

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

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

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

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

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

В 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 и C # заключается в синтаксисе , вызывающем лямбду:

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

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

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

проц против лямбды

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

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

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

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

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

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

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

  • возвращение процедур из вызывающего метода (точно так же, как блоки)
  • лямбда-возвращение из лямбды (так же, как методы)

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

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

Блоки: lightweight procs

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

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

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

to_proc и &

Там - это на самом деле способ ссылкик блоку: & сигил / модификатор / унарный префиксный оператор.Он может появляться только в списках параметров и списках аргументов.

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

def foo(&bar)
end

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

baz(&quux)

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

Эта «развертка» на самом деле работает не только для 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 s отвечают на 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 с нет. Любой объект , который отвечает на call, может быть вызван, а любой объект , который отвечает на to_proc, может быть преобразован в Proc и, таким образом, развернут в блок и передан в метод, который ожидает блок.

История

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

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

8 голосов
/ 02 декабря 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...