Switch Chains
Separating decisions from actions
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 -> | foo: 2
false -> | bar: "Hi"
Here, the type of | foo: 2 is | foo: Int and the type of | bar: "Hi" is | bar: String. The type of a switch expression is the union of its cases, so | foo: Int bar: String (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)) index: Int key: k value: v
-> (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 .+ 1) key 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)) index: Int key: k equals: (\ k k -> Bool)
-> (Maybe v)
= index = index .mod (buckets.length)
buckets .get index
% filled: entry ->
equals (entry.key) key
% true -> | found_it: entry.value
false -> | keep_searching
tombstone -> | keep_searching
empty -> | not_in_map
% found_it: value -> | some: value
not_in_map -> | none
keep_searching -> buckets .raw_get_maybe (index .+ 1) key equals
Beautiful!