Scala-Functions and Pattern Matching

Case Class

當(dāng)要定義復(fù)雜的數(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的格式進(jìn)行打印要怎么做呢伐蒋?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也是一門面向?qū)ο笳Z言玻熙,其中所有具體的類型都是一種classtrait否彩。函數(shù)類型也不例外,比如說JBinding => String的類型其實(shí)是Function1[JBinding, String]嗦随,其中Function1是一個trait胳搞,JBindingString是類型參數(shù)。
下面是trait Function1的大體表示:

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

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

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ù)調(diào)用來由key得到value。
Scala中的Sequences也是繼承了函數(shù)類型袭蝗,如下:

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

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

Partial Matches

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

{ case "ping" => "pong" }

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

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

但是如果調(diào)用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é)

這一節(jié)中表達(dá)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)的面向?qū)ο笳Z言(比如Java)來對JSON數(shù)據(jù)格式進(jìn)行抽象为朋,可以如何定義呢臂拓?
也可以定義基類JSON和子類JSeq JObj JNum JStr JBool JNull,如果要實(shí)現(xiàn)打印函數(shù)潜腻,可能就需要在每個子類中實(shí)現(xiàn)自己的打印函數(shù)埃儿,也就是寫六個show函數(shù)。
如果你有什么想法和思考融涣,歡迎前來討論童番。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市威鹿,隨后出現(xiàn)的幾起案子剃斧,更是在濱河造成了極大的恐慌,老刑警劉巖忽你,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幼东,死亡現(xiàn)場離奇詭異,居然都是意外死亡科雳,警方通過查閱死者的電腦和手機(jī)根蟹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糟秘,“玉大人简逮,你說我怎么就攤上這事∧蜃” “怎么了散庶?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凌净。 經(jīng)常有香客問我悲龟,道長,這世上最難降的妖魔是什么冰寻? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任须教,我火速辦了婚禮,結(jié)果婚禮上斩芭,老公的妹妹穿的比我還像新娘没卸。我一直安慰自己,他們只是感情好秒旋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布约计。 她就那樣靜靜地躺著,像睡著了一般迁筛。 火紅的嫁衣襯著肌膚如雪煤蚌。 梳的紋絲不亂的頭發(fā)上耕挨,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機(jī)與錄音尉桩,去河邊找鬼筒占。 笑死,一個胖子當(dāng)著我的面吹牛蜘犁,可吹牛的內(nèi)容都是我干的翰苫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼这橙,長吁一口氣:“原來是場噩夢啊……” “哼奏窑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屈扎,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤埃唯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鹰晨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墨叛,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年模蜡,在試婚紗的時候發(fā)現(xiàn)自己被綠了漠趁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡忍疾,死狀恐怖闯传,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝昆,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布叠必,位于F島的核電站荚孵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏纬朝。R本人自食惡果不足惜收叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望共苛。 院中可真熱鬧判没,春花似錦、人聲如沸隅茎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辟犀。三九已至俏竞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魂毁。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工玻佩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人席楚。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓咬崔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烦秩。 傳聞我的和親對象是個殘疾皇子垮斯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容