3

I can write a lazy fibonacci in Clojure like this:

(def fib (lazy-cat [1 1] (map +' fib (rest fib))))

and I'm trying (unsuccessfully) to write it in Ruby like this:

fib = Enumerator.new do |yielder|
  yielder << 1 << 1
  fib.zip(fib.drop(1)).map do |a,b|
    yielder << (a + b)
  end
end

In the simplified case, this works:

fib = Enumerator.new do |yielder|
  yielder << 1 << 1
  puts "here"
end
puts fib.take(2).inspect
puts fib.drop(1).take(1).inspect

but this doesn't:

fib = Enumerator.new do |yielder|
  yielder << 1 << 1
  puts "here"
  fib.drop(1)
end
puts fib.take(2).inspect
puts fib.drop(1).take(1).inspect

Why does that last example give me a SystemStackError: stack level too deep error?

Alex Miller
  • 69,183
  • 25
  • 122
  • 167
bmaddy
  • 876
  • 9
  • 16

1 Answers1

4

First, fib in the ruby version is not equivalent to the clojure version. In clojure version, it's a function.

And Enumerable#zip, Enumerable#drop and Enumerable.take are not lazy unless you explicitly specify it. If you don't call Enumerable#lazy, they return an Array (eagerly consuming all items; cause the exception).

def fib
  Enumerator.new do |yielder|
    yielder << 1 << 1
    fib.lazy.zip(fib.lazy.drop(1)).each do |a,b|
      yielder << a + b
    end
  end
end

fib.take(2)
# => [1, 1]
fib.lazy.drop(1).take(1).to_a  # Note: `lazy`, `to_a`.
# => [1]
fib.take(4)
# => [1, 1, 2, 3]
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • Wow, that's surprising to me that you need to use ```.lazy``` there. The Clojure code above creates a var and is used like this: ```(take 2 (drop 1 fib))```. A function would be created with ```defn```. Thanks for the answer--exactly what I was looking for! – bmaddy Oct 03 '14 at 19:38
  • 2
    Maybe a warning about the inefficiency would be good. Already `fib.take(20)` crashes for me with `can't create fiber (FiberError)`. I btw came here from a [new question using this](https://stackoverflow.com/q/48492051/1672429). – Stefan Pochmann Jan 28 '18 at 23:36