Ideas, Languages, and Programs

Pattern Matching Wrap-Up

by Martin Odersky

July 18, 2006



Summary

A follow-up on my previous blog on pattern matching, which summarizes the discussions this entailed.


The previous blog In Defense of Pattern Matching has sparked many interesting discussions. I will try to give a quick wrap-up here. Besides some technical questions, most comments fell into two categories: They were wishing for more flexible and abstract schemes how objects can be matched into patterns, or they were proposing a classical object-oriented decomposition with virtual method calls as a better alternative to pattern matching.

More flexible patterns This was very constructive. It's great motivation for us to continue exploring ways to make pattern matching more flexible. In fact there had been a discussion about this on the Scala mailing list before. One thing to note here is that current pattern matching already provides quite a bit of flexibility (this should not imply that we should stop trying to generalize it further). Take, for instance, the prototypical example of points:

case class Point(x: double, y: double)

One might think this would lock us into a specific implementation of Points with cartesian x/y co-ordinates. But in fact, we could create a new sub-class of points with polar coordinates:

class PolarPoint(radius: double, angle: double) extends Point( Math.sin(angle) * r, Math.cos(angle) * r )

Even though these points now use internally a polar representation, their pattern matching would be cartesian. So we see that values returned by pattern matching need not correspond to an object's internal data representation. In that sense, encapsulation is preserved.

Multi-methods are a flexible alternative to pattern matching. They are not present in Scala. I think multi-methods would be great if they could replace Java's static overloading, which always seemed a bit ad-hoc to me. The question is whether one can do this without breaking compatibility with Java in too serious ways. Maybe the Nice folks have some experience to offer here? I have noted that some multi-method designs, such as MultiJava, have both static overloading and multi-methods; however I fear this would be too confusing for users.

Philosophy Another class of arguments criticized pattern matching on more fundamental grounds. Basically it goes as follows:

"Sure, pattern matching is a more convenient alternative to Java's instanceof and type casts. But all of these techniques are inferior to a proper object-oriented decomposition, where you use virtual methods to differentiate behavior in subclasses."

I believe this argument is valid in many cases, but not in all. There are situations where an OO decomposition is not easily doable. An example that's right under our noses is the try-catch statement found in Java and many other languages. In Scala it is written like this:

try { ... } catch { case ex: IOException => "handle io error" case ex: ClassCastException => "handle class cast errors" case ex: _ => "generic recovery" }

Here, every catch clause does a pattern match on the thrown exception (using a so-called "typed pattern"). In the equivalent Java code this is less obvious but in essence Java catch clauses contain pattern matches as well. What would the strict OO alternative be? To choose the correct handler code we'd have to call a virtual method in the thrown exception. The problem is that the exception itself does not not "know" how the handler should react to it; this clearly depends on the context. It is possible to program around this using the visitor pattern, but the result is clumsy and not easily extensible to new kinds of exceptions. That's probably why most languages have opted for exception handling in the pattern-matching style.

There are arguably many more cases where a decomposition from the outside is preferable to a decomposition from the inside using virtual methods. Cases to watch out for are:

when a computation rule involves several objects, when a computation cannot usefully be defined as a member of the class on which we want to differentiate.

An example of the first kind is the following rule from my term simplification example:

x * l + x * r == x * (l + r)

The corresponding simplification rule in Scala is:

case Add(Mul(x1, l), Mul(x2, r)) if (x1 == x2) => Mul(x1, Add(l, r))

An example of the second kind are exception handlers, discussed above.

In summary, I do believe there is a role for pattern matching in object-oriented languages. But with the added power comes the requirement that programmers now have to choose a style of decomposition. This can sometimes be difficult.

Talk Back!

Have an opinion? Readers have already posted 12 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.