【问题标题】:Why isn't this F# inner function tail-recursive?为什么这个 F# 内部函数不是尾递归的?
【发布时间】:2011-07-13 23:12:53
【问题描述】:

如果我用一个非常高的初始 currentReflection 值调用这个函数,我会得到一个堆栈溢出异常,这表明该函数不是尾递归的(正确的?)。我的理解是,只要递归调用是函数的最终计算,那么它应该被编译器优化为尾递归函数以重用当前堆栈帧。有人知道为什么这里不是这样吗?

let rec traceColorAt intersection ray currentReflection =
        // some useful values to compute at the start
        let matrix = intersection.sphere.transformation |> transpose |> invert
        let transNormal = matrix.Transform(intersection.normal) |> norm
        let hitPoint = intersection.point

        let ambient = ambientColorAt intersection
        let specular = specularColorAt intersection hitPoint transNormal
        let diffuse = diffuseColorAt intersection hitPoint transNormal
        let primaryColor = ambient + diffuse + specular

        if currentReflection = 0 then 
            primaryColor
        else
            let reflectDir = (ray.direction - 2.0 * norm ((Vector3D.DotProduct(ray.direction, intersection.normal)) * intersection.normal))
            let newRay = { origin=intersection.point; direction=reflectDir }
            let intersections = castRay newRay scene
            match intersections with
                | [] -> primaryColor
                | _  -> 
                    let newIntersection = List.minBy(fun x -> x.t) intersections
                    let reflectivity = intersection.sphere.material.reflectivity
                    primaryColor + traceColorAt newIntersection newRay  (currentReflection - 1) * reflectivity

【问题讨论】:

    标签: recursion f# functional-programming tail-recursion tail-call-optimization


    【解决方案1】:

    如果函数只返回另一个函数的结果,则尾递归有效。在这种情况下,你有primaryColor + traceColorAt(...),这意味着它不仅仅是返回函数的值——它还在向它添加一些东西。

    您可以通过将当前累积的颜色作为参数传递来解决此问题。

    【讨论】:

      【解决方案2】:

      traceColorAt 的递归调用显示为更大表达式的一部分。这会阻止尾调用优化,因为在traceColorAt 返回后需要进一步计算。

      要将此函数转换为尾递归,您可以为primaryColor 添加一个额外的累加器参数。对traceColorAt 的最外层调用将传递primaryColor(黑色?)的“零”值,并且每个递归调用将在它计算的调整中求和,例如代码看起来像:

      let rec traceColorAt intersection ray currentReflection primaryColor
      ...
      let newPrimaryColor = primaryColor + ambient + diffuse + specular
      ...
      match intersections with
          | [] -> newPrimaryColor
          | _ ->
              ...
              traceColorAt newIntersection newRay ((currentReflection - 1) * reflectivity) newPrimaryColor
      

      如果您希望对调用者隐藏额外参数,请引入一个辅助函数来执行大部分工作并从traceColorAt 调用它。

      【讨论】:

      • 函数应用的优先级高于函数应用,所以反射率的乘法是在原来的递归调用之后完成的。所以,这个答案中的代码计算的不是同样的东西。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-28
      • 1970-01-01
      相关资源
      最近更新 更多