Scala-Functions and Pattern Matching

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函数。
如果你有什么想法和思考,欢迎前来讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容