Elixir 从零 · 第 20 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 进阶 · LESSON 2035 分钟

只算眼前需要的

用 `Stream` 延迟 map 与 filter,到了 `Enum` 才真正取结果。

01 · 先看完整例子

从无限流里取三个答案

流没有终点,但 take(3) 让程序知道何时停。

Elixir
1
|> Stream.iterate(&(&1 + 1))
|> Stream.filter(fn number -> rem(number, 3) == 0 end)
# 只有需要的数字才继续平方
|> Stream.map(fn number -> %{number: number, square: number * number} end)
|> Enum.take(3)
先对照结果
  • 得到三个 map:%{number: 3, square: 9}、%{number: 6, square: 36}、%{number: 9, square: 81}。
02 · 回头拆代码

从第一行往下读

  1. Stream.iterate/2 生成 1、2、3……,不预先存下无限数字。
  2. filter 只让 3 的倍数通过,map 同时保存原数与平方。
  3. 找到三个结果后,Enum.take/2 停止向上游要数据。
03 · 认清新符号

符号不是暗号

Stream.iterate/2

从一个值开始生成持续序列。

Stream.map/2

登记映射步骤,不立即遍历。

Enum.take/2

只取需要的前几项,并触发计算。

04 · 代码里的概念

把名字和意思对上

01

惰性

先描述计算,直到结果被需要时才执行。

02

消费者

真正拉取并形成结果的函数,如 Enum.take/2、Enum.to_list/1。

05 · 再说清楚

这些写法为什么有用

Enum 通常立即走完整个集合。数据很大,甚至没有尽头时,先做完全部工作并不可能。

Stream 只记住步骤,不马上计算。遇到 Enum.take/2 等消费者时,才按需要拉取数据。

惰性不是免费午餐。小 list 直接用 Enum 更清楚;大数据或多步管道再考虑 Stream。

06 · 自己改一次

合上答案,动手

从 10 开始,取前四个偶数,并给每项加上 :even 标签。

练习起点
10
|> Stream.iterate(&(&1 + 1))
|> Stream.filter(fn number -> rem(number, ____) == 0 end)
|> Stream.map(fn number -> {:even, number} end)
|> Enum.take(____)

目标结果: 得到 [{:even, 10}, {:even, 12}, {:even, 14}, {:even, 16}]。

卡住了,再看提示

偶数除以 2 余 0;一共取 4 项。

运行过以后,再看一种答案
一种答案
10
|> Stream.iterate(&(&1 + 1))
# 只保留偶数
|> Stream.filter(fn number -> rem(number, 2) == 0 end)
|> Stream.map(fn number -> {:even, number} end)
|> Enum.take(4)
想一想:为什么无限流不能直接交给 `Enum.to_list/1`?

它会不停索取下一项,永远等不到完整 list;必须用 take 等操作划定边界。

带走

记住三句

  1. 1

    Stream 描述惰性步骤。

  2. 2

    Enum 消费流并产出结果。

  3. 3

    无限数据必须有明确停止边界。

本课结束代码也跑过一次,就把这一课收好。