When using Buffers.t in a function f, I often use the following pattern:
How can I encode this pattern in a monad?
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
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'
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):
This displays two very noisy structure, however a linear trend in each series is clearly distinguishable, with the monadic cas having the highest slope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With