【问题标题】:OCaml: inner "let" for two functionsOCaml:两个函数的内部“让”
【发布时间】:2018-10-29 00:38:58
【问题描述】:

我想做内部的“让”,但有两个功能。

我现在有一个函数

let fresh_var () =
let r = ref 0 in
r := !r + 1 ; Var !r;;

我想添加第二个函数,以便它可以更改 r,但 r 对于程序的其余部分保持不可见。类似的东西:

let r = ref 0 in 
let fresh_var () = r := !r + 1 ; Var !r
and let refresh () = r := 0

但由于语法错误,上面的部分不起作用。

如何在 OCaml 中实现这个想法?

【问题讨论】:

    标签: ocaml let


    【解决方案1】:

    当您要编写联合声明时,使用and 代替let,而in 仅在本地上下文中使用:

    let r = ref 0
    let fresh_var () = r := !r + 1 ; Var !r
    and refresh () = r := 0
    

    但是,由于您的函数不是相互依赖的,因此这里不需要使用 and,因此您可以使用另一个 let 构造。

    至于你的想法,你必须在一个单独的模块中定义这些函数,其.mli 只声明它们而不是变量r

    【讨论】:

    • 这并没有解决 OP 的要求,即“r 在程序的其余部分保持不可见”
    • 如果.mli 没有声明r,它将在程序的其余部分不可见。
    • 除非所述程序包含在单个模块中,或者甚至包含要提供给解释器的脚本。
    • 这是真的,但我不明白你是如何推断这是一个约束?
    • 我不知道在这种特殊情况下这是否是一个约束。我的观点与 Vonaka 在他们的回答下的评论基本相同:对于这样一个简单的情况,定义一对共享局部变量的函数比使用整个 module.mli 路由要容易得多。
    【解决方案2】:

    这不是 OCaml 中的工作方式。 Local 定义就是这样,本地的。它们不能在函数之间共享。

    如果您想抽象部分实现,我建议改用模块。

    module Incrementer : sig
      val next : unit -> int
      val reset : unit -> unit
    end = struct
      let r = ref 0
    
      let next () =
        r := !r + 1;
        !r
    
      let reset () =
        r := 0
    end
    

    在下面查看它的实际效果:

    # Incrementer.next ();;
    - : int = 1
    
    # Incrementer.next ();;
    - : int = 2
    
    # Incrementer.reset ();;
    - : unit = ()
    
    # Incrementer.next ();;
    - : int = 1
    
    # Incrementer.r;;
    Error: Unbound value Incrementer.r
    

    下面是一个更好的实现,它允许你同时拥有多个Incrementers

    module Incrementer : sig
      type t
      val create : unit -> t
      val next : t -> int
      val reset : t -> unit
    end = struct
      type t = int ref
    
      let create () =
        ref 0
    
      let next t =
        t := !t + 1;
        !t
    
      let reset t =
        t := 0
    end
    

    让我们看看它的实际效果:

    # let incrementer = Incrementer.create ();;
    val incrementer : Incrementer.t = <abstr>
    (* As you can see, the outer code never sees the `int ref` inside. *)
    
    # Incrementer.next incrementer;;
    - : int = 1
    
    # Incrementer.next incrementer;;
    - : int = 2
    
    # Incrementer.reset incrementer;;
    - : unit = ()
    
    # Incrementer.next incrementer;;
    - : int = 1
    

    您也可以将签名和实现写在单独的文件中,以便分别编译。

    【讨论】:

      【解决方案3】:

      您可以简单地创建一个返回一对函数的函数:

      let get_fresh_and_reset () =
        let r = ref 0 in
        (fun () -> incr r; !r), (fun () -> r := 0)
      
      let fresh, reset = get_fresh_and_reset ()
      

      还要注意正确的语法是:=,而不是=:

      编辑:

      正如@Virgile 提到的,如果您不需要多个计数器,您可以简化:

      let fresh_var, refresh =
          let r = ref 0 in (fun () -> incr r; !r), (fun () -> r:=0)
      

      【讨论】:

      • 这可以让你定义几个独立的计数器,但如果确实只有一个,你可以直接做let fresh_var, refresh = let r = ref 0 in (fun () -&gt; incr r; !r), (fun () -&gt; r:=0)
      • 这感觉很骇人听闻,但为什么不呢:p 这有现实世界的应用程序,还是只是语言的一个怪癖?
      • @Richard-Degenne,我不了解现实世界,但我不觉得这是一个 hack。显然,如果您需要一些全局计数器结构,最好使用模块,但是当它只是一个小的内部计数器时,我看不出有什么不选择这个解决方案的理由。
      • 您还可以使用命名函数,使其在代码较长时更具可读性:let (fresh_var, refresh) = let r = ref 0 in let fresh_var () = incr r; !r in let refresh () = r:=0 in (fresh_var, refresh)。这种构造的更一般用途是为记录字段编写一对 getter/setter 函数(引用只是一个可变记录),以便可以将它们传递给不知道该记录的多态函数。
      【解决方案4】:

      OCaml 还支持丰富的面向对象系统

      class counter = object
        val mutable r = 0
      
        method value =
          r
      
        method incr =
          r <- r + 1;
          r
      
        method reset =
          r <- 0;
          0
      end
      

      我们可以像这样使用我们的counter

      let () =
        let c = new counter in
        printf "counter value: %d\n" c#value;   (* counter value: 0 *)
        printf "counter value: %d\n" c#incr;    (* counter value: 1 *)
        printf "counter value: %d\n" c#incr;    (* counter value: 2 *)
        printf "counter value: %d\n" c#incr;    (* counter value: 3 *)
        printf "counter value: %d\n" c#value;   (* counter value: 3 *)
        printf "counter value: %d\n" c#value;   (* counter value: 3 *)
        printf "counter value: %d\n" c#reset;   (* counter value: 0 *)
        printf "counter value: %d\n" c#incr;    (* counter value: 1 *)
      

      类实例封装了它们的数据成员,因此我们可以轻松管理多个计数器

      let () =
        let a = new counter in
        let b = new counter in
        printf "A: %d, B: %d\n" a#value b#value;  (* A: 0, B: 0 *)
        printf "A: %d, B: %d\n" a#incr b#value;   (* A: 1, B: 0 *)
        printf "A: %d, B: %d\n" a#incr b#value;   (* A: 2, B: 0 *)
        printf "A: %d, B: %d\n" a#incr b#value;   (* A: 3, B: 0 *)
        printf "A: %d, B: %d\n" a#value b#incr;   (* A: 3, B: 1 *)
        printf "A: %d, B: %d\n" a#value b#incr;   (* A: 3, B: 2 *)
        printf "A: %d, B: %d\n" a#value b#incr;   (* A: 3, B: 3 *)
        printf "A: %d, B: %d\n" a#reset b#reset;  (* A: 0, B: 0 *)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-05
        • 2019-04-19
        • 1970-01-01
        • 2014-04-05
        • 1970-01-01
        • 2019-06-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多