递归屈服时的递归块扩展错误

mlo*_*obl 3 recursion crystal-lang

我当时希望在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
Run Code Online (Sandbox Code Playgroud)

mlo*_*obl 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
Run Code Online (Sandbox Code Playgroud)

这里的诀窍是,不是使用yield关键字而是必须使用block.call而转发块.这实际上已经在文档中,但它有点微妙.在编译期间,如果你有一个yield,编译器将字面上内联你的块的产量(据我所知).使用时block.call,会创建一个函数,这就是我们需要输入块参数的原因.如果你没有给它一个类型,那么block.call期望0个参数.要传递信息,只需输入类似于我在此方法签名中的操作方式.

根据上面的解释,为什么你不需要block在你刚刚屈服时添加一个类型就可以了,这是有道理的.理解为什么在yield和之间存在性能差异也很重要block.call,因为在一种情况下,创建关闭函数而不是编译器内联代码.