Erlang/OTP 完全指南 / 06 - 函数进阶
第 06 章:函数进阶 — 匿名函数、高阶函数与列表推导
函数是 Erlang 中的一等公民(First-class Citizen)。本章学习匿名函数(fun)、高阶函数(Higher-order Function)和列表推导(List Comprehension)。
6.1 匿名函数(Fun)
6.1.1 基本语法
%% 基本匿名函数
Square = fun(X) -> X * X end.
Square(5). %% 25
%% 多参数
Add = fun(X, Y) -> X + Y end.
Add(3, 4). %% 7
%% 多子句匿名函数
Abs = fun(X) when X >= 0 -> X;
(X) -> -X
end.
Abs(-5). %% 5
6.1.2 闭包(Closure)
匿名函数可以捕获外部变量(形成闭包):
%% 闭包捕获外部变量
Multiplier = fun(N) ->
fun(X) -> X * N end
end.
Triple = Multiplier(3).
Triple(10). %% 30
FiveTimes = Multiplier(5).
FiveTimes(10). %% 50
%% 每个闭包独立持有自己的变量
Greet = fun(Name) ->
fun() -> io:format("Hello, ~s!~n", [Name]) end
end.
HelloAlice = Greet("Alice").
HelloBob = Greet("Bob").
HelloAlice(). %% Hello, Alice!
HelloBob(). %% Hello, Bob!
6.1.3 函数引用
%% 引用已有模块函数
F = fun io:format/2.
F("Hello~n", []).
%% 简写语法(& 操作符,Erlang 不原生支持,Elixir 支持)
%% 在 Erlang 中使用 fun 模块:函数/参数个数
lists:map(fun erlang:abs/1, [-1, -2, 3]). %% [1, 2, 3]
%% 函数引用也可以用在 export 中
-export([handle/1]).
%% 将函数传递给其他函数
lists:foreach(fun io:format/1, ["Hello\n", "World\n"]).
6.2 高阶函数
6.2.1 什么是高阶函数?
接受函数作为参数或返回函数的函数:
%% 接受函数作为参数
apply_twice(F, X) ->
F(F(X)).
apply_twice(fun(X) -> X + 1 end, 5). %% 7
%% 返回函数
add(N) ->
fun(X) -> X + N end.
Add5 = add(5).
Add5(10). %% 15
6.2.2 lists 模块中的高阶函数
| 函数 | 作用 | 示例 |
|---|---|---|
lists:map(F, List) | 对每个元素应用函数 | lists:map(fun(X) -> X*2 end, [1,2,3]) → [2,4,6] |
lists:filter(F, List) | 过滤元素 | lists:filter(fun(X) -> X > 2 end, [1,2,3,4]) → [3,4] |
lists:foldl(F, Acc, List) | 左折叠 | lists:foldl(fun(X,S) -> X+S end, 0, [1,2,3]) → 6 |
lists:foldr(F, Acc, List) | 右折叠 | 类似 foldl,但从右往左 |
lists:any(F, List) | 任一满足 | lists:any(fun(X) -> X > 3 end, [1,2,3]) → false |
lists:all(F, List) | 全部满足 | lists:all(fun(X) -> X > 0 end, [1,2,3]) → true |
lists:foreach(F, List) | 遍历(无返回值) | lists:foreach(fun(X) -> io:format("~p~n", [X]) end, [1,2]) |
lists:sort(F, List) | 自定义排序 | lists:sort(fun(A,B) -> A > B end, [3,1,2]) → [3,2,1] |
lists:partition(F, List) | 分区 | lists:partition(fun(X) -> X > 2 end, [1,2,3,4]) → {[3,4],[1,2]} |
lists:dropwhile(F, List) | 删除前缀满足条件的 | lists:dropwhile(fun(X) -> X < 3 end, [1,2,3,4]) → [3,4] |
lists:takewhile(F, List) | 取前缀满足条件的 | lists:takewhile(fun(X) -> X < 3 end, [1,2,3,4]) → [1,2] |
6.2.3 map 详解
%% 基本用法
lists:map(fun(X) -> X * 2 end, [1, 2, 3]).
%% [2, 4, 6]
%% 提取嵌套字段
Users = [{alice, 25}, {bob, 30}, {charlie, 22}],
Names = lists:map(fun({Name, _}) -> Name end, Users).
%% [alice, bob, charlie]
%% 字符串处理
Lines = [" hello ", " world "],
Trimmed = lists:map(fun string:trim/1, Lines).
%% ["hello", "world"]
%% 链式处理
Result = lists:map(fun(X) -> X * 2 end,
lists:map(fun(X) -> X + 1 end, [1, 2, 3])).
%% [4, 6, 8]
6.2.4 foldl 详解
foldl 是最强大的列表操作,几乎所有列表操作都可以用 foldl 实现:
%% 求和
lists:foldl(fun(X, Acc) -> X + Acc end, 0, [1, 2, 3, 4, 5]).
%% 15
%% 求最大值
lists:foldl(fun(X, Max) -> max(X, Max end), 0, [3, 1, 4, 1, 5]).
%% 5
%% 计数
lists:foldl(fun(X, Count) when X > 3 -> Count + 1;
(_, Count) -> Count
end, 0, [1, 2, 3, 4, 5]).
%% 2
%% 构建 Map
lists:foldl(fun({K, V}, Acc) -> Acc#{K => V} end,
#{},
[{name, "Alice"}, {age, 25}, {city, "Beijing"}]).
%% #{name => "Alice", age => 25, city => "Beijing"}
%% 分组
group_by(Fun, List) ->
lists:foldl(fun(Item, Acc) ->
Key = Fun(Item),
Group = maps:get(Key, Acc, []),
Acc#{Key => [Item | Group]}
end, #{}, List).
group_by(fun(X) -> X rem 2 end, [1,2,3,4,5,6]).
%% #{0 => [6,4,2], 1 => [5,3,1]}
6.2.5 filter 与 partition
%% 过滤偶数
Evens = lists:filter(fun(X) -> X rem 2 =:= 0 end, [1,2,3,4,5,6]).
%% [2, 4, 6]
%% 过滤有效用户
ValidUsers = lists:filter(fun(#{active := A}) -> A end, Users).
%% 分区:满足条件的和不满足的
{Pos, Neg} = lists:partition(fun(X) -> X >= 0 end, [-1, 2, -3, 4, -5]).
%% {[2, 4], [-1, -3, -5]}
%% 反过滤
Reject = fun(Pred, List) ->
lists:filter(fun(X) -> not Pred(X) end, List)
end.
6.3 列表推导(List Comprehension)
6.3.1 基本语法
%% [表达式 || 生成器, ..., 守卫]
%% 语法:[Expr || Generator, ..., Guard]
%% 基本用法
[X * 2 || X <- [1, 2, 3, 4, 5]].
%% [2, 4, 6, 8, 10]
%% 带过滤器
[X || X <- [1,2,3,4,5,6], X rem 2 =:= 0].
%% [2, 4, 6]
%% 多个生成器
[{X, Y} || X <- [1,2], Y <- [a,b]].
%% [{1,a},{1,b},{2,a},{2,b}]
%% 多个条件
[{X, Y} || X <- [1,2,3,4,5], Y <- [1,2,3,4,5],
X + Y =:= 6, X < Y].
%% [{1,5},{2,4}]
6.3.2 生成器类型
%% 列表生成器
[X || X <- [1, 2, 3]].
%% 二进制生成器
<< <<(X * 2)>> || <<X>> <= <<1, 2, 3>> >>.
%% <<2, 4, 6>>
%% Map 生成器
[{K, V} || {K, V} := #{name => "Alice", age => 25}].
%% [{name, "Alice"}, {age, 25}]
%% 范围生成器(整数范围)
[X || X <- lists:seq(1, 10)].
%% [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[X * X || X <- lists:seq(1, 5)].
%% [1, 4, 9, 16, 25]
6.3.3 实用示例
%% 质数判断(简单版)
is_prime(N) when N < 2 -> false;
is_prime(2) -> true;
is_prime(N) ->
not lists:any(fun(X) -> N rem X =:= 0 end,
lists:seq(2, trunc(math:sqrt(N)))).
%% 生成质数列表
Primes = [X || X <- lists:seq(2, 100), is_prime(X)].
%% [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
%% 字符串处理
Words = ["Hello", "World", "Erlang"],
Upper = [string:uppercase(W) || W <- Words].
%% ["HELLO", "WORLD", "ERLANG"]
%% 嵌套列表展平
Nested = [[1, 2], [3, 4], [5]],
Flat = [X || Sub <- Nested, X <- Sub].
%% [1, 2, 3, 4, 5]
%% 文件过滤
Files = ["data.txt", "image.png", "log.txt", "doc.pdf"],
TxtFiles = [F || F <- Files, filename:extension(F) =:= ".txt"].
%% ["data.txt", "log.txt"]
%% 矩阵转置
transpose([[]|_]) -> [];
transpose(Matrix) ->
[lists:map(fun hd/1, Matrix) | transpose(lists:map(fun tl/1, Matrix))].
transpose([[1,2,3],[4,5,6],[7,8,9]]).
%% [[1,4,7],[2,5,8],[3,6,9]]
%% 笛卡尔积
cartesian(A, B) ->
[{X, Y} || X <- A, Y <- B].
cartesian([1,2], [a,b,c]).
%% [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]
6.4 管道操作(模拟)
Erlang 没有原生的管道操作符,但可以用以下模式模拟:
%% 方式一:嵌套调用(可读性差)
Result = lists:map(fun(X) -> X * 2 end,
lists:filter(fun(X) -> X > 3 end,
lists:map(fun(X) -> X + 1 end, [1,2,3,4,5]))).
%% 方式二:中间变量(推荐)
Step1 = lists:map(fun(X) -> X + 1 end, [1,2,3,4,5]),
Step2 = lists:filter(fun(X) -> X > 3 end, Step1),
Result = lists:map(fun(X) -> X * 2 end, Step2).
%% [8, 10, 12]
%% 方式三:自定义管道函数
pipe(Input, Funs) ->
lists:foldl(fun(F, Acc) -> F(Acc) end, Input, Funs).
pipe([1,2,3,4,5], [
fun(Xs) -> lists:map(fun(X) -> X + 1 end, Xs) end,
fun(Xs) -> lists:filter(fun(X) -> X > 3 end, Xs) end,
fun(Xs) -> lists:map(fun(X) -> X * 2 end, Xs) end
]).
%% [8, 10, 12]
6.5 函数组合
%% 函数组合
compose(F, G) ->
fun(X) -> F(G(X)) end.
%% 使用
Double = fun(X) -> X * 2 end.
AddOne = fun(X) -> X + 1 end.
DoubleThenAddOne = compose(AddOne, Double),
DoubleThenAddOne(5). %% 11 (5*2+1)
AddOneThenDouble = compose(Double, AddOne),
AddOneThenDouble(5). %% 12 ((5+1)*2)
%% 管道组合(从左到右)
pipe2(F, G) ->
fun(X) -> G(F(X)) end.
6.6 柯里化(模拟)
Erlang 不原生支持柯里化,但可以模拟:
%% 柯里化模拟
curry2(F) ->
fun(X) -> fun(Y) -> F(X, Y) end end.
Add = fun(X, Y) -> X + Y end.
CurriedAdd = curry2(Add).
Add5 = CurriedAdd(5).
Add5(10). %% 15
%% 更实用的版本
curry3(F) ->
fun(X) -> fun(Y) -> fun(Z) -> F(X, Y, Z) end end end.
6.7 实战:数据处理器
%% data_processor.erl
-module(data_processor).
-export([process_sales/1, top_n/2, summarize/1]).
-type sale() :: #{product := string(), amount := float(), region := atom()}.
-type summary() :: #{total := float(), count := integer(), avg := float()}.
%% 模拟销售数据
-spec process_sales([sale()]) -> #{atom() => summary()}.
process_sales(Sales) ->
%% 按地区分组
ByRegion = group_by(fun(#{region := R}) -> R end, Sales),
%% 计算每个地区的汇总
maps:map(fun(_Region, RegionSales) -> summarize(RegionSales) end, ByRegion).
-spec summarize([sale()]) -> summary().
summarize(Sales) ->
Total = lists:foldl(fun(#{amount := A}, Acc) -> A + Acc end, 0, Sales),
Count = length(Sales),
#{total => Total, count => Count, avg => Total / Count}.
-spec top_n([sale()], integer()) -> [sale()].
top_n(Sales, N) ->
Sorted = lists:sort(fun(#{amount := A}, #{amount := B}) -> A >= B end, Sales),
lists:sublist(Sorted, N).
%% 内部函数
group_by(Fun, List) ->
lists:foldl(fun(Item, Acc) ->
Key = Fun(Item),
Group = maps:get(Key, Acc, []),
Acc#{Key => [Item | Group]}
end, #{}, List).
$ erl
1> c(data_processor).
{ok, data_processor}
2> Sales = [
#{product => "Laptop", amount => 999.99, region => north},
#{product => "Phone", amount => 699.99, region => south},
#{product => "Tablet", amount => 449.99, region => north},
#{product => "Watch", amount => 299.99, region => south}
].
3> data_processor:process_sales(Sales).
#{north =>
#{avg => 724.99, count => 2, total => 1449.98},
south =>
#{avg => 499.99, count => 2, total => 999.98}}
6.8 实战:事件处理器
%% event_handler.erl
-module(event_handler).
-export([new/0, add_handler/3, handle_event/2]).
-type handler() :: {atom(), fun()}.
-type handlers() :: [handler()].
-spec new() -> handlers().
new() -> [].
-spec add_handler(atom(), fun(), handlers()) -> handlers().
add_handler(EventType, Fun, Handlers) ->
[{EventType, Fun} | Handlers].
-spec handle_event(term(), handlers()) -> ok.
handle_event(Event, Handlers) ->
lists:foreach(
fun({Type, Fun}) ->
case Event of
{Type, Data} -> Fun(Data);
_ -> ok
end
end,
Handlers).
6.9 注意事项
⚠️ 常见陷阱
| 陷阱 | 说明 |
|---|---|
| 闭包捕获变量引用 | 闭包捕获变量的值,不是引用(不可变) |
| 列表推导不是惰性的 | 所有元素立即计算,大数据需注意内存 |
| ++ 效率低 | 列表拼接 O(n),优先使用 IO list 或 cons |
| foldl vs foldr | foldl 更高效(尾递归),优先使用 foldl |
| 过多嵌套 | 嵌套的 map/filter/foldl 难以阅读,拆分使用中间变量 |
💡 最佳实践
- 优先使用
lists:foldl/3而非手写递归 - 简单映射用列表推导,复杂逻辑用
lists:map/filter - 复杂数据管道用中间变量,保持可读性
- 闭包避免捕获大对象,防止内存泄漏
- 善用模式匹配在函数参数中解构
6.10 扩展阅读
- 📖 lists module — 官方文档
- 📖 Learn You Some Erlang - Higher-order Functions
- 📖 Erlang Reference Manual - Funs