Блоки и процессы в Ruby

Я начал изучать Ruby, прочитал пару руководств и даже купил книгу ("Programming Ruby 1.9 - The Pragmatic Programmers' Guide") и наткнулся на кое-что новое, чего раньше не видел ни в одном из другие языки, которые я знаю (я работаю веб-разработчиком PHP).

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

Может ли кто-нибудь здесь дать какие-то объяснения такому новичку в Ruby, как я?


person Nilks    schedule 24.08.2010    source источник
comment
Ищите дополнительную информацию о замыканиях, это поможет объяснить, как работают блоки и проки и для чего они полезны.   -  person Doon    schedule 25.08.2010


Ответы (6)


Есть много вещей, которые хороши в блоках. Презентация лифта: блоки позволяют нам передавать действия так же, как мы обычно передаем данные.

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

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
int evenNumbers[50] = {0};
int copyIndex = 0;
for (int i = 0; i < 50; i++) {
    if (list[i] % 2 == 0) {
        evenNumbers[copyIndex++] = list[i];
    }
}

Вот как вы пишете это в Ruby:

list = 1..50
listCopy = list.select {|n| n.even?}

Вся рутинная работа перемещается из вашего кода в метод с осмысленным именем. Нам не нужно копировать массив, просматривать индексы и все такое — нам просто нужен отфильтрованный список. И это то, что select дает нам. Блок позволяет нам передать нашу пользовательскую логику в этот стандартный метод.

Но итераторы — не единственное место, где эта "дыра в средний шаблон" полезен. Например, если вы передадите блок File.open, он откроет файл, выполнит блок с файлом, а затем закроет файл для вас.

Еще одна вещь, которую дают нам блоки, — это действительно мощная форма обратных вызовов. Например, без блоков нам, возможно, придется сделать что-то вроде этого (исходя из того, как на самом деле работают диалоги в Objective-C Cocoa):

class Controller
  def delete_button_clicked(item)
    item.add_red_highlight
    context = {:item => item}
    dialog = Dialog.new("Are you sure you want to delete #{item}?")
    dialog.ok_callback = :delete_OK
    dialog.ok_receiver = self
    dialog.cancel_callback = :cancel_delete
    dialog.cancel_receiver = self
    dialog.context = context
    dialog.ask_for_confirmation
  end

  def delete_OK(sender)
    delete(sender.context[:item])
    sender.dismiss
  end

  def cancel_delete(sender)
    sender.context[:item].remove_red_highlight
    sender.dismiss
  end
end

Йоуза. С блоками мы могли бы сделать это вместо этого (на основе общего шаблона, используемого во многих библиотеках Ruby):

class Controller
  def delete_button_clicked(item)
    item.add_red_highlight
    Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response|
      response.ok { delete item }
      response.cancel { item.remove_red_highlight }
    end
  end
end

На самом деле это два уровня блоков — блок do...end и два блока в стиле {}. Но читается довольно естественно, не так ли? Это работает, потому что блок фиксирует контекст, в котором он создан, поэтому нам не нужно передавать self и item.

Что касается Procs, то они просто объектная оболочка для блоков. Им не очень.

person Chuck    schedule 24.08.2010

Важно рассматривать блоки не как использование методов для начала блока кода, вы фактически берете блок и используете его как параметр в функции.

Поэтому, когда вы используете метод each для перебора массива следующим образом:

superOverFlowArray.each { |flow| puts flow }

Вы отправляете блок { |flow| помещает поток } в каждый метод. Код сообщает ruby ​​отправить текущее значение массива в этот блок вместо |flow|.

Процедуры берут блок и превращают его в переменную. Procs — это экземпляры объектов, которые содержат блоки. Блоки передаются в качестве параметра процедуре и выполняются, когда вы вызываете метод call для этого экземпляра процедуры.

Итак, вот пример Proc:

def category_and_title(category)
     Proc.new { |title| "The title is: " + title + " in category: " + category }
end

myFirstTitle = category_and_title("Police Drama")
mySecondTitle = category_and_title("Comedy")

puts myFirstTitle.call("Law and Order")
puts mySecondTitle.call("Seinfeld")
puts myFirstTitle.call("CSI")

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

person krx    schedule 24.08.2010

Блоки используются во многих методах классов Ruby, и они используются там, где в PHP вы бы использовали обратный вызов.

[1,2,3,4,5].each {|i| print "#{i} "}

[1,2,3,4,5].each do |i|
  print "#{i} "
end

File.open('p014constructs.rb', 'r') do |f1|  
  while line = f1.gets  
    puts line
  end
end

PHP5 представил анонимные функции; вместо использования обратного вызова вы можете использовать анонимную функцию.

echo preg_replace_callback('~-([a-z])~', function ($match) {
  return strtoupper($match[1]);
}, 'hello-world');
person apaderno    schedule 24.08.2010
comment
Я понимаю блоки при использовании итераторов и т. д., но все остальное я не понимаю. (В основном проки, я думаю) - person Nilks; 25.08.2010
comment
Добавьте часть о том, что блоки являются замыканиями! (т. е. вы можете получить доступ к переменным вне области действия блока, что означает, что вам не нужно создавать кучу временных структур и т. д.) - person Ana Betts; 25.08.2010
comment
что означает, что блоки Ruby 1.8 не имеют локальных переменных. Я думаю, вы обнаружите, что вы не правы. - person horseyguy; 25.08.2010

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

    hello = Proc.new do |x, y|
      puts "i am a proc"
      sum = x+y
      puts "sum: #{sum}"
    end

теперь, чтобы использовать этот блок кода, просто вызовите метод «вызов» в приветствии. Обратите внимание, что приведенный выше пример получает аргументы x и y, которые использует блок кода. Поэтому не забудьте передать аргументы, когда вы вызываете объект Proc, как я сделал ниже:

    hello.call(2, 3)

получение следующих результатов:

    i am a proc
    sum: 5

Ура!! Это был простой способ создать объект Proc. Насколько это полезно? Что ж, объекты proc позволяют вам передавать фрагменты кода. Иллюстрация ниже объясняет это лучше, используя proc, созданный в приведенном выше примере. Давайте создадим случайный класс,

    class SomeClass
      def initialize(&block)
        @block = block
      end

      def output_value(x, y)
        @block.call(x, y)
      end
    end

теперь давайте создадим экземпляр SomeClass,

    some_class = SomeClass.new(&hello)

ЗАМЕТЬТЕ, что знак амперсанда "&" перед приветствием позволяет передать объект proc в качестве аргумента.

наконец, давайте выведем значение, передав 2 аргумента, как показано ниже:

    some_class.output_value(1, 3)

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

    i am a proc
    sum: 4

Видеть!! это так просто. Вы смогли передать кусок кода. Procs настолько полезны, что ruby ​​часто их использует. Надеюсь, это было супер полезно :)

person Paa Yaw    schedule 22.06.2016

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

person flq    schedule 24.08.2010
comment
Не совсем так, блоки в Ruby произошли от Smalltalk, и в обоих языках они являются объектами, а не функциями, хотя в обоих они выглядят и действуют точно так же, как анонимные функции. например. Даже синтаксис очень похож. Например, в smalltalk блок будет a := [:x|x+1] - person OscarRyz; 25.08.2010
comment
@OscarRyz: я здесь как бы придираюсь, но блоки в Ruby на самом деле являются функциями, а не являются объектами. Вот почему люди говорят, что это замыкания — замыкания — это своего рода функция. Нельзя, например, написать totally_an_object = {|n| n * 7} — это синтаксическая ошибка. Вам нужно вызвать proc с блоком, чтобы получить фактический объект, представляющий эту функцию. Блоки были настоящими объектами в Smalltalk, но версия Ruby немного отличается. - person Chuck; 25.08.2010

Блоки и процессы позволяют извлекать небольшие фрагменты кода без полной накладной работы и сложности методов.

Даже map сам по себе довольно мощен, и jQuery построен на той же концепции:

  ['spite','rest','purpose'].map {|s| s << 'ful' }

А можно и логику подкинуть если надо

  ['sprite','restful','luck'].map {|s| s << (s.end_with?('uck') ? 'i' : '') << 'ly' }

Существует умный оператор '&', который означает "преобразовать символ в proc и вызвать его", поэтому вы можете преобразовывать строки объектов:

  ['spite',12,:purpose].map(&:to_s)

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

  last = -2 # this variable is accessed and changed within the closure
  objs.sort.map do |o|
    if (last + 1) != o
       last = o
       nil # not one more than previous so return nil
    else
       o
    end
  end.compact  # compact removes nils

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

person ndp    schedule 25.08.2010