程式扎記: [Scala IA] The Basics : Ch2. Getting Started - Pattern matching

標籤

2016年7月10日 星期日

[Scala IA] The Basics : Ch2. Getting Started - Pattern matching

Pattern matching 
Pattern matching is another functional programming concept introduced in Scala. To start with, Scala pattern matching looks similar to switch case in Java. The example in the following listing, showing the similarity between Scala and Java, takes an integer and prints its ordinal number. 
- Listing 2.2 An ordinal class written in Java 
  1. public class Ordinal {  
  2.     public static void main(String[] args) {  
  3.         ordinal(Integer.parseInt(args[0]));  
  4.     }  
  5.   
  6.     public static void ordinal(int number) {  
  7.         switch(number) {  
  8.             case 1: System.out.println("1st"); break;  
  9.             case 2: System.out.println("2nd"); break;  
  10.             case 3: System.out.println("3rd"); break;  
  11.             case 4: System.out.println("4th"); break;  
  12.             case 5: System.out.println("5th"); break;  
  13.             case 6: System.out.println("6th"); break;  
  14.             case 7: System.out.println("7th"); break;  
  15.             case 8: System.out.println("8th"); break;  
  16.             case 9: System.out.println("9th"); break;  
  17.             case 10: System.out.println("10th"); break;  
  18.             default : System.out.println("Cannot do beyond 10");  
  19.         }  
  20.     }  
  21. }  
Here the argument of the program is parsed to the integer value, and the ordinal method returns the ordinal text of a given number. Right now, it only knows how to handle numbers from 1 to 10. The following listing shows the same example in Scala. 
- Listing 2.3 An ordinal class in Scala 
  1. ordinal(args(0).toInt)  
  2. def ordinal(number:Int) = number match {  
  3.     case 1 => println("1st")  
  4.     case 2 => println("2nd")  
  5.     case 3 => println("3rd")  
  6.     case 4 => println("4th")  
  7.     case 5 => println("5th")  
  8.     case 6 => println("6th")  
  9.     case 7 => println("7th")  
  10.     case 8 => println("8th")  
  11.     case 9 => println("9th")  
  12.     case 10 => println("10th")  
  13.     case _ => println("Cannot do beyond 10")  
  14. }  
Here you’re doing something similar to the previous Java example: taking an input integer value from a command like args and determining the ordinal value of the number. Because Scala can also be used as a scripting language, you don’t have to define an entry point like the main method. And you no longer need to provide a break for each case because in Scala you can’t overflow into other case clauses (causing multiple matches) as in Java, and there’s no default statement. In Scala, default is replaced with case _ to match everything else

The wildcard case is optional and works like a safe fallback option. If you remove it, and none of the existing cases match, you get a match error: 
scala> 2 match { case 1 => "One" }
scala.MatchError: 2 (of class java.lang.Integer)
... 32 elided
This is great because it tells you that you’re missing a case clause, unlike in Java, where if you remove the default and none of the existing cases match, it ignores it without providing any sort of feedback. 

The similarity between Java and Scala pattern matching ends here because Scala takes pattern matching to the next level. In Java you can only use a switch statement with primitives and enums, but in Scala you can pattern match strings and complex values, types, variables, constants, and constructors. More pattern-matching concepts are in the next chapter, particularly constructor matching, but look at an example of a type match. The following example defines a method that takes an input and checks the type of the given object: 
  1. def printType(obj: AnyRef) = obj match {  
  2.     case s: String => println("This is string")  
  3.     case l: List[_] => println("This is List")  
  4.     case a: Array[_] => println("This is an array")  
  5.     case d: java.util.Date => println("This is a date")  
  6. }  
In this example you’re using a Scala type pattern consisting of a variable and a type. This pattern matches any value matched by the type pattern—in this case, String, List[AnyRef], Array[AnyRef], and java.util.Date. When the pattern matches with the type, it binds the variable name to the value. You could do that in Java using the instanceof operator and casting, but this is a more elegant solution. Save this method into the file printType.scala and load the file into the Scala REPL: 
scala> :load printType.scala
Loading printType.scala...
printType: (obj: AnyRef)Unit


scala> printType("Hello")
This is string

scala> printType(List(1,2,3))
This is List

scala> printType(new Array[String](2))
This is an array

scala> printType(new java.util.Date())
This is a date

Scala also allows the infix operation pattern, in which you can specify an infix operator in your pattern. In the infix style, operators are written between the operands—for example, 2 + 2. In the following example, you’re extracting the first and the second elements from the List
scala> List(1, 2, 3, 4) match { case f :: s :: rest => List(f, s) }
res7: List[Int] = List(1, 2)

Here you’re matching 1 to f, 2 to s, and 3 and 4 to the rest of the variables. Think of it 
as what it will take to create a List of 1, 2 ,3, and 4 from the expression f :: s :: rest, and then this will make more sense. Sometimes you need to have a guard clause along with the case statement to have more flexibility during pattern matching. In the following example you’re determining the range in which the given number belongs: 
  1. def rangeMatcher(num:Int) = num match {  
  2.     case within10 if within10 <= 10 => println("with in 0 to 10")  
  3.     case within100 if within100 <= 100 => println("with in 11 to 100")  
  4.     case beyond100 if beyond100 < Integer.MAX_VALUE => println("beyond 100")  
  5. }  
With this new information, revisit the ordinal problem. The previous Scala ordinal example supported only 1 to 10, but the following listing implements that for all integers. 
- Listing 2.4 Ordinal2.scala reimplemented 
  1. val suffixes = List("th""st""nd""rd""th""th""th""th""th""th")  
  2.   
  3. println(ordinal(args(0).toInt))  
  4.   
  5. def ordinal(number:Int) = number match {  
  6.     case tenTo20 if 10 to 20 contains tenTo20 => number + "th"  
  7.     case rest => rest + suffixes(number % 10)  
  8. }  
Here in the new implementation of ordinal you’re using range, which is a collection of integer values between a given start and end. The expression 10 to 20 is 10.to(20) (remember that methods can be used as infix operators). You’re calling the to method in RichInt, and it creates an inclusive range (scala.collection.immutable.Range.Inclusive). You’re calling the contains method on the range to check whether the number belongs to the range. In the last case you’re mapping all the numbers below 10 and beyond 20 to a new variable called rest. This is called variable pattern matching in Scala. You can access elements of a List like array using index positions in the List. You’ll revisit pattern matching in chapter 3 after looking at case classes. It’s time to move on to the last topic of this chapter: exception handling. 

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!