Knowing ‘When’ to Switch: A Glimpse into the Future of Pattern Matching

An overview of the when statement: a flexible pattern matching structure in DeltaScript, and an alternative to switch


I have spent over a year working obsessively to bring my vision of the perfect pixel art editor to life. The fruit of my labor is Stipple Effect, a program that lets users write scripts for a variety of use cases, including transforming the project for display in the preview window in real-time:


The rotating head animation is generated from the texture by a preview script


I was very particular about how I wanted the script-writing process to feel for users: quick, clear, painless, iterative. To achieve that, I designed and implemented my own scripting language rather than embedding Lua or another established scripting language in my program.


The result is DeltaScript, a scripting language “sketelon” designed to be extended for specific application domains. Stipple Effect‘s scripting API is one such extension.


I released the language specification for DeltaScript v0.1.0 last week (Jan 16, 2025), so I figured now is the perfect time to write about one of the features I am most excited about.


:::info
Note: This blog post is adapted from the original on my website. Read it there to see the proper syntax highlighting for DeltaScript code snippets.

:::


Why when?

DeltaScript was always supposed to be a high-level interpreted language. As such, I wanted the language to have powerful, flexible control flow structures that could express complex logic concisely and still be readable and maintainable.


One of my biggest frustrations as a programmer is the limitations and the implementation philosophy of the traditional switch statement: limited to literals in case labels, fallthrough, etc.


I do most of my programming in Java, and I must say, recent Java language versions have drastically extended the functionality of switch and turned it into a near-perfect pattern-matching structure. I wanted to do something similar for DeltaScript.

How when works

My when statement supports three different kinds of non-trivial cases:

  • is – matches the control expression against one or more expressions, checking for equality
  • matches – uses the special identifier _ to replace the control expression and defines a pattern in the form of a boolean expression
  • passes – accepts a test function (predicate) of type (T -> bool), where T is the type of the control expression


These cases can be arranged inside a when statement in any order. Each case is checked in order until a case passes its check, at which point the case body is executed. There is no fallthrough; once the runtime execution identifies a successful match case and executes its body, the execution drops out of the when statement and executes the statement that follows it.


Consider this example:

(color c) {
  ~ string pfx = "The color is ";

  when (c) {
    matches _.alpha == 0 -> print(pfx + "transparent");
    is #000000 -> print(pfx + "black");
    is #ffffff -> print(pfx + "white");
    matches _.r == _.g && _.r == _.b && opaque(_) -> 
            print(pfx + "a shade of grey");
    is #ff0000, #00ff00, #0000ff -> print(pfx + "an RGB primary color");
    passes ::bright_opaque -> print("bright");
    otherwise -> print(pfx + "not a match");
  }
}

bright_opaque(color c -> bool) {
  int max = max([ c.r, c.g, c.b ]);
  return max == 0xff && opaque(c);
}

opaque(color c -> bool) -> c.alpha == 0xff


This logic cannot be expressed by a traditional switch statement. Expressing it with an ifelse if would look like this:

(color c) {
  ~ string pfx = "The color is ";

  if (c.alpha == 0) print(pfx + "transparent");
  else if (c == #000000) print(pfx + "black");
  else if (c == #ffffff) print(pfx + "white");
  else if (c.r == c.g && c.r == c.b && opaque(c))
    print(pfx + "a shade of grey");
  else if (c == #ff0000 || c == #00ff00 || c == #0000ff)
    print(pfx + "an RGB primary color");
  else if (bright_opaque(c)) print("bright");
  else print(pfx + "not a match");
}

bright_opaque(color c -> bool) {
  int max = max([ c.r, c.g, c.b ]);
  return max == 0xff && opaque(c);
}

opaque(color c -> bool) -> c.alpha == 0xff


You can read the full semantics of the when statement in the language specification.


I’ll leave you with this long-form example that shows off a few additional language features of interest:

() {
    string[] words = [
        "Racecar", "Pilot", "Madam", 
        "Able was I ere I saw Elba", 
        "Nurses run", "Highway 61", 
        "A man, a plan, a canal - Panama"
    ];

    (string -> bool) no_whitespace_palindrome = 
                    (s -> palindrome(no_whitespace(s)));

    for (word in words) {
        when (word) {
            passes ::palindrome -> print(""" + _ + "" is a pure palindrome!");
            passes no_whitespace_palindrome -> 
                            print(""" + _ + "" is a palindrome if whitespace is ignored");
            passes (s -> palindrome(only_letters(s))) -> 
                            print(""" + _ + "" is a palindrome if whitespace and punctuation are ignored");
            otherwise -> print(""" + _ + "" is not a palindrome");
        }
    }
}

palindrome(string s -> bool) {
    string lc = lowercase(s);
    return lc == reverse(lc);
}

reverse(string s -> string) {
    string res = "";

    for (c in s)
        res = c + res;

    return res;
}

lowercase(string s -> string) {
    string res = "";

    for (c in s) {
        int unicode = (int) c;

        if (uppercase_letter(c))
            res += (char) ((int) 'a' + (unicode - (int) 'A'))
        else
            res += c;
    }

    return res;
}

no_whitespace(string s -> string) {
    ~ char{} WHITESPACE = { ' ', 't' 'n' };
    string res = "";

    for (c in s)
        if (!WHITESPACE.has(c))
            res += c;

    return res;
}

only_letters(string s -> string) {
    string res = "";

    for (c in s)
        if (uppercase_letter(c) || lowercase_letter(c))
            res += c;

    return res;
}

uppercase_letter(char c -> bool) {
    int unicode = (int) c;
    return unicode >= (int) 'A' && unicode <= (int) 'Z';
}

lowercase_letter(char c -> bool) {
    int unicode = (int) c;
    return unicode >= (int) 'a' && unicode <= (int) 'z';
}


This script produces the following output:

"Racecar" is a pure palindrome!
"Pilot" is not a palindrome
"Madam" is a pure palindrome!
"Able was I ere I saw Elba" is a pure palindrome!
"Nurses run" is a palindrome if whitespace is ignored
"Highway 61" is not a palindrome
"A man, a plan, a canal - Panama" is a palindrome if whitespace and punctuation are ignored

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.