我的rec函数是尾递归的吗?

发布于 2024-11-03 17:04:07 字数 333 浏览 4 评论 0原文


这个函数是尾递归的吗?

let rec rec_algo1 step J = 
    if step = dSs then J
    else
        let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
        let argmin = a|> Array.minBy snd |> fst
        rec_algo1 (step+1) (argmin::J)

一般来说,有没有办法正式检查它?

谢谢。

Is this function tail-recursive ?

let rec rec_algo1 step J = 
    if step = dSs then J
    else
        let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
        let argmin = a|> Array.minBy snd |> fst
        rec_algo1 (step+1) (argmin::J)

In general, is there a way to formally check it ?

Thanks.

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

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

发布评论

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

评论(3

别想她 2024-11-10 17:04:07

该函数是尾递归的;我用肉眼就能看出来。

一般来说,这并不总是容易判断。也许最可靠/务实的事情就是在大输入上检查它(并确保您在“发布”模式下进行编译,因为“调试”模式会关闭尾部调用以实现更好的调试)。

This function is tail-recursive; I can tell by eyeballing it.

In general it is not always easy to tell. Perhaps the most reliable/pragmatic thing is just to check it on a large input (and make sure you are compiling in 'Release' mode, as 'Debug' mode turns off tail calls for better debugging).

没有伤那来痛 2024-11-10 17:04:07

是的,您可以正式证明函数是尾递归的。每个表达式归约都有一个尾位置,如果所有递归都位于尾位置,则该函数是尾递归的。函数有可能在一个地方是尾递归的,但在另一个地方却不是。

在表达式 let pat = exprA in exprB 中,只有 exprB 位于尾部位置。也就是说,虽然您可以去评估 exprA,但您仍然必须回来评估 exprB 并牢记 exprA。对于该语言中的每个表达式,都有一个归约规则告诉您尾部位置在哪里。在ExprA中; ExprB 又是ExprB。在 if ExprA then ExprB else ExprC 中,它既是 ExprB 又是 ExprC 等等。

编译器当然知道这一点。然而,F# 中提供了许多可用的表达式,以及编译器在运行过程中执行的许多内部优化,例如在模式匹配编译期间、seq{}async{} 等计算表达式> 可以使知道哪些表达式位于尾部位置变得不明显。

实际上,通过一些实践,小函数很容易通过查看嵌套表达式并检查不在函数调用尾部位置的槽来确定尾部调用。 (请记住,尾部调用可能是另一个函数!)

Yes, you can formally prove that a function is tail-recursive. Every expression reduction has a tail-position, and if all recursions are in tail-positions then the function is tail-recursive. It's possible for a function to be tail-recursive in one place, but not in another.

In the expression let pat = exprA in exprB only exprB is in tail-position. That is, while you can go evaluate exprA, you still have to come back to evaluate exprB with exprA in mind. For every expression in the language, there's a reduction rule that tells you where the tail position is. In ExprA; ExprB it's ExprB again. In if ExprA then ExprB else ExprC it's both ExprB and ExprC and so on.

The compiler of course knows this as it goes. However the many expressions available in F# and the many internal optimizations carried out by the compiler as it goes, e.g. during pattern match compiling, computation expressions like seq{} or async{} can make knowing which expressions are in tail-position non-obvious.

Practically speaking, with some practice it's easy for small functions to determine a tail call by just looking at your nested expressions and checking the slots which are NOT in tail positions for function calls. (Remember that a tail call may be to another function!)

情绪操控生活 2024-11-10 17:04:07

你问我们如何正式检查这一点,以便我尝试一下。我们首先必须定义函数尾递归的含义。 该形式的递归函数定义

let rec f x_1 ... x_n = e

如果 e 内对 f 的所有调用都是尾调用,则 是尾递归的 - 即。发生在尾部上下文中。 尾部上下文 C 被归纳地定义为带有空洞 [] 的术语:

C ::= []
    | e
    | let p = e in C
    | e; C
    | match e with p_1 -> C | ... | p_n -> C
    | if e then C else C

其中 e 是 F# 表达式,x 是一个变量,p 是一个模式。我们应该将其扩展为相互递归函数定义,但我将其作为练习。

现在让我们将其应用到您的示例中。函数体中对 rec_algo1 的唯一调用是在此上下文中:

if step = dSs then J
else
    let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
    let argmin = a|> Array.minBy snd |> fst
    []

并且由于这是尾上下文,因此该函数是尾递归的。这就是函数式程序员观察它的方式 - 扫描定义的主体以查找递归调用,然后验证每个调用是否出现在尾部上下文中。尾部调用的一个更直观的定义是,除了返回调用结果之外,没有对调用结果执行任何其他操作。

You asked how we can formally check this so I'll have a stab. We first have to define what it means for a function to be tail-recursive. A recursive function definition of the form

let rec f x_1 ... x_n = e

is tail recursive if all calls of f inside e are tail calls - ie. occur in a tail context. A tail context C is defined inductively as a term with a hole []:

C ::= []
    | e
    | let p = e in C
    | e; C
    | match e with p_1 -> C | ... | p_n -> C
    | if e then C else C

where e is an F# expression, x is a variable and p is a pattern. We ought to expand this to mutually recursive function definitions but I'll leave that as an exercise.

Lets now apply this to your example. The only call to rec_algo1 in the body of the function is in this context:

if step = dSs then J
else
    let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
    let argmin = a|> Array.minBy snd |> fst
    []

And since this is a tail context, the function is tail-recursive. This is how functional programmers eyeball it - scan the body of the definition for recursive calls and then verify that each occurs in a tail context. A more intuitive definition of a tail call is when nothing else is done with the result of the call apart from returning it.

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