从第一个元素创建一个 ruby 可枚举,以及一​​个从前一个元素获取下一个元素的函数

create a ruby enumerable from a first element, and a function to get the next element from the previous one

在 Scala 中,您可以从它的第一个元素定义一个流,以及一个从前一个元素获取下一个元素的函数;

Stream.iterate(1)(x => 2 * x + 1)

Ruby中是否存在这样的东西?

当然可以手卷-

 module Kernel
   def stream
     Enumerator.new do |y|
       that = self
       while true
         y << that
         that = yield that
       end
     end
   end
 end

但它是地道的吗?已经有这样的东西了吗?

在 Ruby 中,您将使用 Enumerable#inject:

[7, 8, 9].inject(1){ |carry, item| 2 * carry + item }

块的 return 值将用作进位变量的下一个值。

你问的是 (which is called Stream.iterate in ), or more generically, an . It is the exact category-theoretical dual of a (which is called Enumerable#inject in ) aka a

Is there something like this already ?

遗憾的是,核心库或标准库中没有执行此功能的方法。

But is it idiomatic ?

我可能会将其设为 Enumerator and use Kernel#loop 而不是 while true 的单例方法,仅此而已。否则,是的,您的代码非常地道。

我们就这样称呼它 unfold:

def Enumerator.unfold(start)
  new do |y|
    loop do
      y << start
      start = yield start
    end
  end
end

我不知道有什么公认的惯用方法可以做到这一点。 由于您的代码似乎无法生成您喜欢的结果,我将举例说明如何实现它。

您可以使用 Fiber 创建外部迭代展开,如下所示:

def stream(init, &block)
  Fiber.new do 
    memo = init
    loop do
      Fiber.yield memo
      memo = block.call memo
    end
  end
end

a = stream(1){ |x| 2 * x + 1 }

a.resume  # => 1
a.resume  # => 3
a.resume  # => 7
a.resume  # => 15

如果您更喜欢 Enumerator::Lazy 的所有超能力,请考虑以下:

def stream2(init, &block)
  Enumerator.new do |yielder| 
    memo = init
    loop do 
      yielder << memo
      memo = block.call memo 
    end 
  end.lazy
end

a2 = stream2(1){ |x| 2 * x + 1 }

a2.next  # => 1
a2.next  # => 3
a2.next  # => 7

a2.take(10).force  # => [1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]

其行为与此相同:

def stream3(init, &block)
  memo = init
  (1..Float::INFINITY).lazy.map{ |_| memo = block.call(memo) }
end

最后一个可能是最惯用的并且最好地隐藏了枚举的内部结构。但是,我个人不喜欢 stream3,因为它生成数字只是为了丢弃它们。

HTH