• Beginning Scala study note(5) Pattern Matching


    The basic functional cornerstones of Scala: immutable data types, passing of functions as parameters and pattern matching.
    1. Basic Pattern Matching
      In Scala, your cases can include types, wildcards, sequences, regular expressions, and so forth.

    scala> def printNum(int: Int) {
    | int match {
    | case 0 => println("Zero")
    | case 1 => println("One")
    | case _ => println("more than one")
    | }
    | }
    printNum: (int: Int)Unit
    scala> printNum(1)
    One
    scala> printNum(0)
    Zero
    scala> printNum(2)
    more than one

       The underscore "_" wildcard matches anything not defined in the cases above it, just like the default keyword in Java. If you try to put a case _ before any other case clauses, the compiler will throw an unreachable code error on the next clause.

    scala> def printNum(int : Int) {
    | int match {
    | case _ => println("more than one")
    | case 0 => println("Zero")
    | }
    | }
    <console>:13: error: unreachable code
    case 0 => println("Zero")
    ^

       Use the -print option in the Scala compiler to understand the Scala compiler expands a pattern into code.

    scalac -print PrintNum.scala
    package <empty> {
      final object PrintNum extends java.lang.Object with ScalaObject {
        def printNum(int: Int): Unit = {
        <synthetic> val temp1: Int = int;
          (temp1: Int) match {
          case 1 => {
          scala.this.Predef.println("One")
          }
          case 0 => {
          scala.this.Predef.println("Zero")
          }
          case _ => {
          scala.this.Predef.println("more than one")
          }
          }
        };
        def this(): object PrintNum = {
        PrintNum.super.this();
        ()
      }
      }
    }

       The difference of Scala's pattern matching and Java's switch statement.

    # Scala
    def fibonacci(in: Int):Int = in match {
      case 0 => 0
      case 1 => 1
      case n => fibonacci(n - 1) + fibonacci(n - 2)
    }
    # Java
    public int fibonacci(int in){
      switch(in){
      case 0: return 0;
      case 1: return 1;
      default: return fibonacci(n - 1) + fibonacci(n - 2);
      }
    }

       You can see:

      1) There's no break statement between cases in Scala.
      2) The last case in Scala assigns the default value to the variable n. Pattern matching in Scala is also an expression that returns a value.
      3) In Scala, we can have multiple tests on a single line:

    case 0 | -1 | -2 => 0
    # responds java
    case 0:
    case -1:
    case -2:
    return 0;

       Scala allows guards to be placed in patterns to test for particular conditions that cannot be tested in the pattern declaration itself.

    def fib2(in: Int):Int = in match {
        case n if n <= 0 => 0
        case 1 => 1
        case n => fib2(n-1) + fib2(n-2)
    }

       Guards are very helpful as the amount of logic gets more complex.

    2. Matching Any Type
      Example:

    scala> val anyList = List(1,"A",2,2.5,'a')
    anyList: List[Any] = List(1, A, 2, 2.5, a)
    scala> for(m <- anyList) {
    | m match {
    | case i: Int => println("Integer: "+i)
    | case s: String => println("String: "+s)
    | case f: Double => println("Double: "+f)
    | case other => println("other: "+other) # 注意other位于其他case之前会导致other之后的case无法访问
    | }
    | }
    Integer: 1
    String: A
    Integer: 2
    Double: 2.5
    other: a

     3. Testing Data Types

      Test an incoming Object to see whether it's a String, an Integer, or something else.

    scala> def test2(in: Any) = in match {
    | case s: String => "String, length "+s.length
    | case i: Int if i > 0 => "Natural Int"
    | case i: Int => "Another Int"
    | case a: AnyRef => a.getClass.getName
    | case _ => "null"
    | }
    test2: (in: Any)java.lang.String
    scala> test2(1)
    res1: java.lang.String = Natural Int
    scala> test2("123")
    res4: java.lang.String = String, length 3
    scala> test2(List(1))
    res5: java.lang.String = scala.collection.immutable.$colon$colon
    scala> test2(1.2)
    res7: java.lang.String = java.lang.Double
    scala> test2(null)
    res8: java.lang.String = null
    # java code
    public String test2(Object in){
        if(in == null)
          return "null";
        if(in instanceof String)
          return "String, length "+(String)in.length();
        if(in instanceof Integer){
          int i = ((Integer)in).intValue();
        if(i>0)
          return "Natural Int";
        return "Another Int";
      }
      return in.getClass().getName();
    }

     4. Pattern Matching in Lists

      In Scala, the cons cell(非空列表) is represented by the '::' case class. '::' is the name of the method and the name of a case class.

    scala> val x = 1
    x: Int = 1
    scala> x :: rest
    res10: List[Int] = List(1, 2, 3, 4)
    # note the symmetry(对称性) between creating and matching
    scala> (x::rest) match{case Nil => ; case xprime::restprime => println(xprime);println(restprime)}
    1
    List(2, 3, 4)

       Then we can extract the head(x) and tail(rest) of the List in pattern matching.

    5. Pattern Matching and Lists
      Use pattern matching to sum up all the odd Ints in a List[Int].

    scala> def sumOdd(in: List[Int]):Int = in match {
    | case Nil => 0
    | case x::rest if x % 2 == 1 => x + sumOdd(rest)
    | case _::rest => sumOdd(rest)
    | }
    sumOdd: (in: List[Int])Int

       If the list is empty, Nil, then we return 0. The next case extracts the first element from the list and tests to see whether it's odd. If it is, we add it to the sum of the rest of the odd numbers in the list. The default case is to ignore the first element of the list and return the sum of the odd numbers in the rest of the list.

      Replace any number of contiguous identical items with just one instance of that item:

    scala> def noPairs[T](in: List[T]): List[T] = in match {
    | case Nil => Nil
    // the first two elements in the list are the same, so we'll call noPairs with a List that excludes the duplicate element
    | case a :: b :: rest if a == b => noPairs(a :: rest)
    // return a List of the first element followed by noPairs run on the rest of the List
    | case a :: rest => a :: noPairs(rest)
    | }
    noPairs: [T](in: List[T])List[T]
    scala> noPairs(List(1,2,3,3))
    res32: List[Int] = List(1, 2, 3)

       Pattern matching can match against constants as well as extract information.

    scala> def ignore(in: List[String]): List[String] = in match {
    | case Nil => Nil
    // if the second element in the List is "ignore" then return the ignore method run on the balance of the List
    | case _ :: "ignore" :: rest => ignore(rest)
    // return a List created with the first element of the List plus the value of applying the ignore method to the rest of the List
    | case x :: rest => x :: ignore(rest)
    | }
    ignore: (in: List[String])List[String]

       We can also use the class test/cast mechanism to find all the Strings in a List[Any].

    scala> def getStrings(in: List[Any]): List[String] = in match {
         |     case Nil => Nil
         |     case (s: String) :: rest => s :: getStrings(rest)
         |     case _ :: rest => getStrings(rest)
         | }
    getStrings: (in: List[Any])List[String]
    scala> getStrings(List(1,3,"2","hello",4.5,'x'))
    res11: List[String] = List(2, hello)

     6. Pattern Matching and Case Classes

      Case classes are classes that get toString, hashCode, and equals methods automatically. It turns out that they also get properties and extractors. Case classes have properties and can be constructed without using the new keyword.

    scala> case class Person(name: String, age: Int, valid: Boolean)
    defined class Person
    scala> val p = Person("David",45,true)
    p: Person = Person(David,45,true)
    scala> p.name
    res39: String = David
    scala> p.hashCode
    res41: Int = -1915761054

       By default, the properties are read-only, and the case class is immutable.

    scala> p.name = "ws"
    <console>:10: error: reassignment to val
    p.name = "ws"
    ^

       You can also make properties mutable:

    scala> case class MPersion(var name: String, var age:Int)
    defined class MPersion
    scala> val mp = MPersion("ws",27)
    mp: MPersion = MPersion(ws,27)
    scala> mp.name
    res42: String = ws
    scala> mp.name = "ly"
    mp.name: String = ly

       How does case class work with pattern matching?

    scala> def older(p: Person):Option[String] = p match {
    | case Person(name,age,true) if age > 35 => Some(name)
    | case _ => None
    | }
    older: (p: Person)Option[String]

       If valid field is true, the age is extracted and compared against a guard. If the guard succeeds, the person's name is returned, otherwise None is returned.

    scala> older(p)
    res47: Option[String] = Some(David)
    scala> older(Person("Fred",73,true))
    res49: Option[String] = Some(Fred)
    scala> older(Person("Jorge",24,true))
    res50: Option[String] = None

     7. Nested Pattern Matching in Case Classes

      Case classes can contain other case classes, and the pattern matching can be nested, Further, case classes can subclass other case classes.

    scala> case class MarriedPerson(override val name: String,
    | override val age: Int,
    | override val valid: Boolean,
    | spouse: Person) extends Person(name,age,valid)
    defined class MarriedPerson
    scala> val sally = MarriedPerson("Sally",24,true,p)
    sally: MarriedPerson = Person(Sally,24,true)

       Create a method that returns the name of someone who is older or has a spouse who is older:

    scala> def mOlder(p: Person): Option[String] = p match{
    | case Person(name,age,true) if age > 35 => Some(name)
    | case MarriedPerson(name,_,_,Person(_,age,true)) if age > 35 => Some(name)
    | case _ => None
    | }
    mOlder: (p: Person)Option[String]
    scala> mOlder(p)
    res51: Option[String] = Some(David)
    scala> mOlder(sally)
    res52: Option[String] = Some(Sally)

     8. Pattern Matching As Functions

      You can also pass pattern matching as a parameter to other methods. Scala compiles a pattern match down to a PartialFunction[A,B], which is a subclass of Function1[A,B]. So a pattern can be passed to any method that takes a single parameter function.
      This allow us to reduce this code snippet:

    list.filter(a => a match {
      case s: String => true
      case _ => false
    })

       into the following snippet:

    list.filter(
      case s: String => true
      case _ => false
    )

       Patterns are instances. In addition to passing them as parameters, they can also be stored for later use.

      In addition to Function1's apply method, PartialFunction has an isDefinedAt method so that you can test to see whether a pattern matches a given value. If it doesn't match, a MatchError will be raised.

    scala> def handleRequest(req: List[String])
    | (exceptions: PartialFunction[List[String],String]): String = 
    | if (exceptions.isDefinedAt(req)) exceptions(req) else 
    | "Handling URL "+req+" in the normal way"
    handleRequest: (req: List[String])(exceptions: PartialFunction[List[String],String])String

       So if the partial function exceptions(the pattern) matches the request req according to the isDefinedAt method, then we allow the request to be handled by the exceptions functon.

    scala> def doApi(call: String, params: List[String]): String = 
    | "Doing API call "+call
    scala> handleRequest("foo" :: Nil){
    | case "api" :: call :: params => doApi(call,params)}
    res54: String = Handling URL List(foo) in the normal way
    scala> handleRequest("api"::"foo"::Nil){
    | case "api"::call::params=>doApi(call,params)
    | }
    res57: String = Doing API call foo
    scala> val one: PartialFunction[Int,String] = {case 1 => "one"}
    one: PartialFunction[Int,String] = <function1>
    scala> one.isDefinedAt(1)
    res58: Boolean = true
    scala> one.isDefinedAt(4)
    res59: Boolean = false

       Partial function can be composed into a single function using the orElse method.

    scala> val f1: PartialFunction[List[String],String] = {
    | case "stuff" :: Nil => "Got some stuff"
    | }
    f1: PartialFunction[List[String],String] = <function1>
    
    scala> val f2: PartialFunction[List[String],String] = {
    | case "other" :: params => "Other: "+params
    | }
    f2: PartialFunction[List[String],String] = <function1>
    
    scala> val f3 = f1 orElse f2
    f3: PartialFunction[List[String],String] = <function1>

       You can pass them into the handleRequest method:

    handleRequest("a"::"b"::Nil)(f3)

       Partial functions replace a lot of the XML configuration files in Java because pattern matching gives you the same declarative facilities as a configuration file.

    def dispatch: LiftRules.DispatchPF = {
    case Req("api" :: "status" :: Nil, "", GetRequest) => status
    case Req("api" :: "messages" :: Nil, "", GetRequest) => getMsgs
    case Req("api" :: "messages" :: "long_poll" :: Nil, "", GetRequest) =>
    waitForMsgs
    case Req("api" :: "messages" :: Nil, "", PostRequest) =>
    () => sendMsg(User.currentUser.map(_.id.is), S)
    case Req("api" :: "follow" :: Nil, _, GetRequest) =>
    following(calcUser)
    case Req("api" :: "followers" :: Nil, _, GetRequest) =>
    followers(calcUser)
    case Req("api" :: "follow" :: Nil, _, PostRequest) =>
    performFollow(S.param("user"))
    }


  • 相关阅读:
    09.Restful规范
    微信小程序 滚动插件 hSwiper2.0
    前端开发中代码仓库的团队使用(Github)
    hDProcess.js文档浏览进度插件
    Javascrtipt 基本排序算法
    NodeWebkit配置文件简介
    JavaScript中call,apply,bind方法的总结
    Javascript 闭包理解
    javascript常用知识点
    微信小程序 滚动插件 hSwiper
  • 原文地址:https://www.cnblogs.com/mengrennwpu/p/6106819.html
Copyright © 2020-2023  润新知