Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monadic buffers in OCaml

Tags:

monads

ocaml

When using Buffers.t in a function f, I often use the following pattern:

  1. Create a fresh buffer or empty a previously existing buffer.
  2. Pass that buffer to a function which fills it.
  3. Extract the result, empty the buffer and return the result.

How can I encode this pattern in a monad?

like image 855
Tim Enghel Avatar asked Sep 11 '26 04:09

Tim Enghel


1 Answers

Implementation of monadic buffers

This is our implementation of monadic buffers. It has the typical signature of a monad, the type α MonadicBuffer.t represents an ongoing-computation yielding a value of type α which can write to a buffer as a side effect. A unit MonadicBuffer.t then represents a completed computation. The monadic functions add_string and add_char can be used to write in the monadic buffer and contents encodes the pattern you described.

module MonadicBuffer : sig
  type 'a t
  val return : 'a -> 'a t
  val bind : 'a t -> ('a -> 'b t) -> 'b t
  val add_char : char -> unit t
  val add_string : string -> unit t
  val contents : int -> unit t -> string
  val lift : ('a -> 'b) -> ('a t -> 'b t)
  val ( >>= ) :'a t -> ('a -> 'b t) -> 'b t
  val ( >> ) : 'a t -> 'b t -> 'b t
end = struct

  type 'a t = Buffer.t -> 'a

  let return x =
    fun _ -> x

  let bind m f =
    fun buf -> f (m buf) buf

  let add_string s buf =
    Buffer.add_string buf s

  let add_char c buf =
    Buffer.add_char buf c

  let contents sz m =
    let buf = Buffer.create sz in
    m buf;
    Buffer.contents buf

  let lift f m =
    bind m (fun x -> return (f x))

  let ( >>= ) =
    bind

  let ( >> ) m1 m2 =
    bind m1 (fun _ -> m2)
end

Monadic conversion of alists to string

As an example, let us implement the conversion of alists to string in that monad:

let rec add_alist lst =
  let open MonadicBuffer in
  match lst with
    | [] -> return ()
    | (k,v) :: tl -> add_key k v >> add_alist tl
and add_key k v =
  let open MonadicBuffer in
  return ()
  >> add_string k
  >> add_string ": "
  >> add_string v
  >> add_char '\n'

Higher-order conversion of alists to string

It is interessant to compare this version with the classical higher-order solution:

let rec buffer_add_alist buf alist =
  match alist with
    | [] -> ()
    | (k,v) :: tl -> buffer_add_key buf k v; buffer_add_alist buf tl
and buffer_add_key buf k v =
  let open Buffer in
  add_string buf k;
  add_string buf ": ";
  add_string buf v;
  add_char buf '\n'

Your pattern is then enforced by the with_buffer higher-order function:

let with_buffer sz f x =
  let buf = Buffer.create sz in
  f buf x;
  Buffer.contents buf

At the code level, the main difference is that the monadic version does not need to pass the buf variable all around. At the performance level, the advantage goes to the higher-order version. For what is worth, the following plot represents the execution time of each method against the size of the processed alist (each pair being the same, all over the place): Time plot This displays two very noisy structure, however a linear trend in each series is clearly distinguishable, with the monadic cas having the highest slope.

like image 166
Michaël Le Barbier Avatar answered Sep 14 '26 19:09

Michaël Le Barbier