繁体   English   中英

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

[英]Is there an idiomatic way to do implicit local state in OCaml?

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

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 *)

  ()

我很好奇,如果有一种方法,使s状态参数的内部,makeThings回调隐含的,所以我并不需要一遍又一遍,因此键入它保证所有的uniqueId电话获得通过相同的状态prameter。 例如,在Haskell中,你可以使用monad和do-notation来结束代码

makeThings $ do
  i <- uniqueId
  j <- uniqueId
  return (i + j)

在Ocaml中,我想到的唯一的事情就是使s成为一个全局变量(非常不受欢迎)或试图模拟Haskell的monadic接口,我担心这将是很多工作并最终导致缓慢的代码也很丑陋到期缺乏记号。 有没有我没想过的替代方案?

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

您的代码看起来像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 *)

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

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不支持隐式参数。 但是,如果您只需要一个这样的“环境”参数,那么将它添加到所有适当的函数并不是太繁重。

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

  1. 具有state函数f (make_things)。
  2. 每次调用f ,状态都会重置
  3. 但是在f一次调用中,状态可以自动改变

如果我是正确的,那么我们不需要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获得

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

let currentId = ref 0

let uniqueId () = 
  incr currentId;
  !currentId

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

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

如果您担心访问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并且模块系统保护您免受其他地方的代码的currentId并不困难。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM