Может ли метод Ruby выдавать в качестве итератора или возвращать массив в зависимости от контекста? - PullRequest
10 голосов
/ 26 июня 2009

У меня есть произвольный метод в 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]

Возможно ли это в Ruby?

Ответы [ 3 ]

19 голосов
/ 26 июня 2009
def arbitrary
  values = [1,2,3,4]
  return values unless block_given? 
  values.each { |val| yield(val) }
end
arbitrary { |x| puts x }
arbitrary
14 голосов
/ 26 июня 2009

Для этого есть синтаксис:

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
12 голосов
/ 25 августа 2011

В ruby ​​1.9+ вы можете использовать Enumerator для реализации этого.

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 ​​core используют в нескольких местах.

...