4

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?

hugomg
  • 68,213
  • 24
  • 160
  • 246
  • Your question is a bit not clearly, espcially you mentioned `in your mind making s a global variable`, but in your comment to Leo White you said you don't want the state change globally. I guess what you wish to achieve is: `1. A function f (make_things) which has a state. 2. Everytime you call f, the state gets reset 3. But during inside one call of f, the state can be automatically change. ` Is my guess correct? – Jackson Tale Oct 07 '14 at 16:00

5 Answers5

3

Monads work in OCaml too. You can even have a do-notation, thanks to pa_monad_custom syntax extension. Although, in most cases having just an infix bind operator, i.e. >>= will be enough to write a fancy code.

ivg
  • 34,431
  • 2
  • 35
  • 63
  • 1
    But do people use monads in practice for this kind of code? If you can think of some project that does this a link to it would make my life much easier :) – hugomg Oct 05 '14 at 00:11
  • Usually we use monads for IO. I've personally never seen a state monad in a production code. My personal preferences would be to pass state explicitly, and return it when it is changed. – ivg Oct 05 '14 at 01:59
1

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:

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

s is now hidden in closures. 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 *)
camlspotter
  • 8,990
  • 23
  • 27
  • If makeBody's callback calls other functions then its going to need to pass `unique_id` to them, and we are back to were we started... The impression I'm getting is that this just encapsulates the state in an OO pattern (pros and cons to that) but doesn't solve the original "threading the state around" problem I had. As for the weird references+monadic style, thats why I'm asking the question :) I think using mutable state is clearer than using fake state via monads but I'm new to ocaml so what do I know. – hugomg Oct 05 '14 at 01:29
  • If you want to prevent one `unique_id` function from being misused more than one place, define a higher order function like `with_unique_id : ((unit -> int) -> 'a) -> 'a` whose argument takes a fresh function made by `make_unique_id`. BTW, if you want the pure solution of the state monad, you should keep things pure. Having a reference in the monadic state is very confusing. – camlspotter Oct 07 '14 at 02:33
  • Is using reference that bad even if I make the state an abstract type, as suggested in Leo White's answer? – hugomg Oct 07 '14 at 02:42
  • It's ok. My point is that if you want to use state monad, you can use it in OCaml too, and you do not need to use ref at all. I agree with @ivg about the use of state monad in OCaml: I use it sometimes but so rare that I do not feel do notation. – camlspotter Oct 07 '14 at 02:58
1

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.

Leo White
  • 1,131
  • 5
  • 9
1

I guess what you wish to achieve is:

  1. A function f (make_things) which has a state.
  2. Everytime you call f, the state gets reset
  3. But during inside one call of f, the state can be automatically change

If I am correct, then we don't need Monald, instead, we can use memo.

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.

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

Jackson Tale
  • 25,428
  • 34
  • 149
  • 271
0

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.

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.

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:

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

or create a sub-module which 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 (global variable hidden by the .mli file). 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.

Leo White
  • 1,131
  • 5
  • 9
  • I'm not sure this does exactly what I want. Each call to `makeThings` should start generating IDs from 0 instead of the IDs being a globally increasing number (thats because in my real use case the state is a list of things that needs to be cleared between makeThings calls). Also, if I encapsulate the state using a module, is there a way to instantiate two instances of the same module or will I be limited to a single statefull instance? – hugomg Oct 06 '14 at 13:54
  • You can use a module to encapsulate state with multiple instances using a functor or first-class module. But it won't gain you anything in this case, you might as well just pass around `updateId`. – Leo White Oct 06 '14 at 20:32
  • I've added a different answer that supports multiple instances, although it requires you to explicitly pass around a state parameter. Since OCaml does not support implicit parameters, you can't really avoid this. – Leo White Oct 06 '14 at 20:54