Thursday, 27 June 2013

Pattern Matching Syntax – Case by Case

I’ve just read about Patter Matching.  The concepts made perfect sense, but as is frequently the case with me, the syntax still seemed hard to grasp.  Why were there all these bits? Which bit went where?  Why?

def my_method(myArg:TheArgType) = myArg match {   // match myArg against the following cases
    case myArg_is_same_as_this => do_this()       // and return the result I’m guessing
    case _ => do_that()                           // (ditto)
}

Scala in Action, Nilanjan Raychaudhuri, Chapter 2 (with a few slight changes for additional clarity)

That seems to be the simplest form.  That makes perfect sense. Next we kick it up a notch to replace simple value-matching for type-matching:

def my_method(arg:AnyRef) = arg match {  // match the arg against the following cases
    case s:String => do_this()           // now we have the object:Type syntax
    case _ => do_that()                 
}

Scala in Action, Nilanjan Raychaudhuri, Chapter 2 (with a few slight changes for additional clarity)

Having stepped through it, this still makes a lot of sense – especially the reappearance in the pattern of the object:Type syntax from class and method declarations. Jumping ahead a little (but not too much) I can also see that I can use the matched item reference (e.g. “s”) on the right hand side of the rocket (“=>”):

def my_method(arg:AnyRef) = arg match {  // match the arg against the following cases
    case s:String => “this works as I know I’ve got a String “ + s
    case _ => do_that()                 
}

Now I’m ready to kick it up the next notch and try on some “Infix Operator Patterns”.  First off, I’m forearmed – I know what an Infix Operator is.  Next up, my initial reaction is that this syntax looks a little impenetrable:

scala>  List(1, 2, 3, 4) match {
            case firstItem :: secondItem :: restOfTheItems => List(firstItem, secondItem)
            case _ => Nil
        }
scala>  List[Int] = List(1, 2)

Scala in Action, Nilanjan Raychaudhuri, Chapter 2 (with a few slight changes for additional clarity)

I must admit, the first time I read this It didn’t parse at all.  Having now crept up on it slowly, I think I see why.  It’s not the sudden abundance of double colons; rather it’s the purpose of the match.  We’re using things here to do some extraction, and the case statement is in some ways just a wrapper to ensure that nothing untoward goes on when there isn’t a firstItem and secondItem present. 

There’s also the issue that as this example has suddenly moved to the REPL, and we’re just defining our match as a one-off, operating on an explicit List literal which will always match the first pattern. Consequently the second pattern seems a little superfluous.  That confused me, as I thought I was missing something to begin with.

Finally (for now) we’re throwing some additional guard clauses into the mix.  Again, I’m forearmed, having met guard clauses before.

def rangeMatcher(num:Int) = num match {
    case within10  if within10  <= 10               => println(“within 0 to 10”)
    case within100 if within100 <= 100              => println(“within 11 to 100”)
    case beyond100 if beyond100 < Integer.MAX_VALUE => println(“beyond 100”)

}

Looking back, guard clauses were just “if some_comparison_which_evaluates_to_true_or_false”.  In this case we’ve been able to use one where previously we had the type in the type matcher.  If I wrap the guard clause in (redundant) parentheses then it’s a little clearer where the boundaries are

def rangeMatcher(num:Int) = num match {
    case within10 (if within10) <= 10 => println(“within 0 to 10”)
    case within100 (if within100) <= 100 => println(“within 11 to 100”)
    case beyond100 (if beyond100) < Integer.MAX_VALUE => println(“beyond 100”)

}

Yet again, moving my way through it slowly has helped all the pieces fall into place.  Again Scala wins the prize for not-immediately-obvious-syntax, but then that is eclipsed by winning the prize for incredibly-powerful-in-a-usable-way.