Search code examples
functional-programmingocamlstate-monad

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. For example, in Haskell you could use monads and do-notation to end up with code along the lines of

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. Is there an alternative I haven't thought of?


Solution

  • 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. However if you only require a single such "environment" parameter then it is not too onerous to add it to all the appropriate functions.