Блоки и доходности в 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, с аргументом, он передается блочной переменной для обработки запроса.