OCaml中的匿名递归函数

时间:2017-10-13 20:52:28

标签: recursion ocaml anonymous-function

你如何制作一个匿名的递归函数(例如简单的因子n?)我听说它是​​可能的但不知道如何使它在OCaml中工作。

let a =
  fun x -> ....

我只是不知道如何继续......

3 个答案:

答案 0 :(得分:4)

以下是仅使用匿名函数的阶乘的定义:

let fact =
    (fun f -> (fun x a -> f (x x) a) (fun x a -> f (x x) a))
    (fun f n -> if n < 2 then 1 else n * f (n - 1))

需要使用-rectypes标志。

这是一个显示它有效的会话:

$ rlwrap ocaml -rectypes
        OCaml version 4.03.0

let fact =
    (fun f -> (fun x a -> f (x x) a) (fun x a -> f (x x) a))
    (fun f n -> if n < 2 then 1 else n * f (n - 1));;
val fact : int -> int = <fun>
# fact 8;;
- : int = 40320

我在这里查看Y Combinator有点欺骗:Rosetta Code: Y Combinator

<强>更新

免责声明:你最好阅读lambda演算,固定点和Y Combinator,而不是从我这里得到你的信息。我不是一个理论家,只是一个谦虚的修炼者。

按照实际计算几乎是不可能的(但绝对值得做我确定)。但在很高的层面上,这些想法都是这样的。

定义的第一行是Y Combinator,它通常计算函数的不动点。事实上,递归函数是从函数到函数的函数的固定点。

所以第一个目标是找到其固定点是阶乘函数的函数。这是定义的第二行。如果你给它一个int -> int类型的函数,它会返回另一个int -> int类型的函数。如果你给它阶乘函数,它会返回阶乘函数。这意味着阶乘函数是其固定点。

那么当你将Y Combinator应用于这个函数时,你确实得到了阶乘函数。

答案 1 :(得分:3)

让我试着对Jeffrey Scofield的答案进行一些扩展。阶乘函数的非匿名递归定义可以是

let rec fact n =
    if n < 2 then 1 else n * fact (n - 1)

当您尝试定义匿名递归函数时遇到的第一个问题是如何进行实际的递归调用(在我们的示例中为fact (n - 1))。对于呼叫,我们需要一个名称,我们没有匿名函数的名称。解决方案是使用临时名称。使用临时名称f,定义正文只是

fun n -> if n < 2 then 1 else n * f (n - 1)

此术语没有类型,因为&#34;临时名称&#34; f未绑定。但我们也可以通过绑定f将其转换为具有类型的值。我们调用结果g

let g = fun f n -> if n < 2 then 1 else n * f (n - 1)
目前

g尚未匿名,但仅仅是因为我想再次引用它。 请注意g的类型为(int -> int) -> (int -> int)。我们想要的(阶乘函数)将具有类型(int -> int)。所以g采用我们想要的类型(在这种情况下是一个函数类型)并生成相同类型的东西。直觉是g取代了阶乘函数的近似值,即函数f,它适用于所有n达到某个极限N并返回一个更好的近似值,即一个有效的函数对于所有n最多N + 1。

最后,我们需要将g转换为实际的递归定义。 这样做是一项非常通用的任务。回想一下,g可以提高近似质量。最终的因子函数fact是无法进一步改进的函数。因此,将g应用于fact应与fact相同。 (实际上,只有从价值的角度来看才是这样。g fact n中某些n固有的实际计算与fact n的实际计算不同。但返回的值是相同的。 )换句话说,factg的固定点。所以我们需要的是计算固定点的东西。

幸运的是,有一个功能就是这样:Y组合子。从价值的角度来看,Y组合子(让我们在OCaml中使用y,因为大写字母是为构造函数保留的)由y g = g (y g)对所有g的事实定义:给定一些函数g,组合器返回其中一个固定点。 因此,

y : (`a -> `a) -> `a

在我们的例子中,类型变量由(int -> int)实例化。 定义y的一种可能方法是

let y = fun g -> (fun x -> g (x x)) (fun x -> g (x x))

但这只适用于懒惰的评估(因为,我相信,Haskell有)。由于OCaml急切评估,因此在使用时会产生堆栈溢出。原因是OCaml试图将y g 8之类的内容转换为

g (y g) 8
g (g (y g)) 8
g (g (g (y g))) 8
...

没有打电话给g。 解决方案是在y

中使用延迟计算
let y = fun g -> (fun x a -> g (x x) a) (fun x a -> g (x x) a)

一个缺点是y不再适用于任意类型。它仅适用于函数类型。

y : ((`b -> `c) -> (`b -> `c)) -> (`b -> `c)

但是你要求函数的递归定义,而不是其他值的递归定义。因此,我们对阶乘函数的定义为y gyg定义如上。 yg都不是匿名的,但这可以轻松解决:

(fun g -> (fun x a -> g (x x) a) (fun x a -> g (x x) a))
    (fun f n -> if n < 2 then 1 else n * f (n - 1))

更新:

定义y仅适用于-rectypes选项。原因是我们将x应用于自身。

答案 2 :(得分:1)

还有一种不使用Y组合器即可完成匿名递归的“直观”方法。

它利用let绑定存储接受自身作为参数的lambda的值,以便它可以将自身作为第一个参数来调用自身,如下所示:

let fact = (let fact0 = (fun self n -> if n < 2 then 1 else n * self self (n - 1)) in (fun n -> fact0 fact0 n));;

仅在未用let rec定义的情况下才是匿名的。