Switch Chains

Separating decisions from actions

by Marcel Garus · 2025-10-28
available at www.marcelgarus.dev/switch-chains

My programming language Plum has postfix syntax for switching on enums:

a_bool
true -> 2
  false -> 3

It also has structural typing, enum literals like variant, and infers types of expressions:

value =
  a_bool
  true -> foo2
    false -> bar: "Hi"

Here, the value of foo2 is fooInt and the type of bar: "Hi" is barString. The type of a switch expression is the union of its cases, so fooInt barString (which you can read as "either it's foo with an Int payload, or bar with a String payload").

Turns out, structural typing and postfix switches compose really well!

In other languages, I'd often write code like this:

if condition {
  // action 1
  ...
} else {
  var value computation()
  if other_condition(value) {
    // action 2
    ...
  } else {
    // action 3
    ...
  }
}

Note how I interleave the information gathering / decision making with actually doing the action? In Plum, I can cleanly separate them using this pattern:

condition
true -> action_1
  false ->
    value computation
    other_condition value
    true -> action_2
      false -> action_3
action_1 -> ...
  action_2 -> ...
  action_3 -> ...

Here, I construct a value of the type action_1 action_2 action_3 and then immediately switch on it. The comments are gone! The code is now fully self-documenting.

The first complex data structure written in Plum was the standard library's hash map. It uses a typical implementation: A giant array stores the entries and you do linear searches to put entries into a free place and to find them. A key's hash determines where you start the search, so it works in an ammortized runtime of O(1).

The code for putting an item into the map uses the pattern I described above:

raw_put
  buckets: (Array (Bucket k v)) indexInt keyk valuev
  -> (Array (Bucket k v))
= index index .mod (buckets.length)
  buckets .get index
  filled    -> keep_searching
    empty     -> use_this_bucket
    tombstone -> use_this_bucket
  use_this_bucket -> buckets .set index (filled: (key value))
    keep_searching  -> raw_put buckets (index .+ 1key value

You don't need to understand every detail, but the gist is that I'm creating an enum keep_searching use_this_bucket, and then switch on it to do the corresponding action. Here's another function that uses switch chains:

raw_get_maybe
  buckets: (Array (Bucket k v)) indexInt keyk equals: (\ k k -> Bool)
  -> (Maybe v)
= index index .mod (buckets.length)
  buckets .get index
  filledentry ->
      equals (entry.keykey
      true  -> found_itentry.value
        false -> keep_searching
    tombstone -> keep_searching
    empty     -> not_in_map
  found_itvalue -> somevalue
    not_in_map      -> none
    keep_searching  -> buckets .raw_get_maybe (index .+ 1key equals

Beautiful!