为什么这个 F# 内部函数不是尾递归的?

发布于 2024-10-21 17:45:40 字数 1458 浏览 8 评论 0原文

如果我使用非常高的初始 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

If I call this function with a very high initial currentReflection value I get a stack overflow exception, which indicates that the function is not tail-recursive (correct?). My understanding was that as long as the recursive call was the final computation of the function then it should be compiler-optimized as a tail-recursive function to reuse the current stack frame. Anyone know why this isn't the case here?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

樱桃奶球 2024-10-28 17:45:40

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 调用该函数。

The recursive call to traceColorAt appears as part of a larger expression. This prevents tail call optimization because further computation is necessary after traceColorAt returns.

To convert this function to be tail recursive, you could add an additional accumulator parameter for primaryColor. The outermost call to traceColorAt would pass the "zero" value for primaryColor (black?) and each recursive call would sum in the adjustment it computes, e.g. the code would look something like:

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

If you wish to hide the extra parameter from callers, introduce a helper function that performs the bulk of the work and call that from traceColorAt.

您的好友蓝忘机已上羡 2024-10-28 17:45:40

如果函数只是返回另一个函数的结果,则尾递归有效。在本例中,您有 primaryColor + traceColorAt(...),这意味着它不只是返回函数的值 - 它还向其中添加一些内容。

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

Tail recursion works if the function would simply return the result of another function. In this case, you have primaryColor + traceColorAt(...), which means that it is not simply returning the value of the function-- it's also adding something to it.

You could fix this by passing the current accumulated color as a parameter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文