我希望在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
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
并转发您的块。这实际上已经在文档中了,但它有点微妙。在编译过程中,如果你有一个yield
,编译器会从字面上内联你的块,产量是(据我所知)。使用block.call
时,会创建一个函数,这就是我们需要键入块参数的原因。如果你不给它一个类型,block.call
将期望 0 个参数。要传递内容,只需键入类似于我在此方法签名中所做的操作。
基于上面的解释,为什么当你只是屈服并且它会起作用时,你不需要向block
添加一个类型是有道理的。了解为什么yield
和block.call
之间存在性能差异也很重要,因为在一种情况下,会创建闭合函数而不是内联代码的编译器。