如何在Ocaml中实现俄罗斯娃娃图案?

ane*_*eal 5 ocaml

在Javascript中有一种称为俄罗斯娃娃模式的模式(这也可称为"一次性").基本上,它是一个在某些时候取代另一个的功能.

简单的例子:

var func = function(){ 
  func = function(){ console.log("subsequent calls call this...");};
  console.log("first call");
}
Run Code Online (Sandbox Code Playgroud)

因此,第一次调用func时它将输出"first call",然后下一个(以及随后的时间)打印"后续调用调用此...".(这在Scheme中很容易做到,例如)

我一直在困惑如何在Ocaml中做到这一点?

编辑:我提出的一个解决方案:

 let rec func = ref( fun () -> func := ( fun () -> Printf.printf("subsequent..\n"));Printf.printf("First..\n"));;
Run Code Online (Sandbox Code Playgroud)

被称为:!func();;

有趣的是,如果我没有在定义中包含'rec',它从不调用后续函数......它总是打印'First ...'.

gas*_*che 10

yzzlr答案非常好,但有两个评论:

它强制函数的输入为单位类型.您可以使用多态版本:

let doll f1 f2 =
  let rec f = ref (fun x -> f := f2; f1 x) in
  (fun x -> !f x);;
Run Code Online (Sandbox Code Playgroud)

你可以不用毛茸茸的递归:

let doll f1 f2 =
  let f = ref f1 in
  f := (fun x -> f := f2; f1 x);
  (fun x -> !f x);;
Run Code Online (Sandbox Code Playgroud)

(用变异替换递归是一种常见的技巧;它实际上可以用来定义固定点而不使用"rec")


yzz*_*zlr 9

这很简单,但你需要使用副作用.这是一个以两个thunk作为参数的函数,并返回一个新的thunk,它第一次调用第一个thunk,每隔一个时间调用第二个thunk.

let doll f1 f2 =
   let f = ref f1 in
   (fun () ->
      let g = !f in
      f := f2;
      g ())
Run Code Online (Sandbox Code Playgroud)

这不是最佳选择,因为我们将一遍又一遍地使用相同的值继续覆盖ref.

这是一个稍微好一点的版本,它使用递归定义.

let doll f1 f2 =
   let rec f = ref (fun () -> f := f2;f1 ()) in
   (fun () -> !f ())
Run Code Online (Sandbox Code Playgroud)

所以,现在,你会得到这个:

# let f = doll (fun () -> 1) (fun () -> 2);;
val f : unit -> int = <fun>
# f ();;
- : int = 1
# f ();;
- : int = 2
Run Code Online (Sandbox Code Playgroud)