Блоки и выходы в рубине - PullRequest
258 голосов
/ 18 июня 2010

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

Как используется yield?Многие приложения Rails, на которые я смотрел, странным образом используют yield.

Может кто-нибудь объяснить мне или показать, куда мне обратиться, чтобы понять их?

Ответы [ 10 ]

366 голосов
/ 18 июня 2010

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

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

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

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

Возьмем следующий пример:

Я собираюсь определить класс Person, инициализированный именем, и предоставить метод do_with_name, который при вызове будет просто передавать 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. Вы можете вызывать эту переменную как угодно, но этосмысл называть это name).Когда код вызывает 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?

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

22 голосов
/ 18 июня 2010

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

Я должен добавить, что я считаю, что пост, на который я ссылаюсь, относится к 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 голосов
/ 18 июня 2010

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

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

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 =)
13 голосов
/ 18 июня 2010

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

Не знаю, с какого языка вы пришли, но если предположить, что это статический язык, то такого рода вещи будутвыглядит знакомо.Вот как вы читаете файл в 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. , сообщите классу файла, как инициализировать ресурс.
  2. скажите классу файла, что с ним делать.все еще печатая; -)

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

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

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

12 голосов
/ 14 мая 2014

Я нашел эта статья очень полезной. В частности, следующий пример:

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

, который должен дать следующий вывод:

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

Таким образом, каждый раз, когда вызывается yield, ruby ​​запускает код в блоке do или внутри {}. Если параметр предоставлен для yield, то он будет предоставлен как параметр для блока do.

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

Так, когда в рельсах вы пишете следующее:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

Будет запущена функция respond_to, которая выдает блок do с параметром (внутренним) format. Затем вы вызываете функцию .html для этой внутренней переменной, которая, в свою очередь, дает блок кода для запуска команды render. Обратите внимание, что .html выдаст только если это запрошенный формат файла. (техническая информация: эти функции на самом деле используют block.call, а не yield, как вы можете видеть из источника , но функциональность по сути та же, см. этот вопрос для обсуждения.) обеспечивает способ для функции выполнить некоторую инициализацию, затем принять ввод из кода вызова и затем продолжить обработку, если требуется.

Или, другими словами, это похоже на функцию, принимающую анонимную функцию в качестве аргумента и вызывающую ее в javascript.

7 голосов
/ 01 мая 2016

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

Блоки широко используются в гемах Ruby (включая Rails) и в хорошо написанном коде Ruby.Они не являются объектами, поэтому не могут быть присвоены переменным.

Основной синтаксис

Блок - это фрагмент кода, заключенный в {} или do..end.По соглашению синтаксис фигурных скобок должен использоваться для однострочных блоков, а синтаксис do..end должен использоваться для многострочных блоков.

{ # This is a single line block }

do
  # This is a multi-line block
end 

Любой метод может получить блок в качестве неявного аргумента.Блок выполняется оператором yield в методе.Базовый синтаксис:

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

Когда достигается оператор yield, метод meditate возвращает управление блоку, код внутри блока выполняется и управление возвращается методу, который сразу же возобновляет выполнениезаявление о доходности.

Когда метод содержит инструкцию yield, он ожидает получить блок во время вызова.Если блок не указан, то исключение будет выдано после достижения оператора yield.Мы можем сделать блок необязательным и избежать возникновения исключения:

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

Невозможно передать несколько блоков в метод.Каждый метод может получить только один блок.

Подробнее на: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html

5 голосов
/ 04 февраля 2015

Я иногда использую "yield" так:

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
4 голосов
/ 02 декабря 2013

Проще говоря, выходы позволяют создаваемому методу принимать и вызывать блоки. Ключевое слово yield - это место, где будет выполняться «материал» в блоке.

1 голос
/ 29 октября 2018

Есть два замечания, которые я хочу сделать о доходности здесь.Во-первых, хотя многие ответы здесь говорят о различных способах передачи блока методу, который использует yield, давайте также поговорим о потоке управления.Это особенно актуально, так как вы можете выдавать НЕСКОЛЬКО раз на блок.Давайте рассмотрим пример:

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block

Когда каждый метод вызывается, он выполняется построчно.Теперь, когда мы доберемся до блока 3.times, этот блок будет вызываться 3 раза.Каждый раз, когда это вызывает доходность.Этот выход связан с блоком, связанным с методом, который вызвал каждый метод.Важно отметить, что каждый раз, когда yield вызывается, он возвращает управление обратно в блок каждого метода в клиентском коде.По завершении выполнения блока он возвращается обратно в блок 3.times.И это происходит 3 раза.Таким образом, этот блок в клиентском коде вызывается 3 раза, поскольку yield явно вызывается 3 раза.

Мое второе замечание касается enum_for и yield.enum_for создает экземпляр класса Enumerator, и этот объект Enumerator также отвечает на yield.

class Fruit
  def initialize
    @kinds = %w(orange apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "apple" 

Так что обратите внимание, что каждый раз, когда мы вызываем виды с помощью внешнего итератора, он будет вызывать yield только один раз.В следующий раз, когда мы его назовем, он вызовет следующий выход и так далее.

Есть интересный трюк с enum_for.В онлайн документации указано следующее:

enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

Если вы не укажете символ в качестве аргумента для enum_for, ruby ​​подключит перечислитель к каждому методу получателя.Некоторые классы не имеют метода each, как класс String.

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

Таким образом, в случае некоторых объектов, вызываемых с помощью enum_for, вы должны четко указать, каким будет ваш метод перечисления.

0 голосов
/ 21 сентября 2016

Выход может использоваться как безымянный блок для возврата значения в методе. Рассмотрим следующий код:

Def Up(anarg)
  yield(anarg)
end

Вы можете создать метод «Вверх», которому присваивается один аргумент. Теперь вы можете назначить этот аргумент yield, который будет вызывать и выполнять связанный блок. Вы можете назначить блок после списка параметров.

Up("Here is a string"){|x| x.reverse!; puts(x)}

Когда метод Up вызывает yield с аргументом, он передается в переменную блока для обработки запроса.

...