Your resource for web content, online publishing
and the distribution of digital products.
«  
  »
S M T W T F S
 
 
 
 
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
10
 
11
 
12
 
13
 
14
 
15
 
16
 
17
 
18
 
19
 
20
 
21
 
22
 
23
 
24
 
25
 
26
 
27
 
28
 
29
 
30
 
31
 
 
 
 
 
 

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

DATE POSTED:January 28, 2025

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 if…else 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

\