mgree / trampoline / Trampoline

The Trampoline library is for writing suspendable computations, i.e., long-running computations that don't freeze the UI. The library supports two interfaces: step functions and fueled computations. The implementation is entirely based around step functions.

Key definitions for writing suspendable computations

Long running computations have two parts: a Stepper and an input. You'll set the stepper when configuring the trampoline state (see init).


type alias Stepper a o =
a -> StepResult a o

A Stepper a o is a function that takes a value of type a and either:

For example, a stepper for a programming language might look like:

type alias ExprStepper = Stepper Expr (Result RuntimeError Value)

The types a and o need not be different. A long running computation which always succeeds and produces an integer might use a stepper like:

type alias IntStepper = Stepper Int Int


type StepResult a o
    = Stepping a
    | Done o

The result of a single step: either another a, or we're done with an o.

Messages

Your program will control the steppers' inputs and when it runs by sending messages. Internally, long running computations use private messages to continue running while allowing other messages to be processed.


type Msg a msg

The messages used by Trampoline's stepper. The actual definition is abstract, but setInput, go, stop, and msg should suffice.

setInput : a -> AndGo -> Msg a msg

Sets the input for the stepper function. You can set the input and start by running:

setInput 5 AndGo

Or you can wait for a later go message by running:

setInput 5 AndWait


type AndGo
    = AndGo
    | AndWait

Determines whether to immediately start running after SetInput or to wait for Go message.

go : Msg a msg

Tells the stepper to go. It's harmless to send this message when there's no input, the stepper is already running, or the stepper is stopped.

stop : Msg a msg

Tells the stepper to step. It's harmless to send this message when there's no input or the stepper is already stopped.

msg : msg -> Msg a msg

Turns a message from your application into a Msg a msg.

Models


type State a o
    = NoInput
    | HasInput a
    | Running a
    | Stopped a
    | Finished o

The current state of the stepper. This information is revealed completely so you can reflect it best in your program; you should probably not change it yourself.


type alias Stats =
{ numSteps : Internal.Gas
, totalSteps : Internal.Gas 
}

The stepper keeps some rudimentary statistics. Eventually, this information (along with some timing information) will allow for the stepper to adaptively determine how many steps to take at a time. As such, while it's currently harmless to change this information, it's inadvisable.


type alias Model a o model =
{ state : State a o
, stats : Stats
, stepper : Stepper a o
, model : model 
}

A Model a o model wraps your program's model type to accommodate an Stepper a o.

Helpers using the stepper

A variety of wrappers will help you manage state and handle only the messages your program needs to know about.

init : (flags -> ( model, Platform.Cmd.Cmd (Msg a msg) )) -> Stepper a o -> flags -> ( Model a o model, Platform.Cmd.Cmd (Msg a msg) )

The init wrapper initializes the outer trampoline state given:

This is meant to be used with Browser.element, e.g.,

Browser.element 
  { init = Trampoline.init myInit myStepper
  , ... }

See update and subscriptions, too.

update : (msg -> model -> ( model, Platform.Cmd.Cmd (Msg a msg) )) -> (o -> msg) -> Msg a msg -> Model a o model -> ( Model a o model, Platform.Cmd.Cmd (Msg a msg) )

A wrapper for your program's update function. You must provide:

This is meant to be used with Browser.element, e.g.,

Browser.element 
{ update = Trampoline.init myUpdate NotifyMsg
, ... }

where NotifyMsg is some message defined in your application of type o -> msg.

subscriptions : (model -> Platform.Sub.Sub msg) -> Model a o model -> Platform.Sub.Sub (Msg a msg)

A wrapper for your program's subscriptions function, which amounts to running Platform.Sub.map.