Skip to content

Instantly share code, notes, and snippets.

@Keno
Last active December 25, 2025 22:08
Show Gist options
  • Select an option

  • Save Keno/e08874a423e0f1df4d11a798c7ed147c to your computer and use it in GitHub Desktop.

Select an option

Save Keno/e08874a423e0f1df4d11a798c7ed147c to your computer and use it in GitHub Desktop.

Syntax for match statements

This is a proposal for the native implementation of a match statement for pattern matching in julia. This was previously discussed in JuliaLang/julia#18285 and there have been various packages for this as well, most notably https://github.com/JuliaServices/Match.jl. `match is also found in several other languages, such as Scala (https://docs.scala-lang.org/tour/pattern-matching.html) and Rust (https://doc.rust-lang.org/book/ch06-02-match.html) as well as many functional progamming languages

This proposal is my attempt at a native julian take on these constructs, so the semantics are not exactly identical to any of these, but they should be considered inspiration of course.

Basic introduction

The control flow of a match statement is essentially an if/elseif change, evaluated top to bottom, combined with automatic destructuring.

For example, consider:

print(match n
	1 => "Hello"
	2 => "World"
	_ => "Other"
end)

This is semantically equivalent to writing:

print(if n == 1
	"Hello"
elseif n == 2
	"World"
else
	"Other"
end)

On the other hand, match also supports destructuring:

zero = 0
match (x, y)
	(1, a)     | (a, 1) 	=> 1 + a
	($zero, b) | (b, $zero) => 2 + b
	_               		=> 0
end

We will call each of these statements a match case and the LHS of these expressions as a match pattern. Match patterns generalize ordinary destructuring assigment.

Falling through to the end of a match statement is a runtime error.

Match pattern semantics

Continuing by example, the above example would expand to:

zero = 0

matchee = (x, y)
pat1 = matcher(|, matcher(tuple, 1, Capture(1)),
				  matcher(tuple, Capture(1), 1))
r = match(pat1, matchee)
if r !== nothing
	(a, ) = r
	1 + a
else
	pat2 = matcher(|,
		matcher(tuple, zero, Capture(1)),
		matcher(tuple, Capture(1), zero))
	r = match(pat2, patchee)
	if r !== nothing
		(b, ) = r
		2 + b
	else
		0
	end
end

There are some subtle semantics here:

  1. Any identifier occuring in other-than-call position get replaced by Capture.

  2. Any call expressions become calls to matcher. Note that the identifier of the call is evaluated, not replaced by Capture.

  3. Any variable or expression can be explicitly pasted into non-call position by escaping with $.

Inline match-destructuring

In some situations, the destructuring offered by match may be more powerful than ordinary destructuring assignment. We cannot simply extend all destructuring patterns to the LHS of ordinary assignment - this would conflict with function declaration. Instead, there is a special match destructuring assignment syntax like so:

match (a, b) = val

This assigns a, b in toplevel scope, but the LHS of the = is parsed as a match pattern and no end is required.

if guards

Like Rust, Match.jl, and generators, if guards are available:

match val
	(a, b) if sin(b) == 0. => a
	(a, b)				   => b
end

Simplified default case

A single _ in match case position is equivalent to x=>x, i.e.:

v = match foo()
   x if x == 0 => error()
   _
end

Assigns v to the result of foo in the fallback case.

Backwards compatibility

match is currently used as a funciton name for regex matching. In addition, it is a not-uncommon variable name. We should keep these working to the extent possible. As such, match is not a keyword if it occurs (without whitespace) before ( or = or as a single identifier in any other context.

Implementation

match is generally parsed as a macro call to the @match macro with all the smarts in the macro implementation or the match and matcher runtime functions. However, there are some parsing differences as well:

  1. Any expression other than => is disallowed.

  2. Plain ::a is allowed and corresponds to typeassert(a), i.e. gets lowered to matcher(typeassert, a)

  3. The special if guard syntax above is available

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment