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?
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.