F# XLinq 遍历 - 柯里化版本的函数抛出 StackOverflowException

F# XLinq traversal - curried version of function throws StackOverflowException

我有两个类似的函数 nestednestedCurried,它们使用 XLinq 遍历 XML 树。它们没有做任何有用的事情 - 它只是从更复杂的代码中摘录的 "shrinked"。

我对这两个函数的期望是以相同的方式运行,对我来说它们看起来是相同的,唯一的区别是 nestedCurried 没有明确声明 e: XElement 参数 - 通过使用 elements 函数和函数组合 >>

同时,nestedCurried 函数在调用任何 XElement

时抛出 WhosebugException

在 FSI 中评估:

#r "System.Xml.Linq"
open System.Xml.Linq

let inline elements (e: XElement) = e.Elements() |> Seq.toList

let rec nested () e = elements e |> List.collect (nested ())
let rec nestedCurried () = elements >> List.collect (nestedCurried ())

let x = XDocument.Parse """<a></a>"""

let ok : XElement list = nested () (x.Root)
// Stack Overflow below
let boom : XElement list = nestedCurried () (x.Root)

为什么会出现 WhosebugException,这两个函数之间的技术区别是什么,以及如何在不明确指定 XElement 参数的情况下声明 nested 函数?

您的 () 参数不是必需的,而且会造成混淆。您使用 X.Root 部分应用了元素,因此您一遍又一遍地使用 X.Root 调用 nestedCurried - 因此堆栈溢出。要在不明确指定参数的情况下声明嵌套,您可以这样做:

let nested = 
  let rec inner e = elements e |> List.collect (inner)
  inner

如果您将 nestedCurried 声明为

let rec nestedCurried = elements >> List.collect (nestedCurried)

你会得到一个编译器错误 "nestedCurried is evaluated as part of its own definition"。

看:每次调用 nestedCurried 时,都会立即无条件地再次调用 nestedCurried

为了让事情更清楚一点,考虑表达式 List.collect f 等同于 let x = f; List.collect x。这意味着您对 nestedCurried 的定义等同于:

let nestedCurried () =
  let x = nestedCurried()
  elements >> List.collect x

现在是否更清楚为什么这会导致无限递归?