檢視原始碼 推導式

在 Elixir 中,通常會對 Enumerable 進行迴圈,常會過濾一些結果並將值對映到另一個清單中。推導式是此類建構的語法糖:它們將這些常見任務分組到 for 特殊形式中。

例如,我們可以將整數清單對映到其平方值

iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]

推導式由三部分組成:產生器、篩選器和可收集項。

產生器和篩選器

在上面的表達式中,n <- [1, 2, 3, 4]產生器。它實際上會產生要在推導式中使用的值。任何 Enumerable 都可以在產生器表達式的右側傳遞

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]

除了樣式比對之外,還可以透過篩選器選取一些特定元素。例如,我們可以選取 3 的倍數並捨棄所有其他元素

iex> for n <- 0..5, rem(n, 3) == 0, do: n * n
[0, 9]

推導式會捨棄所有篩選器表達式傳回 falsenil 的元素;選取所有其他值。

與使用 EnumStream 模組中的等效函式相比,推導式通常提供更簡潔的表示方式。此外,推導式還允許提供多個產生器和篩選器。以下是一個範例,它接收目錄清單並取得這些目錄中每個檔案的大小

dirs = ["/home/mikey", "/home/james"]

for dir <- dirs,
    file <- File.ls!(dir),
    path = Path.join(dir, file),
    File.regular?(path) do
  File.stat!(path).size
end

也可以使用多個產生器來計算兩個清單的笛卡兒積

iex> for i <- [:a, :b, :c], j <- [1, 2], do:  {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]

最後,請記住推導式中的變數指派,無論是在產生器、篩選器或區塊中,都不會反映在推導式之外。

位元串產生器

位元串產生器也受支援,而且當您需要理解位元串串流時非常有用。以下範例從二進位檔案接收像素清單,並附有其各自的紅色、綠色和藍色值,並將它們轉換成各包含三個元素的元組

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}]

位元串產生器可以與「一般」可列舉產生器混合,也支援篩選器。

:into 選項

在上述範例中,所有理解都傳回清單作為其結果。不過,可以透過將 :into 選項傳遞給理解,將理解的結果插入不同的資料結構中。

例如,位元串產生器可以用於 :into 選項,以便輕鬆移除字串中的所有空格

iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"

集合、對應和其它字典也可以提供給 :into 選項。一般來說,:into 接受任何實作 Collectable 協定的結構。

:into 的常見使用案例可以是轉換對應中的值

iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}

我們使用串流來做另一個範例。由於 IO 模組提供串流(同時是 EnumerableCollectable),因此可以透過理解實作一個回音終端機,將輸入的任何內容以大寫形式回傳

iex> stream = IO.stream(:stdio, :line)
iex> for line <- stream, into: stream do
...>   String.upcase(line) <> "\n"
...> end

現在在終端機中輸入任何字串,您會看到相同的字串會以大寫印出。很不幸地,這個範例也會讓您的 IEx shell 卡在理解中,因此您需要按兩次 Ctrl+C 才能離開它。 :)

其他選項

理解支援其他選項,例如 :reduce:uniq。以下是進一步瞭解理解的其他資源