Mirko Stocker

Eliminating Pattern Matching

In the last few years, I worked on several Java projects where we transformed and analyzed abstract syntax trees, so when I started learning Scala, pattern-matching quickly became one of my favorite language features. I could never warm up to the visitor pattern, so I was thankful that Scala offered a much more powerful alternative.

What I also liked very much was Scala’s consistent use of the Option type in its standard library. Now, instead of having to read the documentation to find out whether some call could return null, the type checker forced me to handle this where necessary. So a lot of my early Scala code looked as follows:

doSomething() match {
  case Some(value) => Some(doSomethingElse(value))
  case None => None
}

While it’s still possible to get a NPE in Scala, it just doesn’t happen with well-written libraries, simply because there’s no need to ever use null.

(By the way, isn’t it funny that Java forces you to check exceptions but doesn’t help you with the much more common and annoying null problem?)

So yes, in practice, Options do save you from NPEs. Does your code also get smaller (because usually, in Scala it will)? Not if you pattern match on Some/None, all the un-wrapping and lifting is quite verbose.

Nowadays, certainly influenced by all the discussions on monads, I realize that pattern-matching on Option is a very primitive form of abstraction, and instead of the code above I now write:

doSomething() map (value => doSomethingElse(value))

(For those unfamiliar with functional programming, map applies the function to the value inside a Some, and does nothing when called on a None.)

Even better, we can fully automate this refactoring! I’m currently working on a first version in the scala-refactoring library. So far, I’ve implemented the refactoring for map, but there are many more we can do, for example:

  • If the Some case does not construct a Some but calls a function that returns an Option, we use flatMap instead of map.
  • If the Some case evaluates to Boolean and the None case returns false, we can replace it with exists. If Some returns true and None returns false, we can just replace the whole pattern match with isDefined.
  • When the None case is (), we can transform to foreach.

So far we have only looked at Option, but there is more: for example, we could also replace pattern-matching on lists and recursion with folds. I’m sure somebody has already written a paper about such refactorings, but I haven’t found anything yet.

What do you think about eliminating pattern matching? Do you also prefer mapping to explicit pattern-matching?

18 Comments

  1. In lot of case I used the match to None to log info.
    There is also conversion to Either/ scalaz’s Validation / lift’s Box
    In the above case automatic refactoring is harder or to avoid.

    NPE help to debug/find what is null, but following map/flatmap to see what is None it’s harder.

  2. David has made a valid point, however I think the refactoring you propose is going to be useful in a lot of places. Sometimes you only care for the positive case.

  3. Mirko,

    nice post, showing some relations between pattern matching for deconstructing / constructing Option values and monads.

    Just one note: if doSomething() is of type => Option[A] and doSomethingElse(value) of type A => Option[B], then you should flatMap over Option[A] instead of map, since you wouldn’t want to handle a Option[Option[B]].

    May i miss something here. May doSomethingElse(value) is thought of being of type A => B, than of course you only need to map over Option[A]. But than again, you only need to rely on Option as a Functor – no need for requiring its monad nature :o)

    Anyhow, interesting relation – interesting idea!

    Greetings

    Mario

  4. this elimination works in the binary world of Some/None, but i still think pattern matching is the only viable and good solution for sealed traits with more than two values. also, matching on option can make code more readable in occasions.

    val typ0 = (rateAttr, rateInfo) match {
    case (None, _) => r0
    case (Some( “ugen” ), None) => r0
    case (_, _) if( expandBin.isDefined ) => r0
    case (Some( “ugen” ), Some( info )) => r1( info.name )
    case (Some( rateName ), _) => r1( rateName )
    }

  5. David: Of course, if something else is done in the None case, then you don’t want to refactor that away. But I’m already handling these cases, my refactorings are very conservative 🙂

    Mario: You’re right, the example I gave doesn’t have much to do with monads, but reading so much about monads, functors, etc. was what made me think about this; that’s what I meant.

    itemState: In your example, and in many others, it’s perfectly fine to use pattern matching. I’m preparing a follow up blog where I analyze the Scala Corpus to see how many instances of such trivial pattern-matching exist and how many could be eliminated.

  6. This is exactly the kind of automatic refactoring the IDE’s could use. It’s one of the those where the IDE can teach you how to use the language better.

    How about “Convert checked exception to Either?”

  7. Uh, false dilemma. You’ve replaced a single usage of pattern matching (i.e. the Option type) with monads but that doesn’t prove anything about the general usefulness of pattern matching. PM is usef for a broad variety of things and is generally much more expressive than a for-comprehension. Consider an AST or lists or command-line arguments. The two are not mutually exclusive.

  8. Jim: After integrating the refactorings in the IDE, my next plan is to create markers in the source code for occurrences that could be refactored. And converting exceptions to Either are a great idea, I’ll put it in my notes.

    Bill: My intention is to just eliminate pattern-matching when it’s used inappropriately, like in the example I gave. As soon as you need the additional flexibility, like in itemState’s comment, PM is great. And if you look at my own code, you can see that I’m a huge fan of PM 🙂

  9. Sounds like a very useful tool. If you’re interested in ideas for other refactorings, take a look at [1]. The paper is about how current solutions to the expression problem aren’t so elegant when code evolution is taken into account and proposes mechanical transformations between ADT-centric and constructor-centric views (in Scala, the latter would probably be better modeled with subtyping).

    [1] http://scholar.google.com.br/scholar?cluster=9264949374601757507

  10. Pattern matching is not a primitive abstraction. You only used it in a primitive fashion. There are lots of use cases other than matching Options where map/flapMap isn’t a valid alternative. Just think about deelpy nested case classes, sequence patterns, etc.

  11. Hi Mirko!

    I am trying to find a way to report a problem building scala-refactoring project from source. Whenever I click on the “report a bug” link (http://scala-refactoring.assembla.com/spaces/scala-refactoring/tickets/new), I get a “not permitted” message. I have not found any mailing lists or other points to start a discussion on this. I haven’t even been able to find an email address for you. 😉

    I hate to drop a message in an inappropriate spot, but can you please help me with a problem building scala-refactoring from source? Can you direct me to a good place to initiate such a discussion?

    Thanks, John

  12. Hi Sullivan, that’s an Assembla thing, you need to watch the project before you can report bugs (it also keeps the spam out).

    Building the project is quite easy, but the command depends on the Scala version you want to build for (master is for >= 2.9). Take a look at the build.sh script in the root of the repository.

    You can also contact me at me@misto.ch if you have any further questions. I once registered a mailing list (http://groups.google.com/group/scala-refactoring-dev?lnk=srg), but it’s not in use yet. If you want, you could also ask there 🙂

  13. Thank you Mirko!

    I am now a member of the Assembla project, and I am able to submit bug reports. Let me take a step back and see if I can figure out the problem I was having before.

    Thanks very much, I do appreciate it.

    Best, John

Leave a Comment