for表達(dá)式是monad語(yǔ)法糖
先看一組示例:
case class Person(name: String, isMale: Boolean, children: Person*)
val lara = Person("Lara", false)
val bob = Person("Bob", true)
val julie = Person("Julie", false, lara, bob)
val persons = List(lara, bob, julie)
println(
persons filter (p => !p.isMale) flatMap (p =>
(p.children map (c => (p.name, c.name))))
)
println(
for (p <- persons; if !p.isMale; c <- p.children)
yield (p.name, c.name)
)
// output is
// List((Julie,Lara), (Julie,Bob))
Person類包含了人員名稱,是否是男性,以及他的孩子的字段。代碼的意義是找出列表中所有的媽媽和孩子結(jié)對(duì)的名稱。
分別使用了map、flatMap、filter的方式進(jìn)行查詢,還使用了for表達(dá)式完成,得到相同的結(jié)果。
實(shí)際上,Scala編譯器能夠把所有使用yield產(chǎn)生結(jié)果的for表達(dá)式轉(zhuǎn)移為高階方法map、flatMap及filter的組合調(diào)用。所有的不帶yield的for循環(huán)都會(huì)被轉(zhuǎn)移為僅對(duì)filter和foreach的調(diào)用。
for表達(dá)式說(shuō)明
for表達(dá)式形式如下:
for (seq) yield expr
這里,seq由生成器、定義及過(guò)濾器組成序列,以分號(hào)隔開(kāi)。如果在for表達(dá)式中用花括號(hào)代替小括號(hào)包圍表達(dá)式序列,那么分號(hào)是可選的。
比如下面的示例:
for (p <- persons; n = p.name; if (n startsWith "To"))
yield n
for {
p <- persons //生成器
n = p.name //定義
if (n startsWith "To") //過(guò)濾器
} yield n
生成器的形式為patten <- expression,表達(dá)式expression典型的返回值是列表,不過(guò)它可以泛化。模式pattern一一匹配列表里的所有元素。如果匹配成功,模式中的變量將綁定元素的相應(yīng)成分。但即使匹配失敗也不會(huì)拋出MatchError,而只是在迭代中丟棄這個(gè)元素罷了。
所有的for表達(dá)式都以生成器開(kāi)始。如果for表達(dá)式中有若干生成器,那么后面的生成器比前面的變化的更快。
for表達(dá)式的轉(zhuǎn)譯
對(duì)于每一個(gè)Monad來(lái)說(shuō),都支持for表達(dá)式,而每個(gè)for表達(dá)式都可以用三個(gè)高階函數(shù)map、flatMap及filter表達(dá)。
基本的轉(zhuǎn)譯方式
- 帶一個(gè)生成器的for表達(dá)式
for (x <- expr1) yield expr2轉(zhuǎn)譯為expr1.map(x => expr2)
-
以生成器和過(guò)濾器開(kāi)始的for表達(dá)式
for (x <- expr1 if expr2) yield expr3
第一個(gè)表達(dá)式可以轉(zhuǎn)譯成for (x <- expr1 filter (x => expr2)) yield expr3 -
以兩個(gè)生成器開(kāi)始的for表達(dá)式
for (x <- expr1; y <- expr2; seq) yield expr3
假設(shè)seq是任意序列的生成器、定義及過(guò)濾器,也可能為空。兩個(gè)生成器被轉(zhuǎn)譯為flatMap的應(yīng)用:
expr1.flatMap(x => for (y <- expr2; seq) yield expr3 )
這就生成了另一個(gè)傳遞給flatMap的函數(shù)值形式的for表達(dá)式。
再舉個(gè)例子:
// 第一步轉(zhuǎn)譯
for (n <- ns;
o <- os;
p <- ps)
yield n*o*p
// 第二步轉(zhuǎn)譯
ns flatMap {n =>
for(o <- os;
p <- ps)
yield n*o*p}
// 第三步轉(zhuǎn)譯
ns flatMap { n =>
os flatMap { o =>
for(p <- ps)
yield n*o*p}}
// 第四步轉(zhuǎn)譯
ns flatMap {n =>
os flatMap {o =>
{ps map {p => n*o*p}}}}
轉(zhuǎn)譯for循環(huán)
for表達(dá)式也有一個(gè)命令式(imperative)的版本,用于那些你只調(diào)用一個(gè)函數(shù),不返回任何值而僅僅執(zhí)行了副作用,這個(gè)版本去掉了yield聲明。
for循環(huán)的轉(zhuǎn)譯版本只需用到foreach,for (x <- expr1) body,轉(zhuǎn)譯為expr1 foreach (x => body)。
更大的例子是,for (x <- expr1; if expr2; y <- expr3) body。它將被轉(zhuǎn)譯為:
expr1 filter (x => expr2) foreach (x =>
expr3 foreach (y => body))
foreach依然可以使用map來(lái)實(shí)現(xiàn):
class M[A] {
def map[B](f: A => B): M[B] = ...
def flatMap[B](f: A => M[B]): M[B] = ...
def foreach[B](f: A => B): Unit = {
map(f)
()
}
}
foreach可以通過(guò)調(diào)用map并丟掉結(jié)果來(lái)實(shí)現(xiàn)。不過(guò)這么做運(yùn)行效率不高,所以scala允許你用自己的方式定義foreach。
轉(zhuǎn)譯定義
如果for表達(dá)式中內(nèi)嵌定義,如for (x <- expr1; y = expr2; seq) yield expr3。
那么將轉(zhuǎn)譯為for ((x, y) <- for (x <- expr1) yield (x, expr2); seq) yield expr3。
這里每次產(chǎn)生新的x值的時(shí)候,expr2都被重新計(jì)算。所以這可能會(huì)浪費(fèi)計(jì)算資源,造成重復(fù)計(jì)算。
比如下面的例子和更好的寫(xiě)法:
for (x <- 1 to 100; y = expensiveComputationNotInvolvingX)
yield x*y
// better code
val y = expensiveComputationNotInvolvingX
for (x <- 1 to 1000) yield x*y
生成器中的模式
如果生成器的左側(cè)是模式pat而不是簡(jiǎn)單變量,那么轉(zhuǎn)譯方法將變得復(fù)雜很多。
綁定變量元組
for ((x1, ..., xn) <- expr1) yield expr2
轉(zhuǎn)譯為:
expr1.map {case (x1, ..., xn) => expr2}
任意模式
for (pat <- expr1) yield expr2
轉(zhuǎn)譯為:
expr1 filter {
case pat => true
case _ => false
} map {
case pat => expr2
}
即,生成的條目首先經(jīng)過(guò)過(guò)濾并且僅有那些匹配與pat的才會(huì)被映射。因此,這保證了模式匹配生成器不會(huì)拋出MatchError。
小結(jié)
因?yàn)閒or表達(dá)式的轉(zhuǎn)譯僅依賴于map、flatMap和filter的搭配,所以可以吧for表達(dá)式應(yīng)用于大批數(shù)據(jù)類型(這些數(shù)據(jù)類型可以用Monad來(lái)描述和概括)上。
除了列表、數(shù)組之外,Scala標(biāo)準(zhǔn)庫(kù)中還有許多其他類型支持四種方法(map、flatMap、filter、foreach),從而允許for表達(dá)式存在。同樣,如果你自己的數(shù)據(jù)類型定義了需要的方法也可以完美支持for表達(dá)式。如果只定義map、flatMap、filter、foreach這些方法的子集,從而部分支持for表達(dá)式或循環(huán)。
規(guī)則如下:
- 如果定義了map,可以允許單一生成器組成的for表達(dá)式
- 如果定義了flatMap和map,可以允許若干個(gè)生成器組成的for表達(dá)式
- 如果定義了foreach,允許for循環(huán)
- 如果定義了filter,for表達(dá)式中允許以if開(kāi)頭的過(guò)濾器表達(dá)式
for表達(dá)式的轉(zhuǎn)譯發(fā)生在類型檢查之前。這可以保持最大的靈活性,因?yàn)榻酉聛?lái)只需for表達(dá)式展開(kāi)的結(jié)果通過(guò)類型檢查即可。
在函數(shù)式編程中,Monad定制了map、flatMap和filter功能,它可以解釋多種類型的計(jì)算,包括從集合、狀態(tài)和I/O操縱的計(jì)算、回溯計(jì)算以及交易等,不一而足。
轉(zhuǎn)載請(qǐng)注明作者Jason Ding及其出處
jasonding.top
Github博客主頁(yè)(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡(jiǎn)書(shū)主頁(yè)(http://www.itdecent.cn/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進(jìn)入我的博客主頁(yè)