Блоки и выходы в Ruby

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

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

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

9 ответов


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

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

когда метод ожидает блок, он вызывает его, вызывая .

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

рассмотрим пример:

я собираюсь определить 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 (N. B. Вы можете называть эту переменную как угодно, но имеет смысл назовите это 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?

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


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

Я должен добавить, что я считаю, что сообщение, на которое я ссылаюсь, относится к 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 на этой машине, поэтому выше может быть ошибка в нем.


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

если метод вызывается с помощью блока, то метод может 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 =)

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

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

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

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

дико разные. Ломая этот

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

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

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

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


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

#!/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 блоки делали. Это в основном способ для функции предоставить доступ к внутренним структурам данных, будь то для итерации или для конфигурации функции.

поэтому, когда в rails вы пишете следующее:

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

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

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


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

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

Базовый Синтаксис

блок - это фрагмент кода, заключенный в { } или 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, он ожидает получить блок во время вызова. Если блок не предоставляется, исключение будет выдано после достижения оператора 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


Я иногда использую "выход" такой:

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

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

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


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

Def Up(anarg)
  yield(anarg)
end

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

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

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