简体   繁体   English

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

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

I want to write some code that builds a thing using some local state. 我想编写一些使用某种本地状态构建东西的代码。 For example, consider the following code that uses local state to generate sequential integers: 例如,请考虑以下使用本地状态生成顺序整数的代码:

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

  ()

I'm curious if there is a way to make that s state parameter inside the makeThings callback implicit, so that I don't need to type it over and over and so its guaranteed that all the uniqueId calls get passed the same state prameter. 我很好奇,如果有一种方法,使s状态参数的内部,makeThings回调隐含的,所以我并不需要一遍又一遍,因此键入它保证所有的uniqueId电话获得通过相同的状态prameter。 For example, in Haskell you could use monads and do-notation to end up with code along the lines of 例如,在Haskell中,你可以使用monad和do-notation来结束代码

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

In Ocaml, the only things that come to my mind are making s a global variable (very undesirable) or trying to emulate Haskell's monadic interface, which I fear is going to be a lot of work and end up with slow code tahts also ugly due to a lack of do-notation. 在Ocaml中,我想到的唯一的事情就是使s成为一个全局变量(非常不受欢迎)或试图模拟Haskell的monadic接口,我担心这将是很多工作并最终导致缓慢的代码也很丑陋到期缺乏记号。 Is there an alternative I haven't thought of? 有没有我没想过的替代方案?

Monads work in OCaml too. Monads也在OCaml工作。 You can even have a do-notation, thanks to pa_monad_custom syntax extension. 由于pa_monad_custom语法扩展,您甚至可以使用do-notation。 Although, in most cases having just an infix bind operator, ie >>= will be enough to write a fancy code. 虽然,在大多数情况下只有一个中缀绑定运算符,即>>=就足以编写一个花哨的代码。

You code looks like a strange mixture of monadic style + reference... If you want to restrict your local states changing only via specific ways, you should hide them in local contexts: 您的代码看起来像monadic样式+引用的奇怪混合...如果您想限制仅通过特定方式更改本地状态,您应该在本地上下文中隐藏它们:

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

s is now hidden in closures. s现在隐藏在闭包中。 Thus you can create counters independent each other: 因此,您可以创建彼此独立的计数器:

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

A slight variation on what you already have. 你已经拥有的东西略有不同。 Instead of using a continuation, just provide a function to generate a fresh state: 而不是使用延续,只需提供一个函数来生成一个新的状态:

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

This is a common approach to handling environments in compilers, which looks a lot like what you are trying to do. 这是处理编译器环境的常用方法,看起来很像你想要做的事情。 It doesn't thread the state through implicitly, since OCaml doesn't support implicit parameters. 它不会隐式地通过状态,因为OCaml不支持隐式参数。 However if you only require a single such "environment" parameter then it is not too onerous to add it to all the appropriate functions. 但是,如果您只需要一个这样的“环境”参数,那么将它添加到所有适当的函数并不是太繁重。

I guess what you wish to achieve is: 我想你想要达到的目标是:

  1. A function f (make_things) which has a state . 具有state函数f (make_things)。
  2. Everytime you call f , the state gets reset 每次调用f ,状态都会重置
  3. But during inside one call of f , the state can be automatically change 但是在f一次调用中,状态可以自动改变

If I am correct, then we don't need Monald, instead, we can use memo . 如果我是正确的,那么我们不需要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 *)

The basic idea is we use thunk to remember the state. 基本的想法是我们使用thunk来记住状态。

More knowledge about memorise can be obtained from https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming 有关记忆的更多知识可以从https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming获得

It sounds like you want a global variable 听起来你想要一个全局变量

let currentId = ref 0

let uniqueId () = 
  incr currentId;
  !currentId

You suggest that a global variable is undesirable, but the behaviour you specify ("all calls to uniqueId get passed the same state parameter") is precisely the behaviour of a global variable. 您建议全局变量是不合需要的,但您指定的行为(“对uniqueId的所有调用都传递相同的状态参数”)正是全局变量的行为。

If you are worried about other code accessing the global variable then simply do not expose currentId in the signature ( .mli file) of your module. 如果您担心其他代码访问全局变量,那么就不要在模块的签名( .mli文件)中公开currentId

If you are concerned about other code within the same module accessing currentId then you can restrict its scope by placing it within the definition of uniqueId : 如果您担心访问currentId的同一模块中的其他代码,那么您可以通过将其放在uniqueId的定义中来限制其范围:

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

or create a sub-module which does not expose currentId in the signature: 或创建一个不在签名中公开currentId的子模块:

module M : sig

  val uniqueId : unit -> int

end = struct

  let currentId = ref 0

  let uniqueId () = 
    incr currentId;
    !currentId

end

include M

Personally, I would go with the first solution (global variable hidden by the .mli file). 就个人而言,我会选择第一个解决方案( .mli文件隐藏的全局变量)。 It is not hard to ensure that the other code within the same module does not abuse currentId and the module system protects you from code elsewhere. 确保同一模块中的其他代码不会滥用currentId并且模块系统保护您免受其他地方的代码的currentId并不困难。

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

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