HORIZON HASKELLDocslts/ghc-10.0.xaa25b892026-10-10Search names, modules, packages, or :: a typeCtrl K

GHC 10.0.0.20260917 · lts/ghc-10.0.x · aa25b89 · 2026-10-10

Modulevortex-shell-0.4.1GHC2024

Vortex.Shell

The kernel: an Evaluator is trace . meter . fold . elaborate.

  • 3 types
  • 5 values
datadata Shell
#

One DSL shell: a theory (named by scriptType, in scope via scope) paired with a wisp-indexed model. The invariant tying the fields together: in scope, scriptType must denote the type constructor of f — blocks are elaborated at exactly that type, so the two must agree or the coercion is wrong.

Constructors

  • forall f. Shell
    • scriptType :: String

      e.g. "Spell"; blocks check as Spell String / Spell ().

    • scope :: [(String, Maybe String)]

      The wisp's entire world. The export list is the syscall table: nothing in it may lift IO into f.

    • banner :: String
    • warmup :: [String]

      Sample blocks the warm session compiles at startup to heat the operation code, so the first real block is as fast as the rest. The DSL author knows its own syscalls; e.g. ["roll 6", "help"].

    • commandDocs :: [(String, String)]

      Optional one-line docs per operation name, surfaced by :commands. Types are reflected automatically; docs are the one thing hint cannot recover (no Haddock at runtime), so they live here.

    • table :: Table f

      The same operations for peers that do not elaborate: a frame that reads as a Call is dispatched from here, no GHC involved.

    • runScript :: forall a. Wisp -> Emit -> f a -> IO a

      The wisp-indexed model: same theory, per-subject algebra.

typetype Evaluator = Wisp -> Emit -> String -> IO Reply
#

Evaluate one block for one wisp: elaborate, run under fuel, trace. Said output streams through the Emit as the block runs; the returned Reply is the terminal. Cold and warm evaluators differ only in the Elaborator behind them.

typetype Bindings = [(String, String)]
#

Session bindings: a name mapped to one equation of a recursive let spliced into every later block. Two provenances, matching Haskell's own semantics: x <- e evaluates now and captures the show-rendered *value* (effects run once, never replayed); let f x = e (or bare f x = e) captures the *definition* verbatim — so functions bind, and a spell bound by let is a recipe cast anew at each use. Either way the environment is itself Haskell text, like everything else on this wire.

A cold evaluator: each block boots a fresh GHC session. Simple, ~seconds per block. Good for one-shot -c and scripts.

A warm evaluator: one persistent GHC session serves every block, so the ~seconds of session boot are paid once at startup and each block costs milliseconds. The session is shared and serialised, so it fits an interactive shell or a modest fleet of wisps; a worker pool is the next step for concurrency.

valuehandleSession :: Evaluator -> Wisp -> Session -> IO ()
#

Drive a shell over a transport session: each frame is one statement, Said frames stream back, one terminal Reply frame ends each block. Bindings persist for the life of the session. An Exec request is a single block; everything else streams.

valuerepl :: Evaluator -> String -> Wisp -> IO ()
#

An interactive vortex on a tty. Bindings persist: n <- roll 20 then pure n works, the value captured show-rendered.