Может ли метод Ruby работать как итератор или возвращать массив в зависимости от контекста?
-
20-08-2019 - |
Вопрос
У меня есть произвольный метод в Ruby, который возвращает несколько значений, поэтому его можно передать в блок:
def arbitrary
yield 1
yield 2
yield 3
yield 4
end
arbitrary { |x| puts x }
Я хотел бы изменить этот метод так, чтобы при отсутствии блока он просто возвращал значения в виде массива.Так что эта конструкция тоже будет работать:
myarray = arbitrary
p a -----> [1, 2, 3, 4, 5]
Возможно ли это в Руби?
Решение
Для этого есть синтаксис:
def arbitrary(&block)
values = [1, 2, 3, 4]
if block
values.each do |v|
yield v
end
else
values
end
end
Примечание:
yield v
Можно заменить на:
block.call v
Другие советы
def arbitrary
values = [1,2,3,4]
return values unless block_given?
values.each { |val| yield(val) }
end
arbitrary { |x| puts x }
arbitrary
В Ruby 1.9+ вы можете использовать Счетчик чтобы реализовать это.
def arbitrary(&block)
Enumerator.new do |y|
values = [1,2,3,4]
values.each { |val| y.yield(val) }
end.each(&block)
end
Его преимущество заключается в том, что он работает и для бесконечных потоков:
# block-only version
#
def natural_numbers
0.upto(1/0.0) { |x| yield x }
end
# returning an enumerator when no block is given
#
def natural_numbers(&block)
Enumerator.new do |y|
0.upto(1/0.0) { |x| y.yield(x) }
end.each(&block)
end
Но самый идиоматический способ сделать это — защитить свой метод to_enum(your_method_name, your_args)
вот так:
def arbitrary
return to_enum(:arbitrary) unless block_given?
yield 1
yield 2
yield 3
yield 4
end
Это идиома, которую сами библиотеки ядра Ruby используют во многих местах.
Не связан с StackOverflow