Блоки и доходности в Ruby

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

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

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

Ответ 1

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

В 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 (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?

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

Ответ 2

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

Ответ 3

В 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 =)

Ответ 4

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

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

Игнорирование всей цепочки цепочек потоков. Идея заключается в том, что это

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

Вот как вы это делаете в рубине

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

Дико отличается. Разрушая это вниз

  • сообщите классу File, как инициализировать ресурс
  • сообщите классу файлов, что с ним делать
  • смеяться над java-парнями, которые все еще печатают; -)

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

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

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

Ответ 5

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

#!/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 not yield, как вы можете видеть из source, но функциональность по существу одинакова, см. этот вопрос для обсуждения.) Это дает возможность для функции выполнить некоторую инициализацию, затем взять ввод из вызывающего кода и затем продолжить обработку, если требуется.

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

Ответ 6

Я иногда использую "yield" следующим образом:

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Def Up(anarg)
  yield(anarg)
end

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

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

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