10-枚舉類型和流
==================
[枚舉對象](#101-%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B) <br/>
[積極vs懶惰](#102-%E7%A7%AF%E6%9E%81vs%E6%87%92%E6%83%B0) <br/>
[流](#103-%E6%B5%81) <br/>
## 10.1-枚舉類型
Elixir提供了枚舉類型(enumerables)的概念,使用[Enum模塊]()操作它們。我們已經介紹過兩種枚舉類型:列表和圖。
```
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]
```
Enum模塊為枚舉類型提供了大量函數來變化,排序,分組,過濾和讀取元素。
Enum模塊是開發者最常用的模塊之一。
<br/>
Elixir還提供了范圍(range):
```
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
```
因為Enum模塊在設計時為了適用于不同的數據類型,所以它的API被限制為多數據類型適用的函數。
為了實現某些操作,你可能需要針對某類型使用某特定的模塊。
比如,如果你要在列表中某特定位置插入一個元素,要用[List模塊](http://elixir-lang.org/docs/stable/elixir/List.html)中的List.insert_at/3函數。而向某些類型內插入數據是沒意義的,比如范圍。
Enum中的函數是多態的,因為它們能處理不同的數據類型。
尤其是,模塊中可以適用于不同數據類型的函數,它們是遵循了[Enumerable協議](http://elixir-lang.org/docs/stable/elixir/Enumerable.html)。
我們在后面章節中將討論這個協議。下面將介紹一種特殊的枚舉類型:流。
## 10.2-積極vs懶惰
Enum模塊中的所有函數都是**積極**的。多數函數接受一個枚舉類型,并返回一個列表:
```
iex> odd? = &(rem(&1, 2) != 0)
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]
```
這意味著當使用Enum進行多種操作時,每次操作都生成一個中間列表,直到得出最終結果:
```
iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000
```
上面例子是一個含有多個操作的管道。從一個范圍開始,然后給每個元素乘以3。
該操作將會生成的中間結果是含有100000個元素的列表。
然后我們過濾掉所有偶數,產生又一個新中間結果:一個50000元素的列表。
最后求和,返回結果。
>這個符號的用法似乎和F#中的不一樣啊...
作為一個替代,[流模塊](http://elixir-lang.org/docs/stable/elixir/Stream.html)提供了懶惰的實現:
```
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000
```
與之前Enum的處理不同,流先創建了一系列的計算操作。然后僅當我們把它傳遞給Enum模塊,它才會被調用。流這種方式適用于處理大量的(甚至是無限的)數據集合。
## 10.3-流
流是懶惰的,比起Enum來說。
分步分析一下上面的例子,你會發現流與Enum的區別:
```
iex> 1..100_000 |> Stream.map(&(&1 * 3))
#Stream<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>
```
流操作返回的不是結果列表,而是一個數據類型---流,一個表示要對范圍1..100000使用map操作的動作。
另外,當我們用管道連接多個流操作時:
```
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)
#Stream<[enum: 1..100000, funs: [...]]>
```
流模塊中的函數接受任何枚舉類型為參數,返回一個流。
流模塊還提供了創建流(甚至是無限操作的流)的函數。
例如,```Stream.cycle/1```可以用來創建一個流,它能無限周期性枚舉所提供的參數(小心使用):
```
iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.cycle/1>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
```
另一方面,```Stream.unfold/2```函數可以生成給定的有限值:
```
iex> stream = Stream.unfold("he??o", &String.next_codepoint/1)
#Function<39.75994740/2 in Stream.unfold/2>
iex> Enum.take(stream, 3)
["h", "e", "?"]
```
另一個有趣的函數是```Stream.resource/3```,它可以用來包裹某資源,確保該資源在使用前打開,在用完后關閉(即使中途出現錯誤)。--類似C#中的use{}關鍵字。
比如,我們可以stream一個文件:
```
iex> stream = File.stream!("path/to/file")
#Function<18.16982430/2 in Stream.resource/3>
iex> Enum.take(stream, 10)
```
這個例子讀取了文件的前10行內容。流在處理大文件,或者慢速資源(如網絡)時非常有用。
<br/>
一開始Enum和流模塊中函數的數量多到讓人氣餒。但你會慢慢地熟悉它們。
建議先熟悉Enum模塊,然后因為應用而轉去流模塊中那些相應的,懶惰版的函數。