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

I want to write code that builds a thing using some local state. For example, consider the following code, which 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 am wondering if there is a way to make this state parameter s

inside the makeThings callback implicit, so that I don't have to type it over and over, and so it makes sure that all calls uniqueId

get passed the same prameter of state. For example, in Haskell, you can use monads and do-notation to end up with code in lines

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

      

In Ocaml, the only thing that comes to my mind is making a s

global variable (very unwanted) or trying to emulate Haskell's monadic interface, which I'm afraid will be a lot of work and end up with slow tahts code also ugly due to lack of do-notation. Is there an alternative that I haven't thought of?

+3


source to share


5 answers


A slight deviation from what you already have. Instead of using continuation, just create a function to create a new 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 general approach to handling the environment in compilers that is very similar to what you are trying to do. It does not pass state through implicitly, as OCaml does not support implicit parameters. However, if you only need one such "environment" parameter, then it is not too heavy to add it to all the corresponding functions.

+1


source


Monads work in OCaml too. You can even do the notation thanks to the pa_monad_custom syntax extension. Although in most cases just the infix binding operator is sufficient, i.e. >>=

will be enough to write fancy code.



+3


source


The code looks like a weird mixture of monadic style + reference ... If you want to restrict local states to change only in certain ways, you have to hide them in local contexts:

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

      

s

now hides in closures. This way you can create counters independently of 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 *)

      

+1


source


I guess what you want to achieve is:

  • The f

    (make_things) function that has state

    .
  • Every time you call the f

    state gets reset
  • But during one call, the f

    state can automatically change

If I'm right, we don't need Monald, we can use memo instead .

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 that we use thunk to remember the state.

More knowledge about memorization can be obtained from https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming

+1


source


It looks like you want a global variable

let currentId = ref 0

let uniqueId () = 
  incr currentId;
  !currentId

      

You are suggesting that the global variable should be undesirable, but the behavior you specified ("all calls to uniqueId receive the same state parameter") is exactly the behavior of the global variable.

If you are worried about other code accessing a global variable, just do not expose the currentId

signature ( .mli

) of your module in the file .

If you are worried about other code in the same module that you are referring to currentId

, you can limit its scope by placing it in the definition uniqueId

:

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

      

or create a submodule that does not expose currentId

in the signature:

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 (a global variable hidden by a file .mli

). It's not hard to make sure that other code within the same module isn't being abused currentId

and that the module's system protects you from code elsewhere.

0


source







All Articles