Кодовый блок Ruby такой же, как лямбда-выражение C #?

Если кому-то интересно, я взломал версию, которая поддерживает остальную часть строки (хотя она может иметь ошибки, не слишком ее тестировала).

def text2int (textnum, numwords={}):
    if not numwords:
        units = [
        "zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
        "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
        "sixteen", "seventeen", "eighteen", "nineteen",
        ]

        tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]

        scales = ["hundred", "thousand", "million", "billion", "trillion"]

        numwords["and"] = (1, 0)
        for idx, word in enumerate(units):  numwords[word] = (1, idx)
        for idx, word in enumerate(tens):       numwords[word] = (1, idx * 10)
        for idx, word in enumerate(scales): numwords[word] = (10 ** (idx * 3 or 2), 0)

    ordinal_words = {'first':1, 'second':2, 'third':3, 'fifth':5, 'eighth':8, 'ninth':9, 'twelfth':12}
    ordinal_endings = [('ieth', 'y'), ('th', '')]

    textnum = textnum.replace('-', ' ')

    current = result = 0
    curstring = ""
    onnumber = False
    for word in textnum.split():
        if word in ordinal_words:
            scale, increment = (1, ordinal_words[word])
            current = current * scale + increment
            if scale > 100:
                result += current
                current = 0
            onnumber = True
        else:
            for ending, replacement in ordinal_endings:
                if word.endswith(ending):
                    word = "%s%s" % (word[:-len(ending)], replacement)

            if word not in numwords:
                if onnumber:
                    curstring += repr(result + current) + " "
                curstring += word + " "
                result = current = 0
                onnumber = False
            else:
                scale, increment = numwords[word]

                current = current * scale + increment
                if scale > 100:
                    result += current
                    current = 0
                onnumber = True

    if onnumber:
        curstring += repr(result + current)

    return curstring

Пример:

 >>> text2int("I want fifty five hot dogs for two hundred dollars.")
 I want 55 hot dogs for 200 dollars.

Могут возникнуть проблемы, если у вас есть, скажем, «200 долларов». Но это было очень грубо.

29
задан Joel Spolsky 2 December 2010 в 04:38
поделиться

3 ответа

Руби на самом деле имеет 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

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

def foo(&block)
  return block
end

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

Чтобы углубиться в это, есть действительно изящный трюк, который добавляет рельсы в Symbol (и был объединен с ядром ruby ​​в 1.9). В основном это & ​​amp; принуждение совершает свою магию, вызывая 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 & amp;: bar Могу ли я использовать этот синтаксис с блоком кода, который принимает более одного аргумента? Скажем, у меня есть hash = {0 => "hello", 1 => "world"}, и я хочу выбрать элементы с ключом в виде 0. Может быть, не хороший пример. - Брайан Шен

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

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

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

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.

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

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

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

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

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

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

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

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

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

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

45
ответ дан Matt Briggs 2 December 2010 в 04:38
поделиться

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

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

8
ответ дан Joel Spolsky 2 December 2010 в 04:38
поделиться

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

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

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

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

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

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

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

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

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

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

->(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.

procs vs. lambdas

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

В частности, не все Proc являются лямбдами. Вы можете проверить, является ли Proc лямбда-выражением, вызвав метод Proc#lambda?. (Обычное соглашение заключается в том, чтобы называть лямбда-выражения Proc s лямбда-выражениями, а не лямбда-выражения Proc просто «процессами».) до Kernel#proc. Тем не менее, обратите внимание, что до Ruby 1.9 Kernel#proc создает лямбду , а не proc.

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

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

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

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

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

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

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

Блоки : легковесные проки

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

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

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

to_proc и &

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

В списке параметров это означает « заключить неявный блок в процесс и связать его с этим именем». В списке аргументов это означает « развернуть этот 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

An 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 (! ). И, конечно, вся идея восходит к «лямбда-калькуле» Алонзо Черча (1935 и даже ранее).

33
ответ дан Jörg W Mittag 2 December 2010 в 04:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: