OCaml中的副作用和顶层表达式

5

我遇到了Ocaml编程方面的困难。

我希望创建一个函数,每次调用时都会使我的计数器加一,并将我的变量字符串与计数器编号连接起来,返回这个新的字符串。

我尝试过以下方法,但并没有成功:

let (counter : int ref) = ref 0;;
let (vargen : string) = "_t";;
let tmp = incr counter;ref (vargen ^ string_of_int !counter);;
Printf.printf "%s\n" !tmp;;
Printf.printf "%s\n" !tmp;;
Printf.printf "%s\n" !tmp;;
Printf.printf "%s\n" !tmp;;

但我的输出总是:
_t1
_t1
_t1
_t1

我应该输出什么:


    _t0
    _t1
    _t2
    _t3

有没有什么想法来解决我的问题啊?
谢谢大家。
4个回答

8
当您编写代码 let tmp = ref foo 时,表达式 foo 会被评估一次,产生一个存储在引用中的值。访问该引用将返回此值,而不重新评估原始表达式。
触发重新评估的方法是使用函数:如果您编写一个函数 (fun () -> foo),它是一个值:它按原样返回,传递给函数,存储在引用中。每次将参数应用于此值时,表达式 foo 都会被评估。
克莱门特的解决方案很好。思路是:
let counter =
  let count = ref (-1) in
  fun () -> incr count; !count

参考值只分配一次,但每次调用函数fun() -> incr count; !count时会递增。将参考值局限于函数可以避免全局变量的某些陷阱。你可以将其视为函数counter的“静态变量”,只不过它是OCaml作用域和评估规则的自然结果,而不是额外的、函数特定的概念。
甚至可以编写一个更通用的vargen生成器,每次调用时创建独立的新计数器:
let make_vargen prefix =
   let count = ref (-1) in
   fun () ->
     incr count;
     prefix ^ string_of_int !count

let fresh_t = make_vargen "t"
let () = print_endline (fresh_t ())  (* t0 *)
let () = print_endline (fresh_t ())  (* t1 *)
let fresh_u = make_vargen "u"
let () = print_endline (fresh_u ())  (* u0 *)
let () = print_endline (fresh_t ())  (* t2 *)
let () = print_endline (fresh_u ())  (* u1 *)

非常好的解释,现在我明白为什么我的程序不工作了。 - tsukanomon

3

因为 tmp 是一个值,它只会执行一次。将其改为函数应该会使计数器在每次调用 tmp 时增加。

此外,出于简单起见,您可以返回 string 而不是 string ref

let counter: int ref = ref (-1);;
let vargen: string = "_t";;

// tmp now is a function
let tmp() = incr counter; vargen ^ string_of_int !counter;;

Printf.printf "%s\n" (tmp());;
Printf.printf "%s\n" (tmp());;
Printf.printf "%s\n" (tmp());;
Printf.printf "%s\n" (tmp());;

非常好,这个解决方案完美地适用于我的问题,解决了我还有一些其他的问题。 - tsukanomon

3
你可以使用:

let tmp = 
  let counter = ref 0 in
  (fun () -> incr counter; vargen ^ (string_of_int !counter))

使用tmp ()调用函数,并将0更改为-1,如果您希望计数器从0开始。


非常紧凑的填充解决方案形式,它非常有帮助。谢谢。 - tsukanomon

1
如果您需要计数器在不增加的情况下可选读取,可以添加一个参数:
let counter =
  let count = ref (-1) in
    fun do_incr -> if do_incr then begin incr count end; !count;;

使用方法如下:
# counter true;;
- : int = 0
# counter true;;
- : int = 1
# counter true;;
- : int = 2
# counter false;;
- : int = 2
# counter false;;
- : int = 2
# counter true;;
- : int = 3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接