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
- // do something with first middle last
- }
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:
- // define name data object.
- // Notice toString is a lazy val. This means value is calculated only once.
- scala> case class Name(first:String, middle:String, last:String) {
- | override lazy val toString="%s, %s %s" format (last, first,middle)
- | }
- defined class Name
- // toString formats name nicely.
- scala> Name("Jesse","Dale","Eichar")
- res1: Name = Eichar, Jesse Dale
- scala> def readName() = {
- | //maybe load from a file or database
- | Name("Jesse","Dale","Eichar") :: Name("Jody","","Garnett") :: Name("Andrea","","Antonello"):: Nil
- | }
- readName: ()List[Name]
- scala> def firstLastName(name:Name) = {
- | // we are putting _ because we don't care about middle name
- | val Name( first, _, last ) = name
- | (first, last)
- | }
- firstLastName: (Name)(String, String)
- // convert the list of Names to a list of tuples of first and last name
- scala> readName().map( firstLastName _ )
- res2: List[(String, String)] = List((Jesse,Eichar), (Jody,Garnett), (Andrea,Antonello))
- // print all first names starting with J
- scala> for( Name(first,_,_) <- readName; if (first.startsWith("J"))) println(first)
- Jesse
- Jody
- // print the first and middle parts of the first name in the list
- scala> readName() match {
- | // the :: _ indicates that we are matching against a list but we don't care about the rest of the list
- | case Name(f,m,_) :: _ => println( f, m)
- | case _ => println("Not a list of names")
- | }
- (Jesse,Dale)
Ah! The lightbulb just went off over using case classes for matching! So I just went and replaced some tuples that were bothering me in some of my code. Thanks!
ReplyDelete