One of the principle design goals of Scala is to be "deep" not wide, which means the language attempts to have a small set of rules that can be applied in many different ways in different situations. Pattern matching is one of my favourite examples of this. Pattern matching is commonly seen in match { case... } statements but you will also see it frequently in exception handling, function declaration and, most important for this post, assignment.
Scala does not have multiple assignment like some languages. Instead it has tuples and matching. Tuples are a light-weight way of grouping data in a simple data object.
(1,2.0,'c',"string", true)
. A simple example of a 5 element tuple. Tuples can be up to 22 elements long and can be homogenous or heterogenous. Using this for multiple assignement works something like:
- val (firstname, lastname) = ("Jesse","Eichar")
This is pattern matching.
- scala> ("Jesse","Eichar") match {
- | case (firstname,lastname) => println(firstname,lastname)
- | }
- (Jesse,Eichar)
Notice that in both cases you need the brackets around firstname, lastname. This instructs the compiler that you are matching against a Tuple.
Now the interesting use is with parameter objects. Tuples are a poor substitute for parameter objects because they do not have context. Changing:
- def myMethod( firstname:String, middlename:String, lastname:String) = {...}
to
- def myMethod( name:(String,String,String)) = {...}
Is not a good change because you loose context. What are the 3 strings? The information must go in the Javadocs. A better option:
- case class Name(first:String, middle:String, last:String)
- def myMethod( name:Name ) = {
- val Name(first, middle, last) = name
-
- }
The beauty is that you have an object that you can pass around easily. It is a case class therefore extracting the information is incredibly easy and unlike a tuple it has context and can have methods added easily.
Yes it is longer to write but if you need to reuse the data in several locations the trade off is well worth it in clarity.
Examples:
- scala> case class Name(first:String, middle:String, last:String) {
- | override lazy val toString="%s, %s %s" format (last, first,middle)
- | }
- defined class Name
- scala> Name("Jesse","Dale","Eichar")
- res1: Name = Eichar, Jesse Dale
- scala> def readName() = {
- |
- | Name("Jesse","Dale","Eichar") :: Name("Jody","","Garnett") :: Name("Andrea","","Antonello"):: Nil
- | }
- readName: ()List[Name]
- scala> def firstLastName(name:Name) = {
- |
- | val Name( first, _, last ) = name
- | (first, last)
- | }
- firstLastName: (Name)(String, String)
- scala> readName().map( firstLastName _ )
- res2: List[(String, String)] = List((Jesse,Eichar), (Jody,Garnett), (Andrea,Antonello))
- scala> for( Name(first,_,_) <- readName; if (first.startsWith("J"))) println(first)
- Jesse
- Jody
- scala> readName() match {
- |
- | case Name(f,m,_) :: _ => println( f, m)
- | case _ => println("Not a list of names")
- | }
- (Jesse,Dale)