Я пытаюсь понять блоки и yield
и как они работают в Ruby.
Как yield
б/У? Многие приложения направляющих я посмотрел на использование yield
странным способом.
Кто-то может объяснить мне или показать мне, куда пойти для понимания их?
Да, поначалу это немного озадачивает.
В Ruby методы могут получать блок кода для выполнения произвольных сегментов кода.
Когда метод ожидает блок, он вызывает его, вызывая функцию yield
.
Это очень удобно, например, для перебора списка или для предоставления специального алгоритма.
Рассмотрим следующий пример:
Я собираюсь определить класс Person
, инициализированный именем, и предоставить метод do_with_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, вы можете вызвать эту переменную как хотите, но есть смысл называть это именем
). Когда код вызывает 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?
Если не является необязательным, просто вызовите его.
Я хотел добавить, почему вы делаете вещи таким образом, к уже полученным ответам.
Не знаю, с какого языка вы пришли, но если это статический язык, то подобные вещи будут выглядеть знакомо. Вот как вы читаете файл в 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();
}
}
}
Игнорируя всю цепочку потоков, идея такова
Вот как вы делаете это в ruby
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Дико отличается. Разбираем этот
Здесь, вместо того, чтобы обрабатывать шаг один и два, вы, по сути, делегируете это другому классу. Как вы можете видеть, это значительно сокращает объем кода, который вам приходится писать, что делает вещи более легкими для чтения, и уменьшает вероятность таких вещей, как утечка памяти, или блокировки файлов, которые не были очищены.
Теперь, не похоже, что вы не можете сделать что-то подобное в java, на самом деле, люди делают это уже десятилетиями. Это называется Стратегия. Разница в том, что без блоков, для чего-то простого, как в примере с файлом, стратегия становится излишеством из-за количества классов и методов, которые вам нужно написать. С блоками это настолько простой и элегантный способ сделать это, что нет никакого смысла НЕ структурировать код таким образом.
Это не единственный способ использования блоков, но другие (например, паттерн Builder, который вы можете увидеть в form_for api в rails) достаточно похожи, чтобы было очевидно, что происходит, как только вы поймете это. Когда вы видите блоки, обычно можно предположить, что вызов метода - это то, что вы хотите сделать, а блок описывает, как вы хотите это сделать.
В Ruby методы могут проверять, были ли они вызваны таким образом, что в дополнение к обычным аргументам был предоставлен блок. Обычно это делается с помощью метода block_given?
, но вы также можете ссылаться на блок как на явный Proc, добавив амперсанд ( &
) перед именем последнего аргумента.
Если метод вызывается с блоком, тогда метод может передать
управление блоку (вызвать блок) с некоторыми аргументами, если это необходимо.Рассмотрим этот пример метода, который демонстрирует:
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 на этой машине, поэтому в приведенном выше примере может быть ошибка.