resource
A host’s thing held by a with, the same for every effect: an open
file, a socket, a process, a canvas — what must be let go of. Each
has its own with: withProcess, Net.withConnection, std.file’s
with f := File.open path do, and so on. In a do block a with is
a statement, with withProcess c as p do, the statements under it its
body. Each one:
- acquires the thing;
- runs the body with it (
scoped’s, aWith s rin a regionsof its own); - lets go of it when the body ends;
- comes to what the body came to.
The thing lives as long as the program can reach it, in the custody
of a custodian (the custodian module): the one whose with of a
custodian the with is in, or the root’s.
Letting go of it, a with ends it there where it held it alone; where
the body gave it to another — a thread spawned in the body, given the
With s r by value — it is ended once the last holder lets go of it,
the with not waiting. That is scoped’s policy. A thing let go of
by a computation of the program’s, a held’s, has the other: it is
let go of as the block is left, whoever holds it still.
import coimport exceptionimport std.process
ran : {e : HFunctor} -> {{sys : process <: e}} -> {{fx : exception ProcessError <: e}} -> Invocation -> Co e Ranran c = do with withProcess c as p do wait pThe body is polymorphic in s, and what it comes to is of a type bound
outside it, so a With s r cannot leave the body:
withProcess c (\p -> val p) is a type error (E0311).
There is one With for every effect, so a with of one effect may
hand its thing to an operation of another by value — a
<canvas> found by browser.dom, drawn on by browser.gpu — and neither
keeps it.
A with comes to what its body came to, and nothing else: a thing not
acquired is no answer of the body’s, so it is thrown, over the
exception effect, the body not run — a with asks for exception err besides its own effect, and try can catch it. A release that
fails is no answer of the body’s either, nor the caller’s to catch: it
is a failure of the thing’s custodian’s, whether the with ended the
thing or its last holder did, later — the custodian shut down, and
the failure passed up, at the root the program’s, said, and the
program ended. So a with asks for custodian too.
held is the with of a thing that is always had and let go of by
a computation — a port of a standard stream, a handle on the page’s
document — which throws nothing; its release is kept by the
program’s top level meanwhile, and runs however the block is left.
The thing is the host’s own, a foreign value with a finalizer, so a
body left otherwise — a throw heard outside it, a handler between that
drops the continuation — lets go of it where the program drops its
last reference to it; scoped adds nothing for that.
With is exposed without its tag: no program makes one, and scoped acquire release body and held acquire release body are the only ways
to make one in a region of its own; beside w x has x in the region
w’s thing is had in, as std.net’s accepted has a connection in its
listener’s. A library wraps one around its own operations acquiring
and releasing the thing, as std.process does:
withProcess : {e : HFunctor} -> {a : Type} -> {{ev : process <: e}} -> {{ex : exception ProcessError <: e}} -> Invocation -> ({s : Type} -> With s Started -> Co e a) -> Co e awithProcess c body = scoped (op (ev (Start (spawnOf c) (\s -> s))) val) (\s -> op (ev (Release s (\r -> r))) val) body
wait : {e : HFunctor} -> {s : Type} -> {{ev : process <: e}} -> With s Started -> Co e Ranwait p = op (ev (Wait (inside p) (\r -> r))) val- Its operations take the
With s a, and give the thing to a provider operation byinside, which takes it out for that operation and nothing else. - It may export its bare acquire and release too, as std.file does
(
open,close; itswithof a binding,with f := File.open path do, isscopedaround them, its body given the file itself): where the provider lets go of a thing once, an operation on it after is the effect’s error (a file’s,EBADF), never one on a thing acquired since, and its operations then take the thing itself. - What lives as long as the program, such as a GPU device, is held by a
withat the top ofmain.
The provider’s side, how it opens and closes the thing, is its scoped operations.
With
data With s aA host’s thing, held in the region s by a with (scoped,
held).
inside
inside : {s a : Type} -> With s a -> aThe thing a with holds, for a provider operation given it: a
library’s operation on a With s a takes it out here and gives it
to the operation, and nothing else does.
beside
beside : {s a b : Type} -> With s a -> b -> With s bA thing had in the region s that w’s thing is had in: for a
thing a library gets by another’s — a connection by its listener
— which lives no longer than that one’s with, and is let go of
by the library’s own operation, or by the program dropping it.
scoped
scoped : {e : HFunctor} -> {err r a : Type} -> {{ex : exception err <: e}} -> {{cu : Custodian.custodian <: e}} -> {{sh : Show err}} -> Co e (Either err r) -> (r -> Co e (Either err Unit)) -> ({s : Type} -> With s r -> Co e a) -> Co e aThe thing acquire gives, held by body in a region of its own —
it is not kept past it — and let go of by release when body
ends: what it came to. release is the with letting go of its
hold, the provider’s to say: it ends the thing where the with held
it alone, and else leaves it to the last holder, ended as that one
lets go of it — a thread the body gave it to. The thing is in the
custody of the custodian whose custody the with is in (the
custodian module’s custody; the root’s, outside every one). A
thing not acquired is thrown, the body not run. One not released,
whenever it is ended — release coming to a Left, or its
provider failing to end it later — is that custodian’s failure,
said as show says the Left: never thrown to the caller. Should
body be left otherwise — a throw heard outside it, a handler
between that drops the continuation — the thing is let go of where
the program drops its last reference to it, as its provider says.
held
held : {e : HFunctor} -> {r a : Type} -> {{cu : Custodian.custodian <: e}} -> Co e r -> (r -> Co e Unit) -> ({s : Type} -> With s r -> Co e a) -> Co e aThe thing acquire gives, which it always does, held by body in
a region of its own and let go of by release as the block is
left, whoever holds it still: what the body came to. The release
is a computation, kept meanwhile by the program’s top level (the
custodian’s kept): where the block is left otherwise — a throw
heard outside it, a handler between that drops the continuation —
the runtime runs it once that is known, at the next operation of
the domain that kept it (C), or as the collector comes to it
(JavaScript). The with of a port of a standard stream, or of the
page’s document.
had
had : {e : HFunctor} -> {r a : Type} -> r -> ({s : Type} -> With s r -> Co e a) -> Co e ax, held by body in a region of its own, nothing done as it
ends: for a library whose thing is let go of otherwise, after the
body, by an operation of its own.