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

时间:2018-03-22 15:55:37

标签: 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

1 个答案:

答案 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添加一个类型,它会起作用。理解为什么yieldblock.call之间存在性能差异也很重要,因为在一种情况下会创建一个结束函数而不是编译器内联代码。