- 元素
- 意義
- 轉譯
- 應用
元素
在Scala
里碴里,一個for
表達式可以包含1
個或多個「生成器」(Generator
)哀托,及其一個可選的yield
子句缺脉;其中痪欲,每個生成器可以包含0
個或多個「守衛(wèi)」(Guard
),及其0
個或多個「定義」(Definition
)攻礼。
領域模型
也就是說业踢,for
表達式可以描述為如下圖所示的模型。
基本形式
依據yield
是否存在礁扮,for
表達式可以歸結為兩種基本形式知举。
-
for
推導式(for comprehension
) -
for
循環(huán)(for loop
)
for推導式
如果for
表達式存在yield
子句,則稱該for
表達式為for
推導式太伊。它最終將使用yield
子句收集結果雇锡,并將其作為for
表達式的值。
for循環(huán)
如果for
表達式不存在yield
子句僚焦,則稱該for
表達式為for
循環(huán)锰提。for
循環(huán)用于執(zhí)行某種副作用,其返回值的類型為Unit
芳悲。
意義
for
表達式是一種重要的編程工具立肘,在某些場景下能夠極大地改善代碼的表達力。
需求:查詢所有產自中國名扛,且價格在
10
元以內的咖啡名稱赛不,及其對應的供應商。
使用高階函數
coffees filter { c =>
c.nationality == "China" && c.price < 10
} flatMap { c =>
suppliers filter {
c.supplierId == _.id
} map {
c.name -> _.name
}
}
使用for推導式
如果使用for
推導式罢洲,可以進一步改善代碼的表達力踢故。
for {
c <- coffees if c.nationality == "China" && c.price < 10
s <- suppliers if s.id == c.supplierId
} yield c.name -> s.name
轉譯
事實上文黎,對于任意的for
推導式,都可以使用map, flatMap, filter
進行表達殿较;而對于任意的for
循環(huán)耸峭,都可以使用foreach, filter
進行表達。
轉譯生成器
一般地淋纲,for
表達式可以包含1
個或多個「生成器」(Generator
)劳闹。
for (x1 <- exp1; x2 <- exp2; ...; xn <- expn) yield expr
當n == 1
for (x1 <- exp1) yield expr
轉譯為
exp1 map { x => expr }
當n >= 2
for (x1 <- exp1; x2 <- exp2; ...; xn <- expn) yield expr
首先,對生成器x1 <- exp1
進行轉譯:
exp1 flatMap { x1 =>
for (x2 <- exp2; ...; xn <- expn) yield expr
}
依次類推洽瞬,分別對x2 <- exp2; x3 <- exp3, ..., x(n_1) <- exp(n_1)
進行轉譯:
exp1 flatMap { x1 =>
exp2 flatMap { x2 =>
...
exp(n-1) flatMap { x(n-1) =>
for(xn <- expn) yield expr
}
}
}
最后本涕,對于最后一個生成器xn <- expn
,則使用map
進行轉譯伙窃。最終for
表達式轉譯為:
exp1 flatMap { x1 =>
exp2 flatMap { x2 =>
...
exp(n-1) flatMap { x(n-1) =>
expn map { xn => expr }
}
}
}
轉譯守衛(wèi)
一般地菩颖,一個生成器可以附加0
個或多個守衛(wèi)。
for (x <- exp if guard1 if guard2 ... if guardn) yield expr
首先为障,在轉譯x <- exp
之前晦闰,首先將guard1
實施于exp
,將其迭代范圍縮小鳍怨。
for {
x <- (exp filter guard1)
if guard2
...
if guardn
} yield expr
以此類推呻右,將guard2, ..., guardn
依次實施于上一迭代的表達式,將其迭代范圍逐漸縮小鞋喇,最終for
推導式轉譯為:
for {
x <- (exp filter guard1 filter guard2 ... filter guardn)
} yield expr
合并守衛(wèi)
但是声滥,每次調用一次filter
都將產生一個中間結果,效率非常低下侦香⌒汛可以使用withFilter
對所有的的守衛(wèi)進行合并。
for {
x <- (exp withFilter guard1 withFilter guard2 ... withFilter guardn)
} yield expr
它等價于:
for {
x <- exp.withFilter(guard1 && guard2 && ... && guardn)
} yield expr
使用withFilter
鄙皇,雖然它也會生成中間人。但是仰挣,其相對于filter
生成昂貴的中間集合伴逸,withFilter
僅僅生成WithFilter
的中間實例,其成本非常低廉膘壶。
轉譯定義
一般地错蝴,一個生成器可以附加0
個或多個值定義。
for (x <- exp; y1 = exp1; y2 = exp2; ...; yn = expn) yield expr
首先颓芭,對y1 = exp1
進行轉譯:
for {
(x, y1) <- for (x <- exp) yield (x, exp1)
y2 = exp2
...
yn = expn
} yield expr
依次類推顷锰,將y2 = exp2, ..., yn = expn
依次實施于上一迭代的表達式,最終for
推導式轉譯為:
for {
(x, y1, y2, ..., yn) <- for (x <- exp) yield (x, exp1, exp2, ..., expn)
} yield expr
轉譯for循環(huán)
一般地亡问,當不存在yield
子句時官紫,轉譯使用map, flatMap
進行轉譯肛宋;但是,不存在yield
子句時束世,則使用foreach
進行轉譯酝陈,相對簡單。
for (x1 <- exp1; x2 <- exp2; ..., xn <- expn) expr
首先毁涉,對生成器x1 <- exp1
進行轉譯:
exp1 foreach { x1 =>
for (x2 <- exp2; ...; xn <- expn) expr
}
依次類推沉帮,分別對x2 <- exp2; x3 <- exp3, ..., xn <- expn
進行轉譯:
exp1 foreach { x1 =>
exp2 foreach { x2 =>
...
expn foreach { xn =>
expr
}
}
}
轉譯模式
事實上,生成器是一個模式匹配的特殊應用贫堰。當對for
表達式進行轉譯時穆壕,其對應的模式匹配規(guī)則將被轉譯為等價的偏函數,并應用于map, flatMap
或foreach
方法之上其屏。
匹配元組
當生成器的模式是一個元組或者是一個變量(可以看成單元素的元組)時喇勋。
for { (x1, x2, ..., xn) <- exp } yield expr
經過轉譯,其模式匹配規(guī)則將被轉譯為等價的偏函數漫玄,它自動過濾了不匹配的元組茄蚯。
exp map { case { (x1, x2, ..., xn) => expr }
一般模式
一般地,如果生成器中的模式不是元組或者變量時睦优,它是一個一般的模式渗常。
for { pat <- exp } yield expr
首先,模式匹配規(guī)則將被轉譯為等價的偏函數汗盘,并進行一次withFilter
操作皱碘,以便過濾掉不匹配的元素;然后隐孽,再將該模式匹配轉譯為等價的偏函數癌椿。
exp withFilter {
case pat => true
case _ => false
} map {
case pat => expr
}
泛化
事實上,for
表達式是對容器C[A]
操作的語法糖表示菱阵。其中踢俄,C[A]
是一個擁有foreach, map, flatMap, filter
方法集合的容器。
trait C[A] {
def map[B](f: A => B): C[B]
def flatMap[B](f: A => C[B]): C[B]
def filter(p: A => Boolean): C[A]
def foreach(op: A => Unit): Unit
}
在Scala
的標準庫中晴及,滿足這種特征的容器有很多都办。例如,Array, Seq, Set, Map, Range, Iterator, Stream, Option
等所有容器虑稼。
接下來將以Option
為例琳钉,講解這幾個算子的實現方式,及其加深理解for
表達式的工作機制蛛倦。
遞歸結構
sealed trait Option[+A] { self =>
def isEmpty: Boolean
def get: A
}
case class Some[+A](x: A) extends Option[A] {
def isEmpty = false
def get = x
}
case object None extends Option[Nothing] {
def isEmpty = true
def get = throw new NoSuchElementException("None.get")
}
實現算子
sealed trait Option[+A] {
def isEmpty: Boolean
def get: A
def map[B](f: A => B): Option[B] =
if (isEmpty) None else Some(f(get))
def flatMap[B](f: A => Option[B]): Option[B] =
if (isEmpty) None else f(get)
def foreach[U](f: A => U): Unit =
if (!isEmpty) f(get)
def filter(p: A => Boolean): Option[A] =
if (isEmpty || p(get)) this else None
}
提升效率
使用filter
將每次返回一個新的Option
實例歌懒,成本相對昂貴。因此溯壶,可以使用withFilter
替代filter
及皂,這樣的設計實現如下三個效果甫男。
- 合并
for
表達式中的所有過濾條件; - 保證
map, flatMap, foreach
操作發(fā)生在過濾條件之后躲庄,以便提高效率查剖; - 操作
withFilter
,可以避免生成昂貴的噪窘,類型為Option
的中間實例笋庄;而以相對廉價的,類型為WithFilter
的中間實例代替倔监。
sealed trait Option[+A] { self =>
def withFilter(p: A => Boolean): WithFilter = new WithFilter(p)
class WithFilter(p: A => Boolean) {
def map[B](f: A => B): Option[B] =
self filter p map f
def flatMap[B](f: A => Option[B]): Option[B] =
self filter p flatMap f
def foreach[U](f: A => U): Unit =
self filter p foreach f
def withFilter(q: A => Boolean): WithFilter =
new WithFilter(x => p(x) && q(x))
}
}