4

I am developing a library where several state monads are commonly mixed. What kind of state is mixed in is not known a-priori, but will likely be defined at the application level. Therefore, my solution is to develop one state monad that has an extensible hidden state.

(** ObjectStateMonad for composable State Monads *)
module ObjectStateMonad =
  struct
    (* A state monad yields tuple of a state-object and an observable value *)
    type ('a, 'b) monad = 'a -> ('a * 'b)

    (* usual bind, just more type parameters *)
    let bind : (('a, 'b) monad) -> ('b -> ('a, 'c) monad) -> ('a, 'c) monad = 
      fun m -> 
      fun f ->
      fun s ->
        let (st, obs) = m(s) in
        ( (f obs) st)

    (* run, as usual *)
    let run m a = m(a)

    type ('a, 'b) field = { field_get : 'a -> 'b ; field_set : 'a -> 'b -> 'a }

    (* get does not directly expose the state but requires a "getter" *)
    let get f = 
      let m : 'a -> ('a * 'b) = fun s -> (s, f.field_get s)
      in m

    (* put requires a "setter" function to modify the state *)
    let put f = 
      fun b ->
      let m : 'a -> ('a * unit) = fun s -> 
    let s2 : 'a = (f.field_set s b) in (s2, ()) 
      in m

    let yield a = fun s -> (s, a)

    let return = yield

    let rec repeat m = function
      | 0 -> m
      | n -> bind m (fun _ -> repeat m (n - 1))       

  end

My implementation uses row-polymorphism to achieve the extensibility:

module FooState = struct
  open ObjectStateMonad

  type state_t = int

  class state_container = object
    val _foo : state_t = 0
    method get_foo = _foo
    method set_foo n = {< _foo = n >} 
  end

  let field = { field_get = (fun a -> (a#get_foo : state_t)) ; field_set = fun a b -> a#set_foo b }

  (* just an example operation *)
  let increment s = ( 
    perform n <-- get field ; 
    _ <-- put field (n+1); 
    return n 
  ) s

end

The module above demonstrates how composability works: Create a class that inherits from all relevant state containers, instantiate that class and run the operations on them.

My problem is that due to the value-restriction in OCaml's polymorphism, I cannot use partial application on that state monad (which is just a function), hence I always have to make the application explicit (parameter s in increment). Now that I am using the pa_monad syntax extension, it should be possible to automatically add this on every occurrence on perform, shouldn't it?

In other words: Can I use pa_monad to always do a η-expansion on certain functions?

Any other solution to the problem is also appreciated.

choeger
  • 3,562
  • 20
  • 33

1 Answers1

0

You're right about pa_monad; it will allow you to elide the s parameter whenever you use increment within a perform block. For example,

open ObjectStateMonad
open FooState

let state, result =
  (perform
    _ <-- increment;
    _ <-- increment;
    a <-- increment;
    return a) (new state_container)
in
print_int state#get_foo ; print_char ':' ; print_int result

prints out 3:2 as expected.

What's more, you don't even need the s parameter in your definition of increment. An η-reduced version:

let increment = 
  perform n <-- get field ; 
  _ <-- put field (n+1); 
  return n

works just as well.

aaronsgh
  • 3
  • 4