18-速构(Comprehension)

    Elixir中,使用枚举类型(Enumerable,如列表)来做循环操作是很常见的,
    通常还搭配过滤(filtering)和映射(mapping)行为。
    速构(comprehensions)就是为此目的诞生的语法糖:把这些常见任务分组,放到特殊的for指令中表达出来。

    例如,我们可以这样,生成原列表中每个元素的平方:

    注意看,<-符号其实是模拟符号的形象。
    这个例子用熟悉(当然,如果你高数课没怎么听那就另当别论)的数学符号表示就是:

    1. S = { X^2 | X [1,4], X N }

    速构由三部分组成:生成器,过滤器和收集式。

    在上面的例子中,n <- [1, 2, 3, 4]就是生成器。
    它字面意思上生成了即将要在速构中使用的数值。任何枚举类型(Enumerable)都可以传递给生成器表达式的右端:

    1. iex> for n <- 1..4, do: n * n
    2. [1, 4, 9, 16]
    1. iex> values = [good: 1, good: 2, bad: 3, good: 4]
    2. iex> for {:good, n} <- values, do: n * n
    3. [1, 4, 16]

    除了使用模式匹配,过滤器也可以用来选择某些特定数值。
    例如我们可以只选择3的倍数,而丢弃其它数值:

    速构过程会丢弃过滤器表达式结果为falsenil的值;其它值都会被保留。

    总的来说,速构提供了比直接使用EnumStream模块的函数更精确的表达。
    不但如此,速构还可以接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,
    删除这些目录下的所有文件:

    1. for dir <- dirs,
    2. file <- File.ls!(dir),
    3. File.regular?(path) do
    4. end

    多生成器还可以用来生成两个列表的笛卡尔积:

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

    关于多生成器、过滤器的更高级些的例子:计算毕达哥拉斯三元数(Pythagorean triples)。
    毕氏三元数一组正整数满足a * a + b * b = c * c,让我们在文件triples.exs里写这个速构:

    1. defmodule Triple do
    2. def pythagorean(n) when n > 0 do
    3. for a <- 1..n,
    4. b <- 1..n,
    5. c <- 1..n,
    6. a + b + c <= n,
    7. a*a + b*b == c*c,
    8. do: {a, b, c}
    9. end
    10. end

    然后,在终端里:

    1. []
    2. [{3, 4, 5}, {4, 3, 5}]
    3. iex> Triple.pythagorean(48)
    4. [{3, 4, 5}, {4, 3, 5}, {5, 12, 13}, {6, 8, 10}, {8, 6, 10}, {8, 15, 17},
    5. {9, 12, 15}, {12, 5, 13}, {12, 9, 15}, {12, 16, 20}, {15, 8, 17}, {16, 12, 20}]

    需要记住的是,在生成器、过滤器或者代码块中赋值的变量,不会暴露到速构外面去。

    速构也支持比特串作为生成器,这种生成器在处理比特流时非常有用。
    下面的例子中,程序接收一个表示像素颜色的比特串(格式为<<像素1的R值,像素1的G值,像素1的B值,
    像素2的R值,像素2的G…>>),把它转换为三元元组的列表:

    1. iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
    2. iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
    3. [{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

    比特串生成器可以和“普通的”枚举类型生成器混合使用,过滤器也是。

    在上面的例子中,速构返回列表作为结果。
    但是,通过使用:into选项,速构的结果可以插入到不同的数据结构中。
    例如,你可以使用比特串生成器加上:into来轻松地移除字符串中的空格:

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

    集合、图、其他字典类型都可以传递给:into选项。总的来说,:into接受任何实现了Collectable协议的数据结构。

    :into选项一个常见的作用是,不用操作键,而改变图中元素的值:

    1. iex> stream = IO.stream(:stdio, :line)
    2. iex> for line <- stream, into: stream do

    现在在终端中输入任意字符串,你会看到同样的内容以大写形式被打印出来。
    不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出:-)。