是否有一种惯用的方法在OCaml中进行隐式本​​地状态?

时间:2014-10-04 23:38:19

标签: functional-programming ocaml state-monad

我想编写一些使用某种本地状态构建东西的代码。例如,请考虑以下使用本地状态生成顺序整数的代码:

type state = int ref 

let uniqueId : (state -> int) = 
fun s -> incr s; !s

let makeThings : ((state -> 'a) -> 'a) =
fun body -> body (ref 0)

let () =

  let x1 = makeThings(fun s ->
    let i = uniqueId s in     (* 1 *)
    i 
  ) in
  print_int x1; print_newline (); (* Prints 1 *)

  (* Each makeThings callback gets its own local state.
     The ids start being generated from 1 again *)
  let x2 = makeThings(fun s ->
    let i = uniqueId s in     (* 1 *)
    let j = uniqueId s in     (* 2 *)
    i + j
  ) in
  print_int x2; print_newline (); (* Prints 3 *)

  ()

我很好奇是否有办法在makeThings回调中隐式设置s状态参数,这样我就不需要反复输入它,所以它保证所有{{} 1}}调用get传递相同的状态参数。例如,在Haskell中,您可以使用monad和do-notation来结束代码

uniqueId

在Ocaml中,我想到的唯一事情就是让makeThings $ do i <- uniqueId j <- uniqueId return (i + j) 成为一个全局变量(非常不受欢迎)或试图模拟Haskell的monadic接口,我担心这将是一项很多工作并最终由于缺乏写法,慢代码也很难看。有没有我没想过的替代方案?

5 个答案:

答案 0 :(得分:3)

Monads也在OCaml工作。由于pa_monad_custom语法扩展,您甚至可以使用do-notation。虽然,在大多数情况下只有一个中缀绑定运算符,即>>=就足以编写一个奇特的代码。

答案 1 :(得分:1)

你的代码看起来像monadic风格+引用的奇怪混合...如果你想限制你的本地状态只能通过特定方式改变,你应该隐藏它们在本地环境中:

let make_unique_id init = 
  let s = ref (init - 1) (* :-) *) in
  fun () -> 
    incr s;
    !s

s现在隐藏在闭包中。因此,您可以创建彼此独立的计数器:

let () =
  let x1 =
    let unique_id = make_unique_id 1 in
    let i = unique_id () in
    i
  in
  print_int x1; print_newline (); (* Prints 1 *)

  let x2 = 
    let unique_id = make_unique_id 1 in 
    let i = unique_id () in     (* 1 *)
    let j = unique_id () in     (* 2 *)
    i + j
  in
  print_int x2; print_newline () (* Prints 3 *)

答案 2 :(得分:1)

你已经拥有的东西略有不同。不要使用延续,只需提供一个生成新状态的函数:

module State : sig

  type t

  val fresh : unit -> t

  val uniqueId : t -> int

end = struct

  type t = int ref 

  let fresh () = ref 0

  let uniqueId s = incr s; !s

end

let () =
  let x1 =
    let s = State.fresh () in
    let i = State.uniqueId s in
      i
  in
    print_int x1;
    print_newline () (* Prints 1 *)

let () =
  let x2 =
    let s = State.fresh () in
    let i = State.uniqueId s in     (* 1 *)
    let j = State.uniqueId s in     (* 2 *)
      i + j
  in
    print_int x2;
    print_newline () (* Prints 3 *)

这是处理编译器环境的常用方法,看起来很像你想要做的事情。它不会隐式地通过状态,因为OCaml不支持隐式参数。但是,如果您只需要一个这样的“环境”参数,那么将它添加到所有适当的函数中并不会太繁重。

答案 3 :(得分:1)

我想你想要达到的目标是:

  1. 具有f的函数state(make_things)。
  2. 每次拨打f时,状态都会重置
  3. 但在f的一次通话中,状态可以自动更改
  4. 如果我是正确的,那么我们不需要Monald,相反,我们可以使用备忘录

    let memo_incr_state () =
        let s = ref 0 in
        fun() -> s := !s + 1; !s
    
    let make_things f = 
        let ms = memo_incr_state() in
        f ms
    
    let f1 ms = 
        let i = ms() in
        let j = ms() in
        i+j
    
    let x1 = make_things f1 (* x1 should be 3 *)
    

    基本思路是我们用thunk来记住状态。

    有关记忆的更多知识可以从https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming

    获得

答案 4 :(得分:0)

听起来你想要一个全局变量

let currentId = ref 0

let uniqueId () = 
  incr currentId;
  !currentId

您建议全局变量是不合需要的,但您指定的行为(&#34;对uniqueId的所有调用都传递相同的状态参数&#34;)正是全局变量的行为。

如果您担心访问全局变量的其他代码,则只需在模块的签名(currentId文件)中公开.mli

如果您担心访问currentId的同一模块中的其他代码,那么您可以通过将其置于uniqueId的定义中来限制其范围:

let uniqueId =
  let currentId = ref 0 in
    fun () -> 
      incr currentId;
      !currentId

或创建一个子模块,该子模块不会在签名中公开currentId

module M : sig

  val uniqueId : unit -> int

end = struct

  let currentId = ref 0

  let uniqueId () = 
    incr currentId;
    !currentId

end

include M

就个人而言,我会选择第一个解决方案(.mli文件隐藏的全局变量)。确保同一模块中的其他代码不会滥用currentId并且模块系统保护您免受其他代码的攻击并不难。