Injecting block argument types

I’m adding a feature into the Neo4j shard that will let me inject the types of data I expect back from the database because writing the conversion for the compiler gets tedious:

result = connection.execute(query)

result.map do |(user, group)|
  {
    User.new(user.as(Neo4j::Node)),
    Group.new(group.as(Neo4j::Node)),
  }
end

Because this is so common, I’ve added a method that lets you pass the types as a tuple and it will build the result as an array of tuples of those types:

connection.exec_cast(query, { User, Group })

And this works fine, I’ve been using it for a while. I’m trying to generalize this so that you can process each result row as you get it back from the DB instead of at the end of the query (so that large query results with millions of rows don’t need to be completely held in memory). I’m using this idea to let you pass a block:

def foo(&block : { Int32 } ->)
  yield({12 })
end

foo { |(n)| n }

This works just fine, but since method overloading does not take block args into account and not all calls to exec_cast will use the same block-arg types, I’m trying to allow them to be injected into the method call so that overloading will occur. Unfortunately, injecting the tuple of types makes it not compile:

def foo(types : T, &block : T ->) forall T
  yield({ 12 })
end

foo({ Int32 }) { |(n)| n }

Does anyone have any ideas how to make this work?

I was able to get it working using @Blacksmoke16’s suggestion of just not specifying the types for the block at all. But there was a catch — I was passing the block to another method (there were a few variations of the method for convenience that were all implemented in terms of this one method), which seems to be tricky when the block takes arguments, and that seems to be why I thought I needed to specify the types.

For example, taking the example from the first post and passing that block to another method shows the problem I was having:

def foo(types : T, &block) forall T
  bar(types, &block)
end

def bar(types : T, &block) forall T
  yield({12})
end

foo({ Int32 }) { |(n)| n }
# Error in line 9: wrong number of block arguments (given 1, expected 0)

If, instead of capturing the block in foo, I pass my own which yields, it works:

def foo(types : T, &block) forall T
  bar(types) { |n| yield n }
end

def bar(types : T, &block) forall T
  yield({12})
end

foo({ Int32 }) { |(n)| n }
# 12