@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.)