Eio 0.1 - effects-based direct-style IO for OCaml 5

@gasche > Are you suggesting adding a Yield effect to the stdlib that Lazy.force could perform? On the default runtime, this would block the domain until resolution but a library like eio could instead switch to another fibre?

Reading this thread, one gets the impression that the design is horrible… but reading the API documentation, it actually looks really good! I believe the explicit capabilities will make it a lot easier to test and mock side effects.

The use of objects is surprising, but in practice they require a lot less syntax than first class modules or records when it comes to subtyping (to remove capabilities).

val ftp : < net : Net.t ; cwd : Dir.t ; ..> -> unit

So for example, this ftp function would accept the default env : Eio.Stdenv.t without any annotation. The Dream clock example could just do (render ~env), since render will infer its argument as env : < clock ; ..> if it’s only used for sleeping. The two_way pipes is also a compelling use-case to reduce the API surface.

I was a bit confused by the semi-pervasive use of Switch, because I thought the name implied that they would be used for early cancellation, when in practice functions like open_in actually requires them to ensure deferred clean up? I guess switches will only show up when something fancy is going on with the resources… which is the case if you only use the structured with_stuff helpers, so they should be the recommended default in the documentation? (introduced before the low-level switch version? like Fibre does)

Should Promise.create accept an optional switch to guard against a never resolved bug due to a cancellation?

Regarding capabilities and effects, I expect a similar story to exceptions to play out where best practices evolve from the unchecked default to something more explicit:

val find : key -> t -> value (** @raise Not_found *)

val find_opt : key -> t -> value option

Yet even the later has disadvantages and doesn’t scale to effects system. We could have instead chosen to give permission to the function to fail:

val find_exn : key -> t -> exn -> value

There’s some precedent in dependently typed languages, where exn would be a proof of mem key t usable in the absurd case that the key is missing. This presumes that exceptions and effects are not globally available, but rather are obtained by setting up a match ... with exception handler (which is reminiscent of Donald Knuth’s event indicators in “Structured Programming with goto Statements” from 1974!)

Of course, this only makes sense if these capabilities can’t escape their scope. I would really like something like Scala’s capture checking linked by @0xa2c2a, it looks a lot more versatile than the typed effects arrows (and wouldn’t -[fs: Dir.t; ..]-> have a terrifying impact on libraries like Dream?)

(Anyway, perhaps eio is hiding effect handlers too much at a time where people really want to play with them. The global ref escape hatch “feels” bad even if it’s easy to setup.)

3 Likes