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

如何编写函数在OCaml中创建列表的循环版本?,ocaml,letrec,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

可以使用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”的右侧不允许使用这种表达式


您的代码有两个问题:

  • result=go xs
    对于
    let rec
  • 该函数试图通过某种计算创建一个循环,该循环落入一个无限循环,导致堆栈溢出
编译器拒绝上述代码,因为您无法编写可能导致在
let rec
右侧进行递归计算的表达式(请参阅)

即使您解决了问题,您仍然有一个问题:
循环
未完成作业:

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

cycle [1;2];;
循环[1;2]
由于堆栈溢出而失败

在OCaml中,
let rec
仅当其定义为“静态”且不执行任何计算时,才可以定义循环结构
let rec xs=1::0::xs
就是这样一个例子:
(:)
不是一个函数,而是一个构造函数,它纯粹构造数据结构。另一方面,
cycle
执行一些代码执行来动态创建一个结构,它是无限的。恐怕您无法在OCaml中编写类似于
cycle
的函数


如果您想在数据中引入一些循环,比如OCaml中的
cycle
,那么您可以使用惰性结构来防止像Haskell的惰性列表这样的直接无限循环,或者使用变异通过替换来生成循环。OCaml的列表既不懒惰也不可变,因此您无法动态编写函数来构造循环列表。

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

首先,对于
编写一个函数来接收有限列表并返回无限循环版本的问题,
可以在代码/实现级别完成,只要您真正使用该函数,它就会出现堆栈溢出问题,并且永远不会返回

您尝试执行的操作的简单版本如下:

let rec circle1 xs = List.rev_append (List.rev xs) (circle1 xs)
val circle: 'a list -> 'a list = <fun>
let rec circle1 xs=List.rev_append(List.rev xs)(circle1 xs)
val circle:'列表->'列表=
它可以编译,理论上是正确的。在
[1;2;3]
上,应该生成
[1;2;3;1;2;3;2;3;…]

然而,它当然会失败,因为它的运行将是无止境的,并最终导致堆栈溢出


那么为什么
让rec circle2=1::2::3::circle2
起作用呢

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

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

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

一切都会好起来的

另外,通过这个例子,我们还可以知道一个事实,对于这样定义的无限圈列表,实际上并不需要有限的内存。它并不是生成一个真正的无限列表来消耗所有内存,相反,当一个循环结束时,它只是跳回到列表的开头


然后让我们回到
圆圈1
的示例。Circle1是一个函数,是的,它有一个地址,但我们不需要或不想要它。我们需要的是函数应用程序的地址
circle1xs
。它不像circle2,它是一个函数应用程序,这意味着我们需要计算一些东西来获得地址。所以

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


好的,那么为什么我们有时会得到
错误:这种表达式不允许作为“let rec”的右侧

let rec绑定构造,以及 递归函数,也支持某类递归函数 非功能值的定义,例如

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

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

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

如果您这样做
让rec circle=circle
,它将给出错误,因为circle不在两种允许的情况下
let rec x=let y=x in y
也不行,因为x不在构造函数或函数中


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


let rec的限制部分如果您不介意使用黑魔法,您可以尝试以下代码:

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、Jane Street的Core、电池等)使用这种被禁止的艺术。

这不是循环性问题。您不能在let rec的rhs中编写可能导致递归计算的表达式:请参阅该lin中的所有建议