One thing I worry about a little (if effects and their handlers become more widespread) is the complexity of composing handlers. Of course this might be a little easier to catch bugs with an effect system, but I still think it might be quite complicated for a user.
This is particularly problematic when effects are performed inside other handlers either as part of the implementation of the handler or because the effect runs a user-defined function (e.g. fork
in Eio). To take the HashDirectory
example:
module Sha = struct
type _ Effect.t += HashDirectory : Fs.dir Path.t -> string Effect.t
let hash_dir dir = Effect.perform (HashDirectory dir)
let local_handler fn =
(* This doesn't actually do the hash, just performs an effect! *)
try_with fn () {
effc = fun (type a) (e : a Effect.t) -> match e with
| HashDirectory path -> Some (fun (k : (a, _) continuation) ->
let first = Path.(path / (List.hd @@ Path.read_dir path)) in
continue k (Path.load first)
)
| _ -> None
}
end
let () =
Eio_main.run @@ fun env -> (* 1 *)
Sha.local_handler @@ fun () -> (* 2 *)
let hash1 = Sha.hash_dir env#cwd in
let hash2 =
Switch.run @@ fun sw ->
Eio.Promise.await_exn @@ Fiber.fork_promise ~sw (fun () -> Sha.hash_dir env#cwd)
in
assert (hash1 = hash2)
In this (maybe a little contrived) example there is no ordering of the handlers at 1
and 2
that allows this program to run. In the way it is currently written, the hash_dir
inside the fork
escapes the scope of the local_handler
. If you were to swap the handler around then the effects performed in the implementation of the local_handler
(by using Eio
functions) would escape the scope of the Eio handler! The programmer has to decide what kind of program they want by ordering the handlers.
I also worry about, but have very little knowledge of, how an effect system would track such a program. Will it know that the function called in fork
better not perform a HashDirectory
effect because the Eio handler is outside the scope of the local_handler
? Is that an easy thing to track? I really have no idea ^^"
But let’s also appreciate the direct-style of it all :)) I think I’m heading towards the camp of limiting my use of effects to only the bare essentials (at the moment, for async IO).