Search code examples
crystal-lang

Is it possible to manually fetch `yield` values instead of in a loop?


Taking this example from the docs, we can send a block that receives the yield values from the function.

def twice
  yield 1
  yield 2
end

twice do |i|
  puts i
end

What I'm wondering is if we can manually call on the function so that the first call gives 1 then we do other stuff, and then the next call gives 2.

I tried (just guessing) various things, like puts twice() but to no avail. Is there any way this can be done?


Solution

  • You can use spawn and channels. This is the closest you can get:

    def foo
      yield 1
      yield 2
    end
    
    chan = Channel(typeof(foo { |x| x })).new
    
    spawn do
      foo do |x|
        chan.send x
      end
      chan.close
    end
    
    p chan.receive?
    p chan.receive?
    p chan.receive?
    

    http://play.crystal-lang.org/#/r/ijd

    You could build an abstraction over that, maybe with macros:

    def foo
      yield 1
      yield 2
    end
    
    macro enumerator(call)
      %chan = Channel(typeof({{call}} { |x| x })).new
      spawn do
        {{call}} do |x|
          %chan.send x
        end
        %chan.close
      end
      %chan
    end
    
    values = enumerator(foo)
    
    p values.receive?
    p values.receive?
    p values.receive?
    

    http://play.crystal-lang.org/#/r/ije

    And probably using a wrapper struct so it looks like an Iterator ( http://crystal-lang.org/api/Iterator.html ). But note that this is kind of slow, at least much slower than using an iterator or a regular non-captured block call.