我希望在Crystal中实现Python的os.walk方法。我试图递归地执行它,但编译器被告知我要小心递归屈服,因为它递归/无限地会在编译时生成代码。这就是我所拥有的
def walk(d = @root, &block)
d = Dir.new(d) if d.is_a?(String)
dirs, files = d.entries.partition { |s| Dir.exists?(File.join(d.path, s)) }
if Dir.exists?(d.path)
yield d.path, dirs, files
dirs.each do |dir_name|
# recursively yield
walk File.join(d.path, dir_name), do |a, b, c|
yield a, b, c
end
end
end
end
答案 0 :(得分:5)
Gitter社区的一些有用的成员向我指出了正确的方向,只是想在这里分享我的经验。答案是,您不能递归使用yield
,但必须使用block
变量(来解释)。这就是我最终的结果:
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
这里的诀窍是,您不必使用yield
关键字,而是使用block.call
代替转发您的区块。这在文档中实际上是already,但它有点微妙。在编译期间,如果你有一个yield
,编译器将按字面意思内联你的块的位置(据我所知)。使用block.call
时,会创建一个函数,这就是我们需要输入块参数的原因。如果你没有给它一个类型,block.call
将期望0个参数。要传递信息,只需输入类似于我在此方法签名中的操作方式。
基于上面的解释,有意义的是,当你刚刚屈服时,你不需要向block
添加一个类型,它会起作用。理解为什么yield
和block.call
之间存在性能差异也很重要,因为在一种情况下会创建一个结束函数而不是编译器内联代码。