Блоки и урожаи в Ruby

Я пытаюсь понять блоки и yield и как они работают в Ruby.

Как yield б/У? Многие приложения направляющих я посмотрел на использование yield странным способом.

Кто-то может объяснить мне или показать мне, куда пойти для понимания их?

264
задан the Tin Man 30 September 2015 в 07:59
поделиться

4 ответа

Да, поначалу это немного озадачивает.

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

Когда метод ожидает блок, он вызывает его, вызывая функцию yield .

Это очень удобно, например, для перебора списка или для предоставления специального алгоритма.

Рассмотрим следующий пример:

Я собираюсь определить класс Person , инициализированный именем, и предоставить метод do_with_name , который при вызове будет просто передавать имя атрибута для полученного блока.

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

Это позволит нам вызвать этот метод и передать произвольный блок кода.

Например, чтобы напечатать имя, мы должны сделать:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

Будет напечатать:

Hey, his name is Oscar

Обратите внимание, блок получает в качестве параметра переменную с именем name (NB, вы можете вызвать эту переменную как хотите, но есть смысл называть это именем ). Когда код вызывает yield , он заполняет этот параметр значением @name .

yield( @name )

Мы могли бы предоставить другой блок для выполнения другого действия. Например, переверните имя:

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

Мы использовали точно такой же метод ( do_with_name ) - это просто другой блок.

Этот пример тривиален. Более интересные способы использования - это фильтрация всех элементов в массиве:

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

Или мы также можем предоставить собственный алгоритм сортировки, например, на основе размера строки:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

Я надеюсь, что это поможет вам лучше понять это.

Кстати, если блок является необязательным, вы должны называть его следующим образом:

yield(value) if block_given?

Если не является необязательным, просто вызовите его.

375
ответ дан 23 November 2019 в 02:29
поделиться

Я хотел добавить, почему вы делаете вещи таким образом, к уже полученным ответам.

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

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Игнорируя всю цепочку потоков, идея такова

  1. Инициализировать ресурс, который нужно очистить
  2. использовать ресурс
  3. убедиться, что он очищен

Вот как вы делаете это в ruby

File.open("readfile.rb", "r") do |infile|
    while (line = infile.gets)
        puts "#{counter}: #{line}"
        counter = counter + 1
    end
end

Дико отличается. Разбираем этот

  1. сказать классу File, как инициализировать ресурс
  2. сказать классу File, что с ним делать
  3. посмеяться над парнями из java, которые все еще печатают ;-)

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

Теперь, не похоже, что вы не можете сделать что-то подобное в java, на самом деле, люди делают это уже десятилетиями. Это называется Стратегия. Разница в том, что без блоков, для чего-то простого, как в примере с файлом, стратегия становится излишеством из-за количества классов и методов, которые вам нужно написать. С блоками это настолько простой и элегантный способ сделать это, что нет никакого смысла НЕ структурировать код таким образом.

Это не единственный способ использования блоков, но другие (например, паттерн Builder, который вы можете увидеть в form_for api в rails) достаточно похожи, чтобы было очевидно, что происходит, как только вы поймете это. Когда вы видите блоки, обычно можно предположить, что вызов метода - это то, что вы хотите сделать, а блок описывает, как вы хотите это сделать.

13
ответ дан 23 November 2019 в 02:29
поделиться

В Ruby методы могут проверять, были ли они вызваны таким образом, что в дополнение к обычным аргументам был предоставлен блок. Обычно это делается с помощью метода block_given? , но вы также можете ссылаться на блок как на явный Proc, добавив амперсанд ( & ) перед именем последнего аргумента.

Если метод вызывается с блоком, тогда метод может передать управление блоку (вызвать блок) с некоторыми аргументами, если это необходимо.Рассмотрим этот пример метода, который демонстрирует:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

Или, используя специальный синтаксис аргумента блока:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)
24
ответ дан 23 November 2019 в 02:29
поделиться

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

Я должен добавить, что я считаю, что сообщение, на которое я ссылаюсь, относится к Ruby 1.8. Некоторые вещи изменились в Ruby 1.9, например, переменные блока являются локальными для блока. В 1.8 вы получите что-то вроде следующего:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

Тогда как 1.9 выдаст вам:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

У меня нет 1.9 на этой машине, поэтому в приведенном выше примере может быть ошибка.

22
ответ дан 23 November 2019 в 02:29
поделиться
Другие вопросы по тегам:

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