如何编写函数来在OCaml中创建列表的循环版本?

时间:2014-10-20 21:50:10

标签: ocaml letrec

可以使用let rec创建无限循环列表,而无需求助于可变引用:

let rec xs = 1 :: 0 :: xs ;;

但是我可以使用相同的技术来编写一个接收有限列表的函数并返回它的无限循环版本吗?我试着写

let rec cycle xs = 
    let rec result = go xs and
            go = function
              | [] -> result
              | (y::ys) -> y :: go ys in
    result
;;

但得到以下错误

  

错误:这种表达式不允许作为“let rec”

的右侧

3 个答案:

答案 0 :(得分:4)

您的代码有两个问题:

  • result = go xs的格式为let rec
  • 的非法形式
  • 该函数尝试通过某种计算创建循环,该循环陷入无限循环,导致堆栈溢出。

编译器拒绝上面的代码,因为你不能在let rec的右边写一个可能导致递归计算的表达式(见Limitations of let rec in OCaml)。

即使您解决了问题,仍然会遇到问题:cycle无法完成工作:

let rec cycle xs =
  let rec go = function
    | [] -> go xs
    | y::ys -> y :: g ys
  in
  go xs;;

cycle [1;2];;
由于堆栈溢出,

cycle [1;2]失败。

在OCaml中,let rec只有在其定义为" static"时才能定义循环结构。并且不执行任何计算。 let rec xs = 1 :: 0 :: xs就是这样一个例子:(::)不是一个函数,而是一个构造函数,它纯粹构造了数据结构。另一方面,cycle执行一些代码执行以动态创建结构,它是无限的。我担心你不能在OCaml中编写像cycle这样的函数。

如果你想在OCaml中的cycle这样的数据中引入一些循环,你可以做的是使用惰性结构来防止像Haskell的惰性列表那样立即无限循环,或者使用变异来创建一个循环通过替代。 OCaml的列表不是懒惰也不是可变的,因此你不能动态地编写一个函数来构造循环列表。

答案 1 :(得分:3)

如果您不介意使用黑魔法,可以尝试以下代码:

let cycle l =
  if l = [] then invalid_arg "cycle" else
  let l' = List.map (fun x -> x) l in   (* copy the list *)
  let rec aux = function
    | [] -> assert false
    | [_] as lst ->   (* find the last cons cell *)
        (* and set the last pointer to the beginning of the list *)
        Obj.set_field (Obj.repr lst) 1 (Obj.repr l')
    | _::t -> aux t
  in aux l'; l'

请注意,强烈建议不要使用Obj模块。另一方面,有工业强度的节目和图书馆(Coq,简街的核心,包括电池)已知使用这种禁止的艺术。

答案 2 :(得分:2)

camlspotter的答案已经足够好了。我只想在这里补充几点。

首先,对于write a function that receives a finite list and returns an infinite, circular version of it的问题,它可以在代码/实现级别完成,只要你真的使用该函数,它就会出现stackoverflow问题并且永远不会返回。

您尝试做的简单版本是这样的:

let rec circle1 xs = List.rev_append (List.rev xs) (circle1 xs)
val circle: 'a list -> 'a list = <fun>

它可以编译,理论上也是正确的。在[1;2;3],它应该生成[1;2;3;1;2;3;1;2;3;1;2;3;...]

然而,当然,它会失败,因为它的运行将是无止境的,最终是stackoverflow。


为什么let rec circle2 = 1::2::3::circle2会起作用?

让我们看看如果你这样做将会发生什么。

首先,circle2是一个值,它是一个列表。在OCaml获取此信息后,它可以为circle2创建一个静态地址,并带有list的内存表示。

内存的实际值是1::2::3::circle2,实际上是Node (1, Node (2, Node (3, circle2))),即具有int 1的节点和具有int 2的节点的地址以及具有int 3的节点的地址和circle2的地址。但是我们已经知道了circle2的地址,对吧?所以OCaml只是把circle2的地址放在那里。

一切都会奏效。

此外,通过这个例子,我们也可以知道这样一个事实:对于这样定义的无限圆圈列表实际上并没有花费有限的内存。它没有生成一个真正的无限列表来消耗所有内存,相反,当一个圆圈结束时,它只是跳过&#34;返回&#34;到列表的头部。


让我们回到circle1的例子。 Circle1是一个函数,是的,它有一个地址,但我们不需要或不需要它。我们想要的是函数应用程序circle1 xs的地址。它不像circle2,它是一个函数应用程序,这意味着我们需要计算一些东西来获取地址。所以,

OCaml将执行List.rev xs,然后尝试获取地址circle1 xs,然后重复,重复。


好的,那么为什么我们有时会得到Error: This kind of expression is not allowed as right-hand side of 'let rec'

来自http://caml.inria.fr/pub/docs/manual-ocaml/extn.html#s%3aletrecvalues

  

let rec binding构造,除了定义   递归函数,也支持某类递归   非功能值的定义,例如

     

让expr中的name1 = 1 :: name2和name2 = 2 :: name1   将name1绑定到循环列表1 :: 2 :: 1 :: 2 :: ...,并将name2绑定到循环   list 2 :: 1 :: 2 :: 1 :: ...非正式地,接受定义的类   由定义名称仅出现的那些定义组成   在函数体内部或作为数据构造函数的参数。

如果您使用let rec来定义绑定,请说let rec name。此name只能在函数体或数据构造函数中使用。

在前两个示例中,circle1位于函数体(let rec circle1 = fun xs -> ...)中,circle2位于数据构造函数中。

如果您执行let rec circle = circle,则会出现错误,因为圈子不在两个允许的情况下。 let rec x = let y = x in y不会做任何一件事,因为再次,x不在构造函数或函数中。


这里也有一个明确的解释:

https://realworldocaml.org/v1/en/html/imperative-programming-1.html

Limitations of let rec部分