drift

Define pure functional cores, which can be wrapped to handle side effects. The state of the core is represented with a Stepper, which for each step, handles an input and produces a new state and outputs.

Types

Represents a side effect to be performed once with a specific value. Can only be run outside of the pure context.

pub type Action(a) {
  Action(effect: Effect(a), argument: a)
}

Constructors

  • Action(effect: Effect(a), argument: a)

The result of canceling a timer

pub type Cancelled {
  TimerNotFound
  Cancelled(time_remaining: Int)
}

Constructors

  • TimerNotFound
  • Cancelled(time_remaining: Int)

Represents the context in which a state is being manipulated within a step. Can be used to add side effects while executing a step.

pub opaque type Context(input, output)

Represents a continuation in the purely functional context, which will be called with a new context and state when resumed. Allows handing external inputs of one type in a generic way in multiple different use cases.

pub opaque type Continuation(a, state, input, output, error)

Represents a side effect to be applied with a yet unknown value. Side effects may be applied multiple times.

pub opaque type Effect(a)

Represents a context in which effects may be applied. May hold state (or Nil, if no state is needed). An effect context can only be constructed when starting a stepper, and transformed using use_effect_context.

pub opaque type EffectContext(s)

Represents the next state of a stepper, after applying one or more steps.

pub type Next(state, input, output, error) {
  Continue(
    outputs: List(output),
    state: Stepper(state, input),
    due_time: option.Option(Int),
  )
  Stop(outputs: List(output), state: state)
  StopWithError(outputs: List(output), error: error)
}

Constructors

  • Continue(
      outputs: List(output),
      state: Stepper(state, input),
      due_time: option.Option(Int),
    )

    Execution of the stepper should continue, effects should be applied, and if due_time is Some, tick should be called at that time.

  • Stop(outputs: List(output), state: state)

    Execution of the stepper should stop with the final effects applied. The terminal state is also included.

  • StopWithError(outputs: List(output), error: error)

    Execution of the stepper should stop with the final effects applied. The given error should be applied in the executing context

An ongoing stepper update, which may update the state, timers, or produce outputs. Once a step is terminated (with or without error), it can no longer be continued.

pub opaque type Step(state, input, output, error)

Holds the current state and active timers.

pub opaque type Stepper(state, input)

A handle to a timer. Can be used to cancel the timer.

pub type Timer =
  @internal Timer

A monotonically increasing timestamp, in milliseconds.

pub type Timestamp =
  Int

Values

pub fn await(
  context: Context(i, o),
  state: s,
  make_output: fn(Continuation(a, s, i, o, e)) -> o,
  continuation: fn(Context(i, o), s, a) -> Step(s, i, o, e),
) -> Step(s, i, o, e)

Completes the current step with the given state, and adds the output constructed by make_output. continuation will be executed with the new context and state when it is resumed. Designed to be used with use, e.g.

use context, state, response <- drift.await(context, state, SomeOutput)
pub fn bind_effect(effect: Effect(a), arg: a) -> Action(a)

Binds a value to an effect, to be performed by the impure context.

pub fn cancel_all_timers(context: Context(i, o)) -> Context(i, o)

Returns a new context with all timers canceled.

pub fn cancel_timer(
  context: Context(i, o),
  to_cancel: @internal Timer,
) -> #(Context(i, o), Cancelled)

Returns a new context with the given timer canceled.

pub fn chain(
  step: Step(s, i, o, e),
  f: fn(Context(i, o), s) -> Step(s, i, o, e),
) -> Step(s, i, o, e)

If a step hasn’t terminated, extracts the context and state from the step, and returns a new step from the given function.

pub fn continuation_id(
  continuation: Continuation(a, b, c, d, e),
) -> Int

Gets the id of the continuation. Should only really be needed for tests.

pub fn continue(
  context: Context(i, o),
  state: s,
) -> Step(s, i, o, e)

Ends the current step, signalling to continue running the stepper. All effects in the context should be applied by the wrapping runtime.

pub fn effect_id(effect: Effect(a)) -> Int

Get the id of an effect. This should only really be needed for tests.

pub fn new(
  state: s,
  io_state: io,
) -> #(Stepper(s, i), EffectContext(io))

Creates a new stepper with the given state for the pure and effectful parts.

pub fn new_effect(effect: fn(a) -> Nil) -> Effect(a)

Constructs an effect from a function to be called with a value produced later. Each Effect created is unique, even if they use the same function. This serves two purposes:

  1. Since the same side effect might be expected to be performed a specific number of times from different contexts, treating each created effect as unique allows discriminating between them based on equality comparison.
  2. Having a distinct id allows writing nice snapshot tests, where effects can be identified in the output.
pub fn now(context: Context(a, b)) -> Int

Gets the current timestamp from the context. The timestamp is always the start time of the step (time does not advance during a step, in order to keep things pure).

pub fn output(context: Context(i, o), output: o) -> Context(i, o)

Returns a new context with the given output added.

pub fn output_many(
  context: Context(i, o),
  outputs: List(o),
) -> Context(i, o)

Returns a new context with the given outputs added.

pub fn output_option(
  context: Context(i, o),
  optional_output: option.Option(o),
) -> Context(i, o)

Returns a new context with the given output added, if it was Some

pub fn perform(
  context: Context(i, o),
  make_output: fn(Action(a)) -> o,
  effect: Effect(a),
  arg: a,
) -> Context(i, o)

A shorthand for outputting effects to be performed. Example:

context
 |> drift.perform(SomeOutput, effect, state)
 |> drift.continue(state)
pub fn perform_effect(
  ctx: EffectContext(s),
  action: Action(a),
) -> EffectContext(s)

Performs a side effect that was prepared.

pub fn read_effect_context(ctx: EffectContext(a)) -> a

Reads the state of an effect context.

pub fn reset_ids() -> Nil

Resets the id counter used for effects and continuations, to get deterministic ids. Should only really be needed for tests.

pub fn resume(
  context: Context(i, o),
  state: s,
  continuation: Continuation(a, s, i, o, e),
  result: a,
) -> Step(s, i, o, e)

Resumes execution of a continuation.

pub fn start_timer(
  context: Context(i, o),
  delay: Int,
  input: i,
) -> #(Context(i, o), @internal Timer)

Returns a new context with a timer added to handle an input after a delay.

pub fn step(
  stepper: Stepper(s, i),
  now: Int,
  input: i,
  apply: fn(Context(i, o), s, i) -> Step(s, i, o, e),
) -> Next(s, i, o, e)

Applies the given input to the stepper, using the provided function. Returns the next state of the stepper.

pub fn stop(context: Context(i, o), state: s) -> Step(s, i, o, a)

Terminates the stepper with the final state without error. All effects in the context should still be applied by the wrapping runtime.

pub fn stop_with_error(
  context: Context(i, o),
  error: e,
) -> Step(a, i, o, e)

Terminates the stepper with an error. All effects in the context should still be applied by the wrapping runtime.

pub fn tick(
  stepper: Stepper(s, i),
  now: Int,
  apply: fn(Context(i, o), s, i) -> Step(s, i, o, e),
) -> Next(s, i, o, e)

Triggers all expired timers, and returns the next state of the stepper.

pub fn use_effect_context(
  ctx: EffectContext(a),
  fun: fn(a) -> a,
) -> EffectContext(a)

Applies a function to the state of an effect context, returning a new effect context.

Search Document