高阶函数:Enum 模块
Enum 模块提供了一组操作可枚举数据(列表、映射等)的函数,是 Elixir 中使用最频繁的模块。
Enum.map — 变换
# 将每个元素乘以 2
Enum.map([1, 2, 3], fn x -> x * 2 end)
# [2, 4, 6]
# 提取结构体字段
users = [%{name: "Alice"}, %{name: "Bob"}]
Enum.map(users, &(&1.name))
# ["Alice", "Bob"]
Enum.filter — 过滤
Enum.filter([1,2,3,4,5], &(rem(&1, 2) == 0))
# [2, 4](保留偶数)
# reject 是 filter 的反面
Enum.reject([1,2,3,4,5], &(rem(&1, 2) == 0))
# [1, 3, 5](过滤偶数)
Enum.reduce — 归约
# 求和
Enum.reduce([1,2,3,4,5], 0, fn x, acc -> x + acc end)
# 15
# 构建映射(频率统计)
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
Enum.reduce(words, %{}, fn word, acc ->
Map.update(acc, word, 1, &(&1 + 1))
end)
# %{"apple" => 3, "banana" => 2, "cherry" => 1}
其他常用 Enum 函数
Enum.sort([3,1,2]) # [1, 2, 3]
Enum.sort_by(users, &(&1.age)) # 按 age 排序
Enum.uniq([1,2,2,3]) # [1, 2, 3]
Enum.group_by(users, &(&1.role)) # 按 role 分组
Enum.flat_map([[1,2],[3,4]], &(&1)) # [1, 2, 3, 4]
Enum.zip([1,2,3], ["a","b","c"]) # [{1,"a"},{2,"b"},{3,"c"}]
Enum.any?([1,2,3], &(&1 > 2)) # true
Enum.all?([1,2,3], &(&1 > 0)) # true
Enum.count([1,2,3]) # 3
Enum.min([3,1,2]) / Enum.max([3,1,2]) # 1 / 3
Enum.take([1,2,3,4], 2) # [1, 2]
Enum.chunk_every([1,2,3,4], 2) # [[1,2],[3,4]]
匿名函数
Elixir 有两种写匿名函数的语法:
# 完整 fn 语法
double = fn x -> x * 2 end
double.(5) # 10(注意调用时需要加点 .)
# 捕获运算符 & 简写(常用于 Enum 回调)
double = &(&1 * 2) # &1 是第一个参数
add = &(&1 + &2) # &1, &2 分别是第一、二个参数
# 捕获已命名函数
Enum.map(["a","b"], &String.upcase/1)
# ["A", "B"]
# &String.upcase/1 捕获 arity 为 1 的 String.upcase 函数
# 多子句匿名函数
classify = fn
x when x > 0 -> :positive
x when x < 0 -> :negative
0 -> :zero
end
闭包
匿名函数会捕获创建时的外部环境,形成闭包:
defmodule Counter do
def make_adder(n) do
fn x -> x + n end # 闭包捕获 n
end
end
add5 = Counter.make_adder(5)
add10 = Counter.make_adder(10)
add5.(3) # 8
add10.(3) # 13
递归与尾递归优化
Elixir 没有循环语句(for 是推导式,不是循环)。重复操作通过递归实现。BEAM 对尾递归(最后一步是递归调用)做了优化,使其不会增长调用栈。
defmodule MyList do
# 普通递归求长度(非尾递归,会增长调用栈)
def length_naive([]), do: 0
def length_naive([_ | rest]), do: 1 + length_naive(rest)
# 尾递归版本(使用累加器)
def length_tail(list), do: length_tail(list, 0)
defp length_tail([], acc), do: acc
defp length_tail([_ | rest], acc), do: length_tail(rest, acc + 1)
# 最后一步直接跳转到递归调用,无需保存栈帧
# 尾递归反转列表
def reverse(list), do: reverse(list, [])
defp reverse([], acc), do: acc
defp reverse([h | t], acc), do: reverse(t, [h | acc])
end
实际中不必手写递归:Enum 模块已经提供了所有常见操作(map/filter/reduce 等),且内部使用了尾递归优化。只有在实现自定义数据结构或算法时才需要手写递归。
Stream:惰性计算
Stream 模块提供惰性(lazy)版本的 Enum 操作。它不立即计算,而是构建一条处理管道,只在需要时(如 Enum.to_list)才执行。适合处理大数据集或无限序列。
# 处理大文件(逐行,不全部载入内存)
File.stream!("huge_log.txt")
|> Stream.filter(&String.contains?(&1, "ERROR"))
|> Stream.map(&String.trim/1)
|> Enum.take(100) # 只取前100条错误,找到即停
# 无限序列
Stream.iterate(1, &(&1 * 2)) # 1, 2, 4, 8, 16, ...
|> Enum.take(10)
# [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
# 生成斐波那契数列
Stream.unfold({0, 1}, fn {a, b} -> {a, {b, a + b}} end)
|> Enum.take(10)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# Enum vs Stream 对比
# Enum:立即计算,每步产生中间列表,适合小数据
# Stream:惰性管道,单次遍历完成所有操作,适合大/无限数据
for 推导式(Comprehension)
# 基本推导:生成 1-10 的平方
for x <- 1..10, do: x * x
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 带过滤器
for x <- 1..10, rem(x, 2) == 0, do: x
# [2, 4, 6, 8, 10]
# 多重生成器(笛卡尔积)
for x <- [1,2], y <- ["a","b"], do: {x, y}
# [{1,"a"},{1,"b"},{2,"a"},{2,"b"}]
# into 收集到映射
for {k, v} <- %{a: 1, b: 2}, into: %{}, do: {k, v * 10}
# %{a: 10, b: 20}
# 模式匹配解构
for %{name: name, age: age} <- users, age >= 18, do: name
defmodule:模块组织
defmodule MyApp.Accounts.User do
@moduledoc """
用户账户模块。
处理用户的创建、认证与权限管理。
"""
@default_role :user # 模块属性(编译时常量)
@doc "创建新用户,返回 {:ok, user} 或 {:error, reason}"
def create(params) do
# ...
end
@doc false # 文档化但不显示在 ExDoc 中
def internal_thing(), do: :ok
end
# 别名简化模块名
alias MyApp.Accounts.User
User.create(params)
# import 引入函数,直接调用
import Enum, only: [map: 2, filter: 2]
map([1,2,3], &(&1 * 2))
实战:CSV 数据处理流水线
defmodule CSVProcessor do
@doc "从 CSV 字符串解析用户数据,返回有效用户列表"
def parse_users(csv_string) do
csv_string
|> String.split("\n", trim: true)
|> Enum.drop(1) # 跳过标题行
|> Enum.map(&parse_row/1)
|> Enum.filter(&valid?/1)
|> Enum.sort_by(&(&1.name))
end
defp parse_row(row) do
case String.split(row, ",") do
[name, age_str, email] ->
%{
name: String.trim(name),
age: parse_age(age_str),
email: String.trim(email)
}
_ -> nil
end
end
defp parse_age(str) do
case Integer.parse(String.trim(str)) do
{n, _} -> n
:error -> nil
end
end
defp valid?(nil), do: false
defp valid?(%{name: ""}), do: false
defp valid?(%{age: nil}), do: false
defp valid?(%{age: age}) when age < 0 or age > 150, do: false
defp valid?(_), do: true
end
# 测试
csv = """
name,age,email
Alice,30,alice@ex.com
Bob,,bob@ex.com
Carol,25,carol@ex.com
"""
CSVProcessor.parse_users(csv)
# [%{name: "Alice", age: 30, email: "alice@ex.com"},
# %{name: "Carol", age: 25, email: "carol@ex.com"}]
本章小结:Elixir 的函数式工具极为丰富:Enum 处理有限数据集;Stream 用于大文件和无限序列;匿名函数与 & 捕获运算符配合 Enum 极为简洁;尾递归优化保证大数据递归的安全;for 推导式优雅地处理多维遍历。下一章深入模式匹配高级用法与宏系统。