A few helpful members of the community on Gitter pointed me in the right direction and just wanted to share my learnings here. The answer is, is that you cannot use yield
recursively but you must use the block
variable instead (explanation to come). Here's what I ended up with:
def walk(d = @root, &block : String, Array(String), Array(String) -> )
d = Dir.new(d) if d.is_a?(String)
dirs, files = d.children.partition { |s| Dir.exists?(File.join(d.path, s)) }
block.call(d.path, dirs, files)
dirs.each do |dir_name|
walk File.join(d.path, dir_name), &block
end
end
The trick here is that instead of using the yield
keyword you have to use block.call
instead and forward your block. This is actually already in the docs, but it's a little subtle. During compilation, if you have a yield
, the compiler will literally inline your block where the yield is (as far as I understand). When using block.call
, a function is created instead, and that's the reason we need to type the block argument. If you don't give it a type, block.call
will expect 0 arguments. To pass things through, just type it similar to how I did it in this method signature.
Based on the above explanation, it makes sense why you wouldn't need to add a type to block
when you just yield and it would just work. It's also important to understand why there's a performance difference between yield
and block.call
, since in one case a closing function is created versus the compiler inlining your code.