5 Comments

Matchure: Serious Clojure Pattern Matching

One of the things I find myself yearning for in a lot of programming languages is a powerful pattern matching system. I wrote one for ruby, but ruby’s syntax just wasn’t flexible enough to make something as elegant as I’d like. When I started using clojure, it seemed like a great little project for getting to know clojure’s macro facilities as well as clojure itself. I set out to build a kickass pattern matching library for clojure that fit in with the language’s way of doing things and was at least as expressive than any other pattern matching facility I’ve used.

The outcome of this effort, is matchure (github, clojars), a pattern matching library for clojure featuring

  • equality checks,
  • sequence destructuring,
  • map destructuring,
  • regexp matches,
  • variable binding,
  • “instance of” checking,
  • arbitrary clojure expressions,
  • and boolean operators (and, or, not).

All of which compile down to high performance clojure.

Introduction

Clojure, like most lisps, has a built-in facility which gets you part of the way there: destructuring in most variable-binding contexts. For example,

1
2
3
(let [a 1]
  a) 
; returns 1

binds 1 to the variable a. You can just as easily grab the first value out of a sequence:

1
2
3
(let [[fst & rst] (list 1 2 3)]
  [fst rst])
; returns [1 (2 3)]

This facility is even more powerful. You can also destructure maps as well. There’s just one problem: let doesn’t work well as a way of testing values.

1
2
3
(let [[fst & rst] (list)]
  [fst rst])
; returns [nil nil]

This is, in part, why I wrote matchure. Matchure provides a pattern matching facility that makes it really easy to perform complex tests against values and bind parts of them to variables.

Syntax and Examples

Like any lisp, clojure is homoiconic – clojure code is represented by clojure data structures. Unlike other lisps, however, clojure has a rich set of data structures with literal representations. '(1 2 3) is a linked list, [1 2 3] is a vector, with fast random access, and {:a 1, :b 2} is a hash map. Furthermore, these syntaxes have special places within clojure itself. Lists are clojure function or macro calls and vectors are used anywhere variables are bound or sequences are destructured. Aside from being a literal representation in code, the hash syntax is used for destructuring maps in let bindings.

Matchure takes this rich set of literal representations and uses it as a natural way to match patterns. At the moment, there are three main forms, if-match, when-match, and cond-match. They work similarly to the clojure built-ins, but match values.

At the most basic level, matchure can be used to test for equality.

1
2
3
4
5
(if-match [nil nil] true) ;=> true
(if-match [1 1] true) ;=> true
(if-match ["asdf" "asdf"] true) ;=> true
(let [s "asdf"]
  (if-match ["asdf" s] true)) ;=> true

Regular expression literals test for a match


(if-match [#"hello" "hello world"] true) ;=> true

and fully qualified class names test for instance? relationships.

1
2
(if-match [java.lang.String "foo"] true) ;=> true
(if-match [java.lang.Comparable "foo"] true) ;=> true

Matchure supports _ and ? as wildcards, so both of these match anything

1
2
(if-match [_ "foo"] true) ;=> true
(if-match [? "foo"] true) ;=> true

_ is idiomatic in clojure when you want to ignore a value. In matchure, ? has special meaning. ? can be thought of as “the thing this part of the pattern is matching against”. As such, ? always matches successfully. It is also used in binding variables. ?foo matches successfully and stores the matched value in the variable foo.


(if-match [?foo "bar"] foo) ;=> "bar"

just like regular clojure, list literals represent clojure code. Here, too, the special meaning of ? comes into play. You can perform arbitrary tests this way using ? to represent the matched against value:


(if-match [(odd? ?) 1] true) ;=> true

Just like with let, you can destructure sequences


(if-match [[?fst & ?rst] [1 2 3]] [fst rst]) ;=> [1 (2 3)]

and maps


(if-match [{:foo (even? ?)} {:foo 2}] true) ;=> true

Finally, unlike any other pattern matching facility I’ve seen, matchure has support for boolean operators.

1
2
(if-match [[(and ?fst (even? ?)) & ?rst] [2 3 4]] [fst rst]) ;=> [2 (3 4)]
(if-match [[(and ?fst (even? ?)) & ?rst] [1 2 3]] [fst rst] :failed-match) ;=>:failed-match

or and not are also supported.

You can get the code from github, or use it in your clojure project by grabbing it from clojars.