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.