18-列表速構(Comprehension)
========
[生成器和過濾器](#182-%E7%94%9F%E6%88%90%E5%99%A8%E5%92%8C%E8%BF%87%E6%BB%A4%E5%99%A8)<br/>
[比特串生成器](#182-bitstring%E7%94%9F%E6%88%90%E5%99%A8)<br/>
[Into](#183-into)<br/>
>Comprehensions翻譯成“速構”不知道貼不貼切,這參照了《Erlang/OTP in Action》譯者的用辭。
“速構”是函數式語言中常見的概念,它大體上指的是用一套規則(比如從另一個列表,過濾掉一些元素)來生成元素填充新列表。
這個概念我們在中學的數學課上就可能已經接觸過,在大學高數中更為常見:如```{ x | x ∈ N }```,指的是這個集合里所有元素是自然數。
該集合是由自然數集合的元素映射生成過來的。相關知識可見[WIKI](http://en.wikipedia.org/wiki/List_comprehension)。
Elixir中,使用枚舉類型(如列表)來做循環操作是很常見的。對對象列表進行枚舉時,通常要有選擇性地過濾掉其中一些元素,還有可能做一些變換。列表速構(comprehensions)就是為此目的誕生的語法糖:把這些常見任務分組,放到特殊的```for```中執行。
例如,我們可以這樣計算列表中每個元素的平方:
```
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
```
注意看,```<-```符號就是模擬自```∈ ```的形象。
這個例子用熟悉(當然,如果你高數課沒怎么聽那就另當別論)的數學符號表示就是:
```
S = { X^2 | X ∈ [1,4], X ∈ N}
```
這個例子用常見的編程語言去理解,基本上類似于foreach...in...什么的。但是更強大。
一個列表速構由三部分組成:生成器,過濾器和收集器。
##18.2-生成器和過濾器
在上面的例子中,```n <- [1, 2, 3, 4]```就是生成器。它字面意思上生成了即將要在速構中使用的數值。
任何枚舉類型都可以傳遞給生成器表達式的右端:
```
iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]
```
這個例子中的生成器是一個__范圍__。
生成器表達式支持模式匹配,它會忽略所有不匹配的模式。
想象一下如果不用范圍而是用一個鍵值列表,鍵只有```:good```和```:bad```兩種,來計算中間被標記成‘good’的元素的平方:
```
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
```
過濾器能過濾掉某些產生的值。例如我們可以只對奇數進行平方運算:
```
iex> require Integer
iex> for n <- 1..4, Integer.odd?(n), do: n * n
[1, 9]
```
過濾器會保留所有判斷結果是非nil或非false的值。
總的來說,速構比直接使用枚舉或流模塊的函數提供了更精確的表述。
不但如此,速構還接受多個生成器和過濾器。下面就是一個例子,代碼接受目錄列表,刪除這些目錄下的所有文件:
```
for dir <- dirs,
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
File.rm!(path)
end
```
需要記住的是,在速構中,變量賦值這種事應在生成器中進行。因為在過濾器或代碼塊中的賦值操作不會反映到速構外面去。
## 18.2-比特串生成器
速構也支持比特串作為生成器,而且這種生成器在組織處理比特串的流時非常有用。
下面的例子中,程序從二進制數據(表示為<<像素1的R值,像素1的G值,像素1的B值,像素2的R值,像素2的G...>>)中接收一個像素的列表,把它們轉換為元組:
```
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
```
比特串生成器可以和“普通的”枚舉類型生成器混合使用,過濾器也是。
## 18.3-Into
在上面的例子中,速構返回一個列表作為結果。
但是,通過使用```:into```選項,速構的結果可以插入到一個不同的數據結構中。
例如,你可以使用比特串生成器加上```:into```來輕松地構成無空格字符串:
```
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
```
集合、圖以及其他字典類型都可以傳遞給```:into```選項。總的來說,```:into```接受任何實現了_Collectable_協議的數據結構。
例如,IO模塊提供了流。流既是Enumerable也是Collectable。
你可以使用速構實現一個回聲終端,讓其返回任何輸入的東西的大寫形式:
```
iex> stream = IO.stream(:stdio, :line)
iex> for line <- stream, into: stream do
...> String.upcase(line) <> "\n"
...> end
```
現在在終端中輸入任意字符串,你會看到同樣的內容以大寫形式被打印出來。
不幸的是,這個例子會讓你的shell陷入到該速構代碼中,只能用Ctrl+C兩次來退出。