美文网首页Scala编程与实践程序员
Scala-Functions and Pattern Matc

Scala-Functions and Pattern Matc

作者: pangolulu | 来源:发表于2016-04-17 17:04 被阅读77次

    Case Class

    当要定义复杂的数据类型时,可以使用Case classes。如下面所示,定义一个JSON数据表示:

    {   
        “firstName”: “John”,
        “lastName”: “Smith”,
        “address”: {
            “streetAddress”: “21 2 nd Street”,
            “state”: “NY”,
            “postalCode”: 10021
        },
        “phoneNumbers”: [
            { “type”: “home”, “number”: “212 555 -1234” },
            { “type”: “fax”, “number”: “646 555 -4567” }
        ]
    }
    

    通过Scala的case class可以抽象为:

    abstract class JSON
    case class JSeq (elems: List[JSON]) extends JSON
    case class JObj (bindings: Map[String, JSON]) extends JSON
    case class JNum (num: Double) extends JSON
    case class JStr (str: String) extends JSON
    case class JBool(b: Boolean) extends JSON
    case object JNull extends JSON
    

    所以,可以定义上面的JSON变量为:

    val data = JObj(Map(
      "firstName" -> JStr("John"),
      "lastName" -> JStr("Smith"),
      "address" -> JObj(Map(
        "streetAddress" -> JStr("21 2nd Street"),
        "state" -> JStr("NY"),
        "postalCode" -> JNum(10021)
      )),
      "phoneNumbers" -> JSeq(List(
        JObj(Map(
          "type" -> JStr("home"), "number" -> JStr("212 555-1234")
        )),
        JObj(Map(
          "type" -> JStr("fax"), "number" -> JStr("646 555-4567")
        )) )) ))
    

    Pattern Matching

    如果我们想要用JSON的格式进行打印要怎么做呢?Scala提供的Pattern Matching语法可以非常方便和优雅的写出递归语法。如下定义了打印函数:

    abstract class JSON {
      def show: String = this match {
        case JSeq(elems) => "[" + (elems map (_.show) mkString ", ") + "]"
        case JObj(bindings) =>
          val assocs = bindings map {
            case (key, value) => "\"" + key + "\": " + value.show
          }
          "{" + (assocs mkString ", ") + "}"
        case JNum(num) => num.toString
        case JStr(str) => "\"" + str + "\""
        case JBool(b) => b.toString
        case JNull => "null"
      }
    }
    

    Function

    有一个地方需要讨论一下,以下pattern matching代码块中返回的类型是什么?

    { case (key, value) => key + ”: ” + value }
    

    在前面的打印代码中,map函数需要的参数类型是JBinding => String的函数类型,其中JBindingStringJSONpair,也就是type JBinding = (String, JSON)
    Scala也是一门面向对象语言,其中所有具体的类型都是一种classtrait。函数类型也不例外,比如说JBinding => String的类型其实是Function1[JBinding, String],其中Function1是一个traitJBindingString是类型参数。
    下面是trait Function1的大体表示:

    trait Function1[-A, +R] {
      def apply(x: A): R
    }
    

    其中[-A, +R]表示的是范型中的逆变和协变,以后会在其它文章中介绍。
    综上,上面的pattern matching代码块其实是一个Function1类型的实例,即:

    new Function1[JBinding, String] {
      def apply(x: JBinding) = x match {
        case (key, value) => key + ”: ” + show(value)
      }
    }
    

    将函数定义成trait的好处是我们可以继承函数类型。
    例如Scala中的Map类型继承了函数类型,如下:

    trait Map[Key, Value] extends (Key => Value)
    

    就能通过map(key)的形式,也就是函数调用来由key得到value。
    Scala中的Sequences也是继承了函数类型,如下:

    trait Seq[Elem] extends (Int => Elem)
    

    所以可以通过elems(i)的形式来由序列的下表访问对应的元素。

    Partial Matches

    通过上面的知识可以知道,下面的pattern matching代码块,

    { case "ping" => "pong" }
    

    可以得到一个String => String的函数类型,即:

    val f: String => String = { case "ping" => "pong" }
    
    

    但是如果调用f(”pong”)将会返回MatchError的异常,这显而易见。那么问题来了,“Is there a way to find out whether the function can be applied to a given argument before running it?”
    在Scala中可以这么解决,定义PartialFunction,如下所示:

    val f: PartialFunction[String, String] = { case "ping" => "pong" }
    f.isDefinedAt("ping") // true
    f.isDefinedAt("pong") // false
    

    PartialFunctionFunction的区别就是PartialFunction定义了isDefinedAt函数。如果我们定义{ case "ping" => "pong" }是一个PartialFunction类型,那么Scala编译器将会展开为:

    new PartialFunction[String, String] {
      def apply(x: String) = x match {
      case "ping" => "pong"
      }
      def isDefinedAt(x: String) = x match {
       case "ping" => true
       case _ => false
      }
    }
    

    总结

    这一节中表达JSON数据格式的例子非常有趣,我把完整的代码放在下面,Scala的代码非常简洁。

    abstract class JSON {
      def show: String = this match {
        case JSeq(elems) => "[" + (elems map (_.show) mkString ", ") + "]"
        case JObj(bindings) =>
          val assocs = bindings map {
            case (key, value) => "\"" + key + "\": " + value.show
          }
          "{" + (assocs mkString ", ") + "}"
        case JNum(num) => num.toString
        case JStr(str) => "\"" + str + "\""
        case JBool(b) => b.toString
        case JNull => "null"
      }
    }
    
    case class JSeq(elems: List[JSON]) extends JSON
    case class JObj(bindings: Map[String, JSON]) extends JSON
    case class JNum(num: Double) extends JSON
    case class JStr(str: String) extends JSON
    case class JBool(b: Boolean) extends JSON
    case object JNull extends JSON
    
    object Main {
      def main(args: Array[String]) {
        val data = JObj(Map(
          "firstName" -> JStr("Yu"),
          "lastName" -> JStr("Gong"),
          "address" -> JObj(Map(
            "streetAddress" -> JStr("NY"),
            "state" -> JStr("NY")
          )),
          "phoneNumbers" -> JSeq(List(
            JObj(Map(
              "type" -> JStr("home"), "number" -> JStr("12233")
            )),
            JObj(Map(
              "type" -> JStr("fax"), "number" -> JStr("22222")
            ))
          ))
        ))
    
        println(data.show)
      }
    }
    

    稍微思考一下,如果用传统的面向对象语言(比如Java)来对JSON数据格式进行抽象,可以如何定义呢?
    也可以定义基类JSON和子类JSeq JObj JNum JStr JBool JNull,如果要实现打印函数,可能就需要在每个子类中实现自己的打印函数,也就是写六个show函数。
    如果你有什么想法和思考,欢迎前来讨论。

    相关文章

      网友评论

        本文标题:Scala-Functions and Pattern Matc

        本文链接:https://www.haomeiwen.com/subject/sfbolttx.html