В Ruby методы могут получить блок кода для выполнения произвольных сегментов кода.
Когда метод ожидает блок, он вызывает его, вызывая yieldфункцию.
Это очень удобно, например, для перебора списка или для создания собственного алгоритма.
Возьмите следующий пример:
Я собираюсь определить Personкласс, инициализированный именем, и предоставить do_with_nameметод, который при вызове просто передает nameатрибут полученному блоку.
classPersondef initialize( name )@name= name
enddef do_with_name
yield(@name)endend
Это позволило бы нам вызвать этот метод и передать произвольный кодовый блок.
Например, чтобы напечатать имя, мы бы сделали:
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"]
Или мы также можем предоставить собственный алгоритм сортировки, например, на основе размера строки:
Извините, это имя переменной экземпляра, инициализированной "Oscar" (не очень понятно в ответе)
OscarRyz
Как насчет кода, как это? person.do_with_name {|string| yield string, something_else }
f.ardelian
7
Так что в терминах Javascripty это стандартизированный способ передачи обратного вызова для данного метода и его вызова. Спасибо за объяснение!
Ицньютон
В более общем смысле - это рубиновый «улучшенный» синтаксический сахар для паттерна Стратегия. потому что типичное использование заключается в предоставлении кода, чтобы сделать что-то в контексте другой операции. Но улучшения в ruby открывают путь к таким классным вещам, как написание DSL с использованием блоков для передачи контекста
Роман Булгаков
25
В Ruby методы могут проверять, были ли они вызваны таким образом, чтобы блок был предоставлен в дополнение к обычным аргументам. Обычно это делается с помощью block_given?метода, но вы также можете ссылаться на блок как на явный Proc, добавив префикс ampersand ( &) перед окончательным именем аргумента.
Если метод вызывается с блоком, то метод может 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 =)
Вполне возможно, что кто-то даст здесь действительно подробный ответ, но я всегда находил этот пост Роберта Сосински отличным объяснением тонкостей между блоками, процессами и лямбдами.
Я должен добавить, что я считаю, что пост, на который я ссылаюсь, относится к 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 на этой машине, поэтому выше может быть ошибка.
@klenwell спасибо за внимание, я снова обновил ссылку.
TheIV
13
Я хотел бы добавить, почему вы так поступили, к и без того отличным ответам.
Не знаю, на каком языке вы говорите, но если предположить, что это статический язык, то это будет выглядеть знакомо. Вот как вы читаете файл в Java
publicclassFileInput{publicstaticvoid main(String[] args){File file =newFile("C:\\MyFile.txt");FileInputStream fis =null;BufferedInputStream bis =null;DataInputStream dis =null;try{
fis =newFileInputStream(file);// Here BufferedInputStream is added for fast reading.
bis =newBufferedInputStream(fis);
dis =newDataInputStream(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();}}}
Игнорируя всю цепочку потоковых вещей, Идея заключается в следующем
Инициализируйте ресурс, который необходимо очистить
смеяться над парнями из Java, которые все еще печатают ;-)
Здесь, вместо обработки первого и второго шагов, вы в основном делегируете это другому классу. Как вы можете видеть, это значительно снижает объем кода, который вам нужно написать, что облегчает чтение и снижает вероятность утечек памяти или блокировок файлов.
Теперь не то, чтобы вы не могли делать что-то подобное в Java, на самом деле, люди делают это уже десятилетия. Это называется Стратегия паттерном . Разница в том, что без блоков, для чего-то простого, такого как пример файла, стратегия становится излишней из-за количества классов и методов, которые вам нужно написать. С блоками это такой простой и элегантный способ сделать это, что не имеет никакого смысла НЕ структурировать ваш код таким образом.
Это не единственный способ использования блоков, но другие (например, шаблон Builder, который вы можете увидеть в API form_for в rails) достаточно похожи, чтобы было понятно, что происходит, когда вы обернетесь вокруг этого. Когда вы видите блоки, обычно безопасно предположить, что вызов метода - это то, что вы хотите сделать, а блок описывает, как вы хотите это сделать.
Давайте немного упростим это: File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" endи еще больше смеемся над парнями из Java.
Майкл Хэмптон
1
@MichaelHampton, смейтесь после того, как прочитаете файл длиной в пару гигабайт.
Акостадинов
@akostadinov Нет ... это заставляет меня плакать!
Майкл Хэмптон
3
@MichaelHampton Или, еще лучше: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(плюс нет проблем с памятью)
Фонд Моника иск
12
Я нашел эту статью очень полезной. В частности, следующий пример:
#!/usr/bin/rubydef test
yield5
puts "You are in the method test"yield100end
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 5You are in the method test
You are in the block 100You are in the block 5You are in the method test
You are in the block 100
Таким образом, по сути каждый раз, когда делается вызов yieldruby, будет запускаться код в doблоке или внутри {}. Если указан параметр, yieldто он будет предоставлен в качестве параметра для doблока.
Для меня это был первый раз, когда я действительно понял, что doделают блоки. Это в основном способ для функции предоставить доступ к внутренним структурам данных, будь то для итерации или для конфигурации функции.
Это запустит respond_toфункцию, которая выдает doблок с (внутренним) formatпараметром. Затем вы вызываете .htmlфункцию для этой внутренней переменной, которая в свою очередь выдает блок кода для запуска renderкоманды. Обратите внимание, что .htmlвыдаст только если это запрошенный формат файла. (техническая специфика: эти функции на самом деле используют block.callне так, yieldкак вы можете видеть из источника, но функциональность по сути та же самая, см. этот вопрос для обсуждения.) Это обеспечивает способ для функции выполнить некоторую инициализацию, а затем принять ввод из вызывающего кода и затем продолжите обработку, если требуется.
Или, другими словами, это похоже на функцию, принимающую анонимную функцию в качестве аргумента и вызывающую ее в javascript.
В Ruby блок - это, по сути, кусок кода, который может быть передан и выполнен любым методом. Блоки всегда используются с методами, которые обычно подают к ним данные (в качестве аргументов).
Блоки широко используются в гемах Ruby (включая Rails) и в хорошо написанном коде Ruby. Они не являются объектами, поэтому не могут быть присвоены переменным.
Основной синтаксис
Блок - это фрагмент кода, заключенный в {} или do..end. По соглашению синтаксис фигурных скобок должен использоваться для однострочных блоков, а синтаксис do..end должен использоваться для многострочных блоков.
{# This is a single line block }do# This is a multi-line blockend
Любой метод может получить блок в качестве неявного аргумента. Блок выполняется оператором yield в методе. Основной синтаксис:
def meditate
print "Today we will practice zazen"yield# This indicates the method is expecting a blockend# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes."}Output:Today we will practice zazen for40 minutes.
Когда оператор yield достигнут, метод meditate передает управление блоку, код внутри блока выполняется и управление возвращается методу, который возобновляет выполнение сразу после оператора yield.
Когда метод содержит оператор yield, он ожидает получить блок во время вызова. Если блок не указан, то исключение будет выдано после достижения оператора yield. Мы можем сделать блок необязательным и избежать исключения:
def meditate
puts "Today we will practice zazen."yieldif block_given?end meditate
Output:Today we will practice zazen.
Невозможно передать несколько блоков в метод. Каждый метод может получить только один блок.
Хорошо, но почему ? Есть много причин, например, одна Loggerне должна выполнять какую-либо задачу, если пользователю это не нужно. Вы должны объяснить свои, хотя ...
Ulysse BN
4
Проще говоря, выходы позволяют создаваемому методу принимать и вызывать блоки. Ключевое слово yield - это место, где будет выполняться «материал» в блоке.
Есть два момента, которые я хочу сделать о доходности здесь. Во-первых, хотя многие ответы здесь говорят о различных способах передачи блока методу, который использует yield, давайте также поговорим о потоке управления. Это особенно актуально, так как вы можете выдавать НЕСКОЛЬКО раз на блок. Давайте посмотрим на пример:
classFruit
attr_accessor :kinds
def initialize
@kinds=%w(orange apple pear banana)enddef each
puts 'inside each'3.times{yield(@kinds.tap {|kinds| puts "selecting from #{kinds}"}).sample }endend
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.
Так что обратите внимание, что каждый раз, когда мы вызываем виды с помощью внешнего итератора, он будет вызывать 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, вы должны четко указать, каким будет ваш метод перечисления.
Выход можно использовать как безымянный блок для возврата значения в методе. Рассмотрим следующий код:
DefUp(anarg)yield(anarg)end
Вы можете создать метод «Вверх», которому присваивается один аргумент. Теперь вы можете назначить этот аргумент yield, который будет вызывать и выполнять связанный блок. Вы можете назначить блок после списка параметров.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Когда метод Up вызывает yield с аргументом, он передается в переменную блока для обработки запроса.
Ответы:
Да, сначала это немного озадачивает.
В Ruby методы могут получить блок кода для выполнения произвольных сегментов кода.
Когда метод ожидает блок, он вызывает его, вызывая
yield
функцию.Это очень удобно, например, для перебора списка или для создания собственного алгоритма.
Возьмите следующий пример:
Я собираюсь определить
Person
класс, инициализированный именем, и предоставитьdo_with_name
метод, который при вызове просто передаетname
атрибут полученному блоку.Это позволило бы нам вызвать этот метод и передать произвольный кодовый блок.
Например, чтобы напечатать имя, мы бы сделали:
Будет печатать:
Обратите внимание, что блок получает в качестве параметра переменную с именем
name
(NB. Вы можете вызывать эту переменную как угодно, но имеет смысл ее вызыватьname
). Когда код вызывает,yield
он заполняет этот параметр значением@name
.Мы могли бы предоставить другой блок для выполнения другого действия. Например, измените имя:
Мы использовали точно такой же метод (
do_with_name
) - это просто другой блок.Этот пример тривиален. Более интересные способы фильтрации всех элементов массива:
Или мы также можем предоставить собственный алгоритм сортировки, например, на основе размера строки:
Я надеюсь, что это поможет вам лучше понять это.
Кстати, если блок не является обязательным, вы должны назвать его следующим образом:
Если не является обязательным, просто вызовите его.
РЕДАКТИРОВАТЬ
@hmak создал repl.it для этих примеров: https://repl.it/@makstaks/blocksandyieldsrubyexample
источник
racsO
еслиthe_name = ""
"Oscar"
(не очень понятно в ответе)person.do_with_name {|string| yield string, something_else }
В Ruby методы могут проверять, были ли они вызваны таким образом, чтобы блок был предоставлен в дополнение к обычным аргументам. Обычно это делается с помощью
block_given?
метода, но вы также можете ссылаться на блок как на явный Proc, добавив префикс ampersand (&
) перед окончательным именем аргумента.Если метод вызывается с блоком, то метод может
yield
управлять блоком (вызывать блок) с некоторыми аргументами, если это необходимо. Рассмотрим этот пример метода, который демонстрирует:Или, используя специальный синтаксис аргумента блока:
источник
Вполне возможно, что кто-то даст здесь действительно подробный ответ, но я всегда находил этот пост Роберта Сосински отличным объяснением тонкостей между блоками, процессами и лямбдами.
Я должен добавить, что я считаю, что пост, на который я ссылаюсь, относится к ruby 1.8. Некоторые вещи изменились в ruby 1.9, например, переменные блока являются локальными для блока. В 1.8 вы получите что-то вроде следующего:
Тогда как 1.9 даст вам:
У меня нет 1.9 на этой машине, поэтому выше может быть ошибка.
источник
Я хотел бы добавить, почему вы так поступили, к и без того отличным ответам.
Не знаю, на каком языке вы говорите, но если предположить, что это статический язык, то это будет выглядеть знакомо. Вот как вы читаете файл в Java
Игнорируя всю цепочку потоковых вещей, Идея заключается в следующем
Вот как ты это делаешь в ruby
Дико отличается. Ломая это
Здесь, вместо обработки первого и второго шагов, вы в основном делегируете это другому классу. Как вы можете видеть, это значительно снижает объем кода, который вам нужно написать, что облегчает чтение и снижает вероятность утечек памяти или блокировок файлов.
Теперь не то, чтобы вы не могли делать что-то подобное в Java, на самом деле, люди делают это уже десятилетия. Это называется Стратегия паттерном . Разница в том, что без блоков, для чего-то простого, такого как пример файла, стратегия становится излишней из-за количества классов и методов, которые вам нужно написать. С блоками это такой простой и элегантный способ сделать это, что не имеет никакого смысла НЕ структурировать ваш код таким образом.
Это не единственный способ использования блоков, но другие (например, шаблон Builder, который вы можете увидеть в API form_for в rails) достаточно похожи, чтобы было понятно, что происходит, когда вы обернетесь вокруг этого. Когда вы видите блоки, обычно безопасно предположить, что вызов метода - это то, что вы хотите сделать, а блок описывает, как вы хотите это сделать.
источник
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
и еще больше смеемся над парнями из Java.IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(плюс нет проблем с памятью)Я нашел эту статью очень полезной. В частности, следующий пример:
который должен дать следующий вывод:
Таким образом, по сути каждый раз, когда делается вызов
yield
ruby, будет запускаться код вdo
блоке или внутри{}
. Если указан параметр,yield
то он будет предоставлен в качестве параметра дляdo
блока.Для меня это был первый раз, когда я действительно понял, что
do
делают блоки. Это в основном способ для функции предоставить доступ к внутренним структурам данных, будь то для итерации или для конфигурации функции.Поэтому, когда в рельсах вы пишете следующее:
Это запустит
respond_to
функцию, которая выдаетdo
блок с (внутренним)format
параметром. Затем вы вызываете.html
функцию для этой внутренней переменной, которая в свою очередь выдает блок кода для запускаrender
команды. Обратите внимание, что.html
выдаст только если это запрошенный формат файла. (техническая специфика: эти функции на самом деле используютblock.call
не так,yield
как вы можете видеть из источника, но функциональность по сути та же самая, см. этот вопрос для обсуждения.) Это обеспечивает способ для функции выполнить некоторую инициализацию, а затем принять ввод из вызывающего кода и затем продолжите обработку, если требуется.Или, другими словами, это похоже на функцию, принимающую анонимную функцию в качестве аргумента и вызывающую ее в javascript.
источник
В Ruby блок - это, по сути, кусок кода, который может быть передан и выполнен любым методом. Блоки всегда используются с методами, которые обычно подают к ним данные (в качестве аргументов).
Блоки широко используются в гемах Ruby (включая Rails) и в хорошо написанном коде Ruby. Они не являются объектами, поэтому не могут быть присвоены переменным.
Основной синтаксис
Блок - это фрагмент кода, заключенный в {} или do..end. По соглашению синтаксис фигурных скобок должен использоваться для однострочных блоков, а синтаксис do..end должен использоваться для многострочных блоков.
Любой метод может получить блок в качестве неявного аргумента. Блок выполняется оператором yield в методе. Основной синтаксис:
Когда оператор yield достигнут, метод meditate передает управление блоку, код внутри блока выполняется и управление возвращается методу, который возобновляет выполнение сразу после оператора yield.
Когда метод содержит оператор yield, он ожидает получить блок во время вызова. Если блок не указан, то исключение будет выдано после достижения оператора yield. Мы можем сделать блок необязательным и избежать исключения:
Невозможно передать несколько блоков в метод. Каждый метод может получить только один блок.
Смотрите больше на: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
источник
Я иногда использую «yield» так:
источник
Logger
не должна выполнять какую-либо задачу, если пользователю это не нужно. Вы должны объяснить свои, хотя ...Проще говоря, выходы позволяют создаваемому методу принимать и вызывать блоки. Ключевое слово yield - это место, где будет выполняться «материал» в блоке.
источник
Есть два момента, которые я хочу сделать о доходности здесь. Во-первых, хотя многие ответы здесь говорят о различных способах передачи блока методу, который использует yield, давайте также поговорим о потоке управления. Это особенно актуально, так как вы можете выдавать НЕСКОЛЬКО раз на блок. Давайте посмотрим на пример:
Когда каждый метод вызывается, он выполняется построчно. Теперь, когда мы дойдем до блока 3.times, этот блок будет вызываться 3 раза. Каждый раз, когда это вызывает доходность. Этот выход связан с блоком, связанным с методом, который вызвал каждый метод. Важно отметить, что каждый раз, когда yield вызывается, он возвращает управление обратно в блок каждого метода в клиентском коде. По завершении выполнения блока он возвращается обратно в блок 3.times. И это происходит 3 раза. Таким образом, этот блок в клиентском коде вызывается 3 раза, поскольку yield явно вызывается 3 раза.
Мое второе замечание касается enum_for и yield. enum_for создает экземпляр класса Enumerator, и этот объект Enumerator также отвечает на yield.
Так что обратите внимание, что каждый раз, когда мы вызываем виды с помощью внешнего итератора, он будет вызывать yield только один раз. В следующий раз, когда мы его назовем, он вызовет следующий выход и так далее.
Есть интересная новость в отношении enum_for. Документация онлайн заявляет следующее:
Если вы не укажете символ в качестве аргумента enum_for, ruby подключит перечислитель к каждому методу получателя. Некоторые классы не имеют метода each, как класс String.
Таким образом, в случае некоторых объектов, вызываемых с помощью enum_for, вы должны четко указать, каким будет ваш метод перечисления.
источник
Выход можно использовать как безымянный блок для возврата значения в методе. Рассмотрим следующий код:
Вы можете создать метод «Вверх», которому присваивается один аргумент. Теперь вы можете назначить этот аргумент yield, который будет вызывать и выполнять связанный блок. Вы можете назначить блок после списка параметров.
Когда метод Up вызывает yield с аргументом, он передается в переменную блока для обработки запроса.
источник