Ideas, Languages, and Programs

In Defense of Pattern Matching

by Martin Odersky

June 29, 2006



Summary

Pattern matching is much maligned by object-oriented programmers. I wonder why?


As I wrote before, Scala aims to unify object-oriented and functional programming. When people talk about functional programming, they usually mean one of two different things: In the exclusive sense, functional means no side-effects. In the inclusive sense it means a programming style which composes functions in interesting ways. When I talk about functional programming, I usually mean it in the inclusive sense.

A number of language constructs are called functional because they are used a lot in functional languages or because they first appeared in a functional language. Three such features are:

functions as first-class values

parameterized types

pattern matching

When I talk to many advocates of object-oriented programming, they readily embrace the first two. After all, functions as first-class values are also found in Smalltalk (where they are called blocks), Python, or Ruby. The next version of C# is going to have them, and I would not be surprised if a future version of Java also acquired them. Parameterized types are also very common, with variations such as C++ templates, or the generics of Eiffel, Java 1.5 or C# 2.0. But when I propose pattern matching, I get violent outbursts of rejection. The arguments against are usually a permutation of:

Pattern matching is unnecessary, just use the visitor design pattern. Pattern matching is not extensible. Pattern matching breaks encapsulation .

I disagree, obviously.

``Pattern matching is unnecessary'' - Sure, one can use the visitor pattern to get something similar. But visitors require a lot of code scaffolding. Also, they don't allow nested patterns. To pick an example (complete code), let's say I want to simplify arithmetic expressions. Expressions are given by the following Scala class hierarchy:

class Term case class Num(n: int) extends Term case class Var(name: String) extends Term case class Mul(l: Term, r: Term) extends Term case class Add(l: Term, r: Term) extends Term

There is a base class Term with four subclasses: Num for numbers, Var for variables, Mul for multiplication operations, and Add for addition operations. So the term (2 * x) would be represented as new Mul(new Num(2), new Var(x)) .

Note that each of the four subclasses has a case modifier. This means we can pattern match on it, and also that we get an implicit factory method with the same name as the case class. With the factory methods, we can abbreviate the term above to Mul(Num(2), Var(x)) .

Now let's say we want to implement some simplification rules on such terms. Two useful simplifications apply the equalities:

0 * x = 0 1 * x = x

With pattern matching they can be expressed straightforwardly:

def simplify(term: Term) = term match { case Mul(Num(0), x) => Num(0) case Mul(Num(1), x) => x case _ => term }

How much harder is this with a visitor? I invite you to try it out!

``Pattern matching is not extensible'' - This assumes that pattern matching can only be done on so-called algebraic datatypes which have a fixed number of cases. This is indeed the common approach in functional languages (an exception are polymorphic variants in OCaml). In Scala, however, any class can be labeled a case class. It would be perfectly possible to define other case classes that inherit from Term in different compilation units.

``Pattern matching breaks encapsulation'' - I think this is a misunderstanding in that people assume that pattern matching can inspect the internal fields of an object. But in Scala, pattern matching simply reverses the construction process. That is, you can find out through pattern matching what is the case class of the selector value and what are its constructor parameters. Any internal fields remain hidden. Also, if you don't want your clients to see the way an object was constructed, don't mark its class as a case class.

So, in summary I think pattern matching is extremely convenient and is a good fit with object-oriented programming. I wonder why not more mainstream languages have adopted it.

Talk Back!

Have an opinion? Readers have already posted 35 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Martin Odersky adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Martin Odersky is the inventor of the Scala language and professor at EPFL in Lausanne, Switzerland. In 2008, he was appointed fellow of the ACM for his contributions to the fusion of functional and object-oriented programming. He believes the two paradigms are two sides of the same coin, to be unified as much as possible. To prove this, he has worked on a number of language designs, from Pizza to GJ to Functional Nets. He has also influenced the development of Java as a co-designer of Java generics and as the original author of the current javac reference compiler.

This weblog entry is Copyright © 2006 Martin Odersky. All rights reserved.