A number of the “more advanced” features in the Scala language are clear departures from Java. They may mirror the corresponding Java feature, but in syntax the concepts are totally different. Among these “different features” is the concept of pattern matching, something critical to the understanding of exceptions.
Pattern Matching
As is oft parroted on the blogosphere, pattern matching in Scala is really a lot like Java’s switch/case construct. So in Java, one might write something like this:public boolean checkPrime(int number) { // checks if a number between 1 and 10 is prime switch (number) { case 1: return true; case 2: return true; case 3: return true; case 5: return true; case 7: return true; default: return false; } } |
One of the major limitations of switch/case in Java (and really any C derivative language) is that it can only be used on primitives. You can’t use switch/case to test a String, for example (a need which arises more often than one would think). In fact, the most complex type testable within switch/case is the Enum, and even this is just being reduced to its ordinal values under the surface. In short, switch/case is a very crippled hold-over from the dark ages of #include and extern.
Fortunately, the designers of Scala chose not to include this “feature” in their new language. Instead, they implemented a “new” (it’s actually been around in other languages for a long time) concept called pattern matching. At a basic level, it allows algorithms which are very similar to the checkPrime(int) example:
def checkPrime(number:Int):Boolean = { number match { case 1 => return true case 2 => return true case 3 => return true case 5 => return true case 7 => return true case _ => return false } } |
Scala case statements can’t “overflow” into each-other (causing multiple matches) like Java’s can, so even if we weren’t returning values, the algorithm would still be safe. In fact the method can be more concisely expressed as follows:
def checkPrime(number:Int) = number match { case 1 => true case 2 => true case 3 => true case 5 => true case 7 => true case _ => false } |
Now if this were the end of Scala’s pattern matching capabilities, it would still be worth using. However, Scala’s capabilities go far beyond mere integer comparison. For example, Scala’s match statements are not restricted to “primitive” types (which Scala doesn’t have, incidentally). You can just as easily perform pattern matching on strings or even more complex values. For example, the instanceof operation in Scala looks like this:
var obj = performOperation() var cast:Color = obj match { case x:Color => x case _ => null } |
Object obj = performOperation(); Color cast = null; if (obj instanceof Color) { cast = (Color) obj; } |
Case Classes
Now type matching may be cool, but once again it fails to encompass the awesome power afforded by Scala’s match statement. Not only is Scala capable of inspecting the type it is matching, but also values within that type. This probably doesn’t make much sense, so once again we resort to code samples:case class Number(value:Int) def checkPrime(n:Number) = n match { case Number(1) => true case Number(2) => true case Number(3) => true case Number(5) => true case Number(7) => true case Number(_) => false } checkPrime(Number(12)) |
Scalists (is that a word?) like to use case classes in situations where they need a “quick and dirty” class, due to the predefined operations and the conciseness of its instantiation syntax. I personally don’t care for this convention, mainly because case classes have some odd corners which can bite you when you least expect. For one thing, case classes cannot extend other case classes (though they can extend normal classes and normal classes can inherit from case classes). More importantly, case classes become implicitly abstract if they inherit an abstract member which is not implemented. This can lead to some very strange looking compiler errors when attempting pattern matching on what you thought was a valid hierarchy.
Anyway, back to our example. For each case, we’re actually creating a new instance of Number, each with a different value. This is where the significance of the case class instantiation syntax comes into play. Scala then takes our new instance and compares it with the one being matched (and this is all type-checked by the way). Scala sees that the instances are the same type, so it introspects the two instances and compares the property values (in this case, just value). Now this would seem to be massively inefficient, but Scala is able to do some clever things with case classes in pattern matching and everything turns out nicely.
Everything seems sort of intuitive until we reach the final case, which is using our friend the underscore. Of course we could have just written this as the “any case” (case _) but I wanted to demonstrate wildcards in case classes. This statement basically means “match objects of type Number with any value”. This is the case which is matched by our checkPrime(Number(12)) invocation farther down. Oh, and as a side note, if null is passed to this function, Scala will throw a MatchError, proving once again the loveliness of the Scala type system.
Of course, this sort of matching doesn’t seem very useful. All we did was encapsulate an Int within a case class. While this is cool for illustrative purposes, it would be grounds for execution if seen in a real code base. This is where the power of inheritence meets case classes.
class Color(val red:Int, val green:Int, val blue:Int) case class Red(r:Int) extends Color(r, 0, 0) case class Green(g:Int) extends Color(0, g, 0) case class Blue(b:Int) extends Color(0, 0, b) def printColor(c:Color) = c match { case Red(v) => println("Red: " + v) case Green(v) => println("Green: " + v) case Blue(v) => println("Blue: " + v) case col:Color => { print("R: " + col.red + ", ") print("G: " + col.green + ", ") println("B: " + col.blue) } case null => println("Invalid color") } printColor(Red(100)) printColor(Blue(220)) printColor(new Color(100, 200, 50)) printColor(null) |
Red: 100 Blue: 220 R: 100, G: 200, B: 50 Invalid colorThere are a couple of important things about this example. Firstly, you should notice that we’re heavily using the feature in pattern matching which allows us to assign new values as part of the match. In each of the specific color cases (Red, Green, Blue) we’re passing an undefined variable v to the new case class instance. This variable will be assigned the property value of the class if a match is made. So for our first invocation, the matcher finds that we’re looking at an instance of Red. It then retrieves the property value from the instance (red) and assigns that value to the local case parameter x. This value is then usable within the definition of the case.
The second thing which should jump out at you is the use of polymorphic case classes. Here we have several specific types of Color which each take a single value as their property. This value is then passed to the super-constructor (along with a number of literal values, depending on the color). Red, Green and Blue are all case classes, Color is not. We can’t just say case Color(r, g, b) => because Color is just an ordinary class, it cannot be matched in such a fashion.
This pattern is also the first we have seen with a multi-line case. Technically, this is still just a single expression (a scope) which itself contains multiple expressions, but you can still think of it like a switch statement with multiple statements prior to a break.
Finally, if nothing else applies, the instance will match case null and print our “Invalid color” message. You’ll notice we did no explicit null checking, we just let the matcher handle the ugly details for us. Now isn’t that clean?
Case classes are about the closest thing Scala has to Java’s enumerations. Oh Scala does have a type Enumeration that you can do interesting things with, but idiomatically case classes are almost exclusively used in situations where enumerated values would be employed in Java. This of course lends itself to greater flexibility (because case classes may contain values) and better “wow factor” when showing off your code.
Exception Handling
Well I’ve been promising all along that I would somehow tie this in with exceptions and the time is now. It turns out that Scala doesn’t have exception handling, at least not in the way that Java does. Instead, Scala allows you to try/catch any exception in a single block and then perform pattern matching against it.Let’s take our checkPrime() example. It really only handles integers between 1 and 10, so it would be quite nice to test this precondition and throw an exception if it fails. We can do this quite trivially:
def checkPrime(n:Int) = { if (n < 1 || n > 10) { throw new IllegalArgumentException("Only values between 1 and 10") } n match { case 1 => true case 2 => true // ... } } |
So how do we invoke this method, pedantically watching for exceptions? It turns out that the syntax looks surprisingly like pattern matching:
try { checkPrime(12) } catch { case e:IllegalArgumentException => e.printStackTrace() } |
If we wanted to catch all exception types, we could resort to our old friend the underscore:
try { checkPrime(12) } catch { case _ => println("Caught an exception!") } |
import java.sql._ import java.net._ Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:testdb", "sa", "") try { PreparedStatement stmt = conn.prepareStatement("INSERT INTO urls (url) VALUES (?)") stmt.setObject(1, new URL("http://www.codecommit.com")) stmt.executeUpdate() stmt.close() } catch { case e:SQLException => println("Database error") case e:MalformedURLException => println("Bad URL") case e => { println("Some other exception type:") e.printStackTrace() } } finally { conn.close() } |
This example also shows use of the finally block, something which should be quite familiar to any Java developers. In Scala, the finally works precisely the way it does in Java, so there should be no concerns about odd behavior when designing code which requires it.
Conclusion
Scala exceptions are fairly intuitive beasts. They behave almost exactly like Java’s (with the exception of being all unchecked), providing a familiarity and an interoperability with existing Java libraries. The exception handling mechanism however demonstrates considerably more power, making use of Scala’s built-in pattern matching capabilities and thus maintaining a far more internally consistent syntax.
(codecommit)
0 comments: on "Scala for Java Refugees Part 4: Pattern Matching and Exceptions"
Post a Comment