Scala-Functions and Pattern Matching

Case Class

當要定義復雜的數(shù)據(jù)類型時,可以使用Case classes。如下面所示,定義一個JSON數(shù)據(jù)表示:

{   
    “firstName”: “John”,
    “l(fā)astName”: “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語法可以非常方便和優(yōu)雅的寫出遞歸語法。如下定義了打印函數(shù):

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函數(shù)需要的參數(shù)類型是JBinding => String的函數(shù)類型,其中JBindingStringJSONpair,也就是type JBinding = (String, JSON)。
Scala也是一門面向對象語言,其中所有具體的類型都是一種classtrait。函數(shù)類型也不例外,比如說JBinding => String的類型其實是Function1[JBinding, String],其中Function1是一個traitJBindingString是類型參數(shù)。
下面是trait Function1的大體表示:

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

其中[-A, +R]表示的是范型中的逆變和協(xié)變,以后會在其它文章中介紹。
綜上,上面的pattern matching代碼塊其實是一個Function1類型的實例,即:

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

將函數(shù)定義成trait的好處是我們可以繼承函數(shù)類型。
例如Scala中的Map類型繼承了函數(shù)類型,如下:

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

就能通過map(key)的形式,也就是函數(shù)調用來由key得到value。
Scala中的Sequences也是繼承了函數(shù)類型,如下:

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

所以可以通過elems(i)的形式來由序列的下表訪問對應的元素。

Partial Matches

通過上面的知識可以知道,下面的pattern matching代碼塊,

{ case "ping" => "pong" }

可以得到一個String => String的函數(shù)類型,即:

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的區(qū)別就是PartialFunction定義了isDefinedAt函數(shù)。如果我們定義{ 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
  }
}

總結

這一節(jié)中表達JSON數(shù)據(jù)格式的例子非常有趣,我把完整的代碼放在下面,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)
  }
}

稍微思考一下,如果用傳統(tǒng)的面向對象語言(比如Java)來對JSON數(shù)據(jù)格式進行抽象,可以如何定義呢?
也可以定義基類JSON和子類JSeq JObj JNum JStr JBool JNull,如果要實現(xiàn)打印函數(shù),可能就需要在每個子類中實現(xiàn)自己的打印函數(shù),也就是寫六個show函數(shù)。
如果你有什么想法和思考,歡迎前來討論。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容