remote
An effect whose operations another program does: an API, shared by a
client and a server, each of them a handler of the same effect. The
client’s sends each operation, written (wire), through a
transport, and goes on with the answer read; the server’s reads it,
does it by the effect’s real handler and writes the answer; a test’s
does both in one program, the bytes as they would go. Pure, for any
backend: the transports are std.remote’s (C: an HTTP server, and a
client by std.http) and browser.remote’s (a page’s fetch). Import
it by a name, import remote as Remote. heff’s notes example is a
page and a server sharing one effect so.
An API is written once, in a module both programs import: its effect,
each operation’s index, name and types (operation), and two
functions of its constructors — what each called is (call) and how
each is served (serve) — which api puts together with the API’s
name. Its fingerprint is a hash of the name and of every operation’s
index, name and types, as their Wires’ shapes say them: a client
and a server of different builds of it are told apart.
A frame is what goes each way, a header and what it carries:
b[“hw”, 1::8, kind::8, fingerprint::32, index::16, n::32, payload::binary-size(n)]
the wire’s version (1), what the frame is (0 a call, 1 its answer, 2
a failure), the API’s fingerprint, the operation’s index (of a
failure, why: 1 another API, 2 no such operation, 3 its argument not
read, 4 no frame), and the payload: the argument, the answer, or what
the server says of the failure, a String as the wire writes it.
Operation
data Operation a b = MkOperation Int StringAn operation of an API, as the wire names it: its index among the
API’s operations and its name; what it takes, a, and what it gives
back, b (a constructor of no argument takes a Unit).
operation
operation : {a b : Type} -> Int -> String -> Operation a bCall
type Call x = { index : Int, argument : Bytes, answer : Bytes -> Maybe x }An operation called: its index, its argument written, and its answer read and given to what the operation gives it to.
call
call : {a b x : Type} -> {{takes : Wire.Wire a}} -> {{gives : Wire.Wire b}} -> Operation a b -> a -> (b -> x) -> Call xThe operation o called with arg, its answer given to k (the
constructor’s continuation).
Served
type Served x = { index : Int, signature : String, run : Bytes -> Maybe x }An operation served: its index, its signature (of which the API’s fingerprint is made), and its argument read and made the operation, the answer written.
serve
serve : {a b x : Type} -> {{takes : Wire.Wire a}} -> {{gives : Wire.Wire b}} -> Operation a b -> (a -> (b -> Bytes) -> x) -> Served xThe operation o served: make is its constructor, given the
argument read and what its answer is written by.
Api
theory Api (h : HFunctor) where name : String fingerprint : Int calls : {m : Type -> Type} -> {x : Type} -> h.f m x -> Call x serves : {m : Type -> Type} -> List (Served (h.f m Bytes))An API: the effect h, its name, its fingerprint, each operation as
it is called, and each as it is served, whatever computations the
effect’s functor holds (none of an API’s operation is sent).
fnv
fnv : String -> IntA text’s FNV-1a hash, 32 bits.
>>> fnv ""2166136261>>> fnv "a"3826002220fingerprintOf
fingerprintOf : String -> List String -> IntAn API’s fingerprint: its name and every operation’s signature, one a line, hashed.
api
api : (h : HFunctor) -> String -> ({m : Type -> Type} -> {x : Type} -> h.f m x -> Call x) -> ({m : Type -> Type} -> List (Served (h.f m Bytes))) -> Api hThe API of h, named: calls says what each operation called is
(call), serves how each is served (serve).
signatures
signatures : {h : HFunctor} -> Api h -> List StringThe API’s operations’ signatures, as its fingerprint is made of them.
version
version : IntThe wire’s version, in each frame: a frame of another is refused.
Kind
data Kind = Asked | Answered | FailedWhat a frame is: a call, its answer, or a failure.
Frame
type Frame = { kind : Kind, fingerprint : Int, index : Int, payload : Bytes }A frame read: what it is, the API’s fingerprint, the operation’s index (of a failure, why), and its payload.
frame
frame : Kind -> Int -> Int -> Bytes -> BytesA frame written.
>>> frame Asked 1 2 b["x"]b["hw", 1, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, "x"]readFrame
readFrame : Bytes -> Either String FrameA frame read: what it is, or why it is none.
>>> either (\why -> why) (\(f : Frame) -> show f.payload) (readFrame (frame Answered 7 0 b["ok"]))"ok">>> either (\why -> why) (\(f : Frame) -> "a frame") (readFrame b["hw", 2, 0])"a frame of the wire's version 2, not 1"RemoteError
data RemoteError = Unreachable String | Mismatched String | Refused String | Malformed StringWhy a call came to no answer: no answer came (the transport’s: no connection, a status that is no 200); the server is of another build of the API, or of the wire; it could not read the call (no such operation, its argument); or the answer could not be read.
instanceShowRemoteError
instance instanceShowRemoteError : Show RemoteErrorWhy, as a line.
named
named : String -> Int -> StringAn API’s name and fingerprint, as a failure says them.
saidIn
saidIn : Bytes -> StringTransport
type Transport (e : HFunctor) = { exchange : Bytes -> Co e (Either RemoteError Bytes) }A transport: a frame sent, and the frame that answers it, or why
none came (Unreachable). HTTP’s POST is std.remote’s and
browser.remote’s; loopback is a server in the same program.
sent
sent : {h : HFunctor} -> {e : HFunctor} -> {{failed : exception RemoteError <: e}} -> Api h -> Transport e -> {a : Type} -> h.f (Co e) a -> Co e aAn operation of the API sent by the transport, its answer read; where
none came, or it could not be read, a throw of exception RemoteError.
remotely
remotely : {h : HFunctor} -> {e : HFunctor} -> {{failed : exception RemoteError <: e}} -> Api h -> Transport e -> {a : Type} -> Co (h <+> e) a -> Co e aThe client: h’s operations, each sent by the transport, the rest
of the world done as it is. A call that came to no answer throws
RemoteError (exception, in e): recover hears it, and attempt
makes one no recover heard Left.
respond
respond : {h : HFunctor} -> {e : HFunctor} -> Api h -> ({a : Type} -> h.f (Co e) a -> Co e a) -> Bytes -> Co e BytesThe server: a call’s frame answered, the operation done by alg,
the effect’s real handler (as through takes one); a frame that is
no call of this API’s answered by a failure, which the client
throws.
loopback
loopback : {h : HFunctor} -> {e : HFunctor} -> Api h -> ({a : Type} -> h.f (Co e) a -> Co e a) -> Transport eA transport to a server in the same program: each frame answered by
respond, as a server would. (No network: a test’s.)
locally
locally : {h : HFunctor} -> {e : HFunctor} -> {{failed : exception RemoteError <: e}} -> Api h -> ({a : Type} -> h.f (Co e) a -> Co e a) -> {a : Type} -> Co (h <+> e) a -> Co e ah’s operations done by alg, each through the wire as remotely
sends one and respond answers it: what a test of a client runs,
with no server.