An effect to await-ify any monad

This is probably not very useful in practice, but I thought that some people may appreciate seeing this. Similar to how effects can create a generator out of any iter function, they can also create an await out of any monad.

module type MONAD = sig
  type 'a t

  val return : 'a -> 'a t
  val bind : 'a t -> ('a -> 'b t) -> 'b t
end

module MakeAwait (M : MONAD) = struct
  type _ Effect.t += Await : 'a M.t -> 'a Effect.t

  let await m = Effect.perform (Await m)

  let run f =
    match f () with
    | x -> x
    | effect Await m, k -> M.bind m (Effect.Deep.continue k)
end

I’m not sure if this is generally advisable over the tried-and-true let* syntax, but is still a fun trick. Its benefits and tradeoffs are different compared to let*, so perhaps it fills a need for someone. Example usage:

module AResult = MakeAwait (struct
  type 'a t = ('a, exn) Result.t

  let return = Result.ok
  let bind = Result.bind
end)

let hello_world =
  AResult.run (fun () ->
      let s1 = AResult.await (Ok "hello") in
      let s2 = AResult.await (Ok "world") in
      Ok (s1 ^ s2))

The only time I’ve felt a desire to use a pattern like this was when writing JSOO code that interfaced with external JavaScript promises, and I wanted to enjoy JS-style await. (Yes, I know JS promises themselves are not type-safe.)

open Js_of_ocaml

type 'a promise
type _ Effect.t += Await : 'a promise Js.t -> 'a Effect.t

let await p = Effect.perform (Await p)

let run f =
  match f () with
  | x -> Js.Unsafe.global##._Promise##resolve x
  | exception x -> Js.Unsafe.global##._Promise##reject x
  | effect Await p, k ->
      Js.Unsafe.meth_call p "then"
        [|
          Js.Unsafe.inject (Js.wrap_callback (Effect.Deep.continue k));
          Js.Unsafe.inject (Js.wrap_callback (Effect.Deep.discontinue k));
        |]
3 Likes

This idea has been known in the literature as monadic reflection. And indeed you can apply this to any monad. See effects-examples/reify_reflect.ml at master · ocaml-multicore/effects-examples · GitHub.

5 Likes

Ah, thanks! I’m not familiar with the literature, and I hadn’t seen this in the wild before. Good to know what term I should have been searching for. :slightly_smiling_face:

1 Like

Well, any monad that only runs the continuation once! OCaml continuations are one-shot, so monads that retry the binder more than once will raise Effect.Continuation_already_resumed.

2 Likes