Sources and sinks in practice

Three patterns for connecting a pipeline to the rest of your program: push events in from outside, send messages to a running stage, and move data between pipelines and ordinary values.

Push events from outside

Some sources can’t produce events on request. Their events come from a socket, a timer, or another process. source.new_with_emitter passes an Emitter to the initialiser, and any process can push events through it:

let assert Ok(measurements) =
  source.new_with_emitter(init: fn(emitter) {
    my_socket.on_packet(fn(packet) { source.push(emitter, [packet]) })
    Ok(Nil)
  })
  |> source.start()

The source sends pushed events to its consumers right away, up to the open demand. The buffer holds the rest. Backpressure can’t slow the outside world down: if pushes keep outrunning demand, the buffer fills and the overflow policy decides what to drop.

Send messages to a stage

A stage can receive messages of any type you choose, alongside the event flow. Use this for timers, configuration changes, and queries. Give the stage a message channel with on_message. At startup, the initialise callback receives the channel’s subject. The handler receives each message together with the state, and it can do everything the stage can do — a source or a gate can emit events from it:

let assert Ok(prices) =
  source.new(init: Nil, on_demand: fn(state, _demand) {
    source.emit([], state)
  })
  |> source.on_message(
    initialise: fn(subject) {
      // A tick every second.
      let _timer = process.send_after(subject, 1000, Tick)
      Nil
    },
    handler: fn(state, message) {
      case message {
        Tick -> source.emit([read_price()], state)
        Refresh(new_source) -> source.emit([], new_source)
      }
    },
  )
  |> source.start()

Yielders and folds

source.from_yielder builds a source from a yielder; the source stops normally when the yielder ends. A source you write yourself must end with source.emit_final(events): the last events go out, and then the source stops. Don’t wait for a later demand to return source.stop() — after a batch that doesn’t fill the demand, the consumers won’t ask again, and the pipeline waits forever. In the other direction, sink.fold runs the full flow of an outlet through a fold and returns the final value:

import gleam/yielder

let assert Ok(numbers) =
  source.from_yielder(yielder.range(1, 100)) |> source.start()
let assert Ok(total) =
  sink.fold(from: numbers.data, initial: 0, with: int.add, within: 5000)
Search Document