Banner B. Schafer.

Yampa Switches

Dynamic Signal Function Composition in Functional Reactive Programming

Switches allow you to change the signal function being applied over time, enabling state transitions, mode changes, and dynamic behavior in FRP applications without breaking the pure functional paradigm.

What Are Switches?

In Yampa (an arrow-based FRP library), a switch is a combinator that allows you to dynamically change which signal function is currently active. This is essential for building real applications with multiple modes, states, or phases.

Think of switches as the FRP equivalent of state machines: they let you transition from one behavior to another based on events, while maintaining the compositional and pure nature of signal functions.

Why Switches Matter

Without switches, you’d be stuck with a single, unchanging signal function for your entire program. Switches enable: game menus, mode transitions, dynamic object creation/destruction, and conditional behavior changes.

The Basic Switch

switch

switch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b

The most fundamental switching combinator. It runs the first SF until it produces an event, then uses the event’s value to generate a new SF to run from that point forward.

How it works:

-- Example: Run a countdown, then switch to a different behavior
gameWithTimer :: SF () Render
gameWithTimer = 
  switch (countdownScreen >>> arr (\s -> (s, gameStart s))) $ \_ ->
    mainGameplay

Switch Variants

Yampa provides several switch variants that differ in timing (immediate vs delayed) and recurrence (one-time vs recurring).

Timing: Immediate vs Delayed

Immediate

The new SF processes the current input immediately when switching occurs.

switch, rSwitch, kSwitch

Use when the new SF should “see” the switching event’s input.

Delayed (d-prefix)

The new SF starts at the next time step. The current output is discarded.

dSwitch, drSwitch

Use when you need a clean boundary between behaviors.

Recurrence: One-Time vs Recurring

One-Time

Switch happens at most once, then the continuation runs forever.

switch, dSwitch

Recurring (r-prefix)

Can switch multiple times as events continue to fire.

rSwitch, drSwitch

Key Switch Types

dSwitch - Delayed Switch

dSwitch :: SF a (b, Event c) -> (c -> SF a b) -> SF a b

Like switch, but the new SF starts at the next time step. The current input that triggered the switch is not processed by the new SF.

rSwitch - Recurring Switch

rSwitch :: SF a b -> SF (a, Event (SF a b)) b

Starts with an initial SF, but can switch to new SFs repeatedly whenever events arrive in the input signal. Each event carries the new SF to switch to.

drSwitch - Delayed Recurring Switch

drSwitch :: SF a b -> SF (a, Event (SF a b)) b

Combines delayed observation with recurring behavior. The new SF starts at the next time step, and switches can happen multiple times.

kSwitch - Continuation Switch

kSwitch :: SF a b -> SF (a, b) (Event c) -> (SF a b -> c -> SF a b) -> SF a b

Advanced switch that gives the continuation function access to both the event value and the original SF, allowing you to preserve or inspect state across switches.


Parallel Switches

Yampa also provides parallel switches for managing collections of signal functions running concurrently.

pSwitch - Parallel Switch

pSwitch :: col (SF a b) -> SF (a, col b) (Event c) -> (col (SF a b) -> c -> SF a (col b)) -> SF a (col b)

Manages a collection of SFs running in parallel. When an event fires, all SFs can be replaced with new ones based on the event value.

When to Use Parallel Switches

Use pSwitch and variants (like dpSwitch) when managing multiple game objects, particles, or any collection of independent behaviors that might need to change together. This is the foundation of object routers in Yampa games.

Parallel Variant Modifiers


Critical Warnings

⚠️ Infinite Switching at Time Zero

If the new SF can also switch at time zero, and it refers back to the first SF, you can create an infinite loop of switches that never resolves. The continuation is evaluated strictly at switching time!

⚠️ Double (Nested) Switching

When a switch occurs, the new SF is applied immediately. If that new SF can also fire an event at time zero, a nested switch can happen. Be careful about composing switches that might trigger simultaneously.


Practical Patterns

program :: SF () Render
program = 
  switch menuScreen $ \case
    StartGame -> switch gameplay $ \gameResult ->
      gameOverScreen gameResult
    Options   -> optionsScreen

Using Swont (Signal Continuation)

The Swont pattern wraps switches in a continuation monad for cleaner composition:

newtype Swont i o a = Swont { unSwont :: Cont (SF i o) a }
  deriving (Functor, Applicative, Monad)

program :: SF () Render
program = foreverSwont $ do
  menuChoice <- menuScreen
  case menuChoice of
    Start   -> mainGame
    Options -> optionsScreen

Object Collection Management

-- Managing multiple game objects that can be added/removed
objectManager :: SF (Input, Event [Object]) [Object]
objectManager = 
  dpSwitch initialObjects
           detectCollisions
           updateObjectCollection

When to Use Which Switch

Use switch/dSwitch when…

You have a one-time state transition (title screen → game, game → game over).

Use rSwitch/drSwitch when…

You need repeated mode changes based on ongoing events (character states: idle → walk → jump → idle).

Use kSwitch when…

You need to inspect or preserve the current SF’s state when switching (pause/resume functionality).

Use pSwitch variants when…

Managing collections of objects that all need to be updated or replaced together (game entities, particles, bullets).


Key Takeaways

Switches are the mechanism for dynamic behavior in Yampa. They let you compose signal functions that change over time while maintaining purity and compositionality. The variety of switches (immediate/delayed, one-time/recurring, single/parallel) gives you fine-grained control over timing and state management.

“The basic idea of switching is formed by combining a subordinate signal function and a signal function continuation parameterised over some initial data.”

Created with the Twilight Scholar design system · For more on Yampa, see the official documentation and Yampa Wiki