【问题标题】:Is the following function tail recursive?以下函数尾递归吗?
【发布时间】:2023-03-25 09:00:01
【问题描述】:

我有一个函数,其中星号线是涉及递归调用的连词。当连词起作用时,如果h1 <> h2 则不会进行递归调用。但是如果调用了,那么编译器是否仍会回溯并在true 值上执行一大堆连词?还是会省略这个不必要的步骤?

换句话说,以下函数是否有效地尾递归?

let isExtensionOf<'A when 'A : equality> (lst1 : list<'A>) (lst2 : list<'A>) : bool =
    let rec helper (currLst1 : list<'A>) (currLst2 : list<'A>) : bool =
        match currLst1, currLst2 with
        | h1 :: _, [] -> false
        | [], _ -> true
        | h1 :: t1, h2 :: t2 -> (h1 = h2) && (helper t1 t2) // *
    helper lst1 lst2

是的,我知道星号线应该写成if h1 = h2 then helper t1 t2 else false。但我只是好奇。

提前致谢。

【问题讨论】:

  • 回答这类问题的最简单方法是:输入一个你认为会引发病态行为的巨大列表,然后看看会发生什么。
  • 我宁愿编译它,然后用 ILSpy 查看生成的代码。更可靠。另请注意,根据是否启用优化,结果可能会有所不同。
  • 嗯,它处理了1,00,000,000(一亿)的基数列表,没有眨眼,所以我只是假设我的问题可以得到肯定的回答。

标签: recursion f# tail-recursion


【解决方案1】:

判断函数是否为尾递归的另一个简单技巧是抛出异常并查看堆栈跟踪。比如可以修改helper如下:

let rec helper (currLst1 : list<'A>) (currLst2 : list<'A>) : bool =
    match currLst1, currLst2 with
    | h1 :: _, [] -> failwith "!"
    | [], _ -> failwith "!"
    | h1 :: t1, h2 :: t2 -> (h1 = h2) && (helper t1 t2)

如果您现在调用helper [1..10] [1..10],您会得到如下所示的堆栈跟踪:

System.Exception: !
在 FSI_0002.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 .$FSI_0003.main@() 由于错误而停止

但是,如果您将代码更改为非尾递归 - 例如通过修改最后一行以首先进行递归调用(helper t1 t2) &amp;&amp; (h1 = h2),然后堆栈跟踪显示所有递归调用:

系统异常:! 在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 FSI_0004.helper[A](FSharpList'1 currLst1, FSharpList'1 currLst2) 在 test.fsx:line 4
在 .$FSI_0005.main@()

【讨论】:

  • 如果我不错过某些东西,这种方法只能在发布模式下工作。在调试模式下,尾调用优化被禁用(我的意思是在属性中生成尾调用tailcheckbox)。在调试模式下,此复选框默认未选中。
【解决方案2】:

从 ILSpy 看来是这样的:

    IL_0000: nop
    IL_0001: newobj instance void class '<StartupCode$ConsoleApplication3>.$Program'/helper@10<!!A>::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldarg.1
    IL_0009: ldarg.2
    IL_000a: tail.
    IL_000c: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!A>, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!A>>::InvokeFast<bool>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
    IL_0011: ret

【讨论】:

    猜你喜欢
    • 2012-12-30
    • 1970-01-01
    • 2020-10-02
    • 2017-05-23
    • 2015-06-27
    • 2015-12-10
    • 1970-01-01
    • 2013-05-05
    相关资源
    最近更新 更多