"Each" method special definition

Given the class definition below, I am faced with the following problem, which I am stuck on !
How can I define the each method so that iteration is done on @sources.

class Table(T)
include Enumerable(T)

def initialize(@sources : Enumerable(T))
end

def each …?
end
end

Any idea(s) ?

Normally you’d do something like:

  def each(& : T ->) : Nil
    @sources.each do |v|
      yield v
    end
  end

However when trying this I get Error: recursive block expansion: blocks that yield are always inlined, and this call leads to an infinite inlining, which I haven’t seen before :thinking:. BUT, only if the initialize argument is typed as Enumerable(T), if i change that to like Array(T) it works fine…so not sure what’s going on there.

https://play.crystal-lang.org/#/r/dv18

EDIT: Or could use delegate :each, to: @sources as a shortcut, but I think it would lose the block/return typing. Not a big deal in this context tho.

Yes, I have encountered this very problem, hence my call for help. Unfortunately, the Enumerable(T) restriction is the most suitable for my application, Array(T) would be too reductive.
Perhaps a lower level approach (pointers) is worth considering…

I presume the error comes from the fact that the definition of @sources.each is Enumerable(Int32)#each and its called from an implementation of Enumerable(Int32)#each (because include Enumerable(T) for Table(T)).
So you can nest Table indefinitely: Table(Int32).new(Table(Int32).new(Table(Int32).new(Table(Int32).new(...)))). This would lead to recursive calls of the same method, resulting in recursive block expansion.

1 Like

@Blacksmoke16 Please can you elaborate on this solution, I am not familiar with the delegate macro?

See Object - Crystal 1.6.0-dev.

It basically just generates the wrapper method for you. E.g. in the API docs delegate downcase, to: @string would generate like:

def downcase(*args, **kwargs)
  @string.downcase *args, **kwargs
end

@Blacksmoke16 , Thanks, I’ll try that tomorrow !

No, to be clear it’ll suffer from the same issue as

since it generates equivalent code. Just a shortcut to reduce the boilerplate if that would have worked.

I finally found another solution, replacing T in include Enumerable(T) by another type, without too much refactoring in my app.
Thanks anyway.