Generic control clause.
Clause that never resumes (e.g. an exception handler) (these do not need to capture a resumption and execute finally clauses upfront).
Tail-resumptive clause that does not itself invoke operations (these can be executed ‘in-place’ without setting the correct evidence vector).
Tail-resumptive clause: resumes exactly once at the end (these can be executed ‘in-place’ without capturing a resumption).
Effect handler evidence of a handler h
in the context.
Evidence equality compares the markers.
Index into an evidence vector.
The tag of a handler identifies the type at runtime (e.g. "exn/core/std"
).
Show a handler tag.
Internal effect handler primitives.
Internal primitives to implement evidence based algebraic effect handlers. These are emitted by the compiler during evidence translation and this module is always implicitly imported.
This module is compiled without monadic translation and thus we need to do this by hand in this module which allows us to implement most primitives directly in Koka keeping the external C/JavaScript/etc primitives to a minimum.
The paper:
describes precisely how the monadic evidence translation works on which this module is based. Read this first to understand how this module works.
Another paper of interest is:
which which explains the internal typing of handlers, evidence vectors, etc. in a simpler setting.
0.1. Notes
An effect row has kind
::E
, while an atomic effect kind is::X
. (We will see that::X
is equal to the kind::(E,V) -> V
) (::V
is for value kinds *)We use the term “answer” context to talk about the result type
r
and effect typee
of (the context of) the handler in the stack. Thee
does not include the effect::X
of the handler.marker<e,r>
: a unique integer corresponding to an answer context:<e,r>
. This functions as a dependent type: when the integer matches at runtime, that will be the type of the answer context.handlers
h
are partially applied types with signatureh<e,r> :: (E,V)->V
for some answer context:<e,r>
. The handlers contain all operations (much like a virtual method table). (these handler types are generated by the compiler for each effect type)Evidence
ev<h :: (E,V)->V >
for a handlerh
is an existential tupleforall e r. Ev( marker: marker<e,r>, hnd: h<e,r> )
containing the marker and the actual handler (pointer) for some answer context:<e,r>
– we don't know the answer context exact type as it depends on where the handler was dynamically bound; we just have evidence that this handlerh
exists in our context.Actually, we use a quadruple for the evidence (corresponding to the evidence as formalized in the generalized evidence paper). We also add the handler effect tag (
htagstd/core/hnd/htag: ((E, V) -> V) -> V<h>
) (for dynamic lookup), and the evidence vector of the answer context where the handler was defind (evv<e,r>
) (so we can execute operations in-place using the evidence vector at the point where they were defined).Each operation definition in a handler is called a clause. For a one argument operation, we have:
defining an operation
a -> b
for some handlerh
in an answer context:<e,r>
. (these are generated by the compiler from a handler definition)An operation is performed by a rank-2 function:
fun perform1( ev : evstd/core/hnd/ev: ((E, V) -> V) -> V<h>, select-op : (forall<e1,r> h<e1,r> -> clause1std/core/hnd/clause1: (V, V, (E, V) -> V, E, V) -> V<a,b,h,e1,r>), x : a ) : e b
where we can call an operation given evidence for a handlerevstd/core/hnd/ev: ((E, V) -> V) -> V<h>
together with a polymorphic field selection function that for any handlerh
in any answer context, returns its clause. It is defined as:Each clause definition can now determine to fully yield to the handler, or be tail-resumptive etc. (that is, this is determined at the handler definition site, not the call site, and generated by the compiler) For example, we could be most general (
ctl
) and yield back to the marker (where the handler was defined in the call-stack) (with a function that receives the continuation/resumptionk
):or be super efficient and directly call the (tail-resumptive) operation in-place (
fun
):and various variants in-between. The last definition is unsafe for example if the (user defined)
op
invokes operations itself as the evidence vector should be the one as defined at the handler site. So, we norally use instead:where
under1
uses the evidence vector (hevv
) stored in the evidenceev
to executeop(x)
under. (this is also explained in detail in the generalized evidence paper).