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ù)類型适瓦,其中JBinding
是String
和JSON
的pair
竿开,也就是type JBinding = (String, JSON)
。
Scala也是一門面向?qū)ο笳Z言玻熙,其中所有具體的類型都是一種class
或trait
否彩。函數(shù)類型也不例外,比如說JBinding => String
的類型其實(shí)是Function1[JBinding, String]
嗦随,其中Function1
是一個trait
胳搞,JBinding
和String
是類型參數(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
PartialFunction
和Function
的區(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ù)。
如果你有什么想法和思考融涣,歡迎前來討論童番。