Julia 里函数是一等公民:能存进变量、当参数传、当返回值抛。匿名函数、闭包、可变参数、
do块四件套组合起来,能把很多啰嗦的逻辑压成几行。
square = x -> x^2 # 单表达式匿名函数 square(5) # 25 map(x -> x^2, 1:5) # [1, 4, 9, 16, 25] filter(x -> x % 2 == 0, 1:10) # [2, 4, 6, 8, 10] reduce(+, 1:10) # 55,求和 sum(1:10) # 同样 55,但更直白
我的习惯:一行能说清的转换用匿名函数;超过一行就老老实实 function 定义具名函数,匿名函数堆多了可读性会崩。
function make_counter() count = 0 return () -> (count += 1) end c1 = make_counter() c1() # 1 c1() # 2 c2 = make_counter() c2() # 1 —— c2 有自己独立的 count
闭包常用于回调与延迟计算:把状态封在函数里传出去,外面的人拿不到也改不了 count 本体。再如按因子生成一系列乘法器:
make_multiplier(k) = x -> x * k double = make_multiplier(2) triple = make_multiplier(3) double(5), triple(5) # (10, 15)
function mysum(args...) # 任意个位置参数打包成元组 sum(args) end mysum(1, 2, 3, 4) # 10 function plot_line(x; color="black", width=1) # 分号后是关键字参数 println("画线 x=$x 颜色=$color 线宽=$width") end plot_line(0.5; width=2)
调用侧的 ... 则是解包:
args = (1, 2, 3) mysum(args...) # 等价 mysum(1, 2, 3)
很多 API 的最后一个参数是回调,do 让它读起来像原生控制流:
# 打开文件、用完自动关闭,do 块就是那个回调 open("output.txt", "w") do io println(io, "第一行") println(io, "第二行") end total = mapreduce(x -> x^2, +, 1:10) do x x^2 # do 块版:第一个参数提到外面 end
第一段 open ... do 是 Julia 里最值得立刻学会的惯用法——资源自动释放,不怕忘关文件。
⚠️ 常见坑:
do块传的是单参数匿名函数,它的参数(上例的io)由被调用函数决定。传错了参数名不会报"未定义",而是拿到nothing或错误类型,排查时先查文档确认回调签名。
| 工具 | 什么时候用 | 什么时候别用 |
|---|---|---|
map |
逐元素变换,语义清楚 | 有状态累积时(用 reduce/循环) |
filter |
按谓词筛集合 | 复杂多条件(先写具名谓词函数) |
reduce |
聚合成单值 | 数值求和直接 sum 更快更直白 |
背景:数值积分是"把一个函数变成一个数"的典型高阶函数场景,我们实现梯形公式并支持任意精度策略。第一步,核心实现:
function trapezoid(f, a, b; n=100) # f: 被积函数; a,b: 区间端点; n: 分段数 h = (b - a) / n s = (f(a) + f(b)) / 2 for i in 1:n-1 s += f(a + i * h) # 累加中间节点 end s * h end trapezoid(x -> sin(x), 0, π) # ≈ 1.9998,精确值 2 trapezoid(x -> sin(x), 0, π; n=1000) # ≈ 1.9999999,n 越大越准
操作层面注意调用方式:被积函数是第一个参数(匿名函数现场写),精度用关键字参数 n 调节,不需要记参数顺序。第二步,把它升级成可换公式的版本,体会闭包与"函数返回函数"的组合:
function make_integrator(method) # method 是积分公式,闭包记住它,返回统一签名的积分器 return f -> (a, b; n=100) -> method(f, a, b; n=n) end trape = make_integrator(trapezoid) trape(x -> x^2)(0, 1) # ≈ 0.3333,先造"专属积分器"再喂区间
解读:make_integrator 返回的闭包把"用哪种公式"焊死在函数里,将来增加辛普森公式只需再写一个公式函数,调用侧一行不改——这正是科学计算库接口设计的常见手法。变式:误差控制版再包一层,比较 n 与 2n 两次结果的差,小于容差就停,一个生产可用的积分器就成形了。
其一,x -> x^2 写在 map 外面又忘了赋值,报 UndefVarError——匿名函数不会自动绑定名字,要么 f = x -> ...,要么直接内联。其二,reduce(+, []) 空集合没有初值,抛"空集合上无法归约",解法是给初值 reduce(+, v; init=0),批量处理空文件时必撞。其三,do 块的 end 配对错位,语法报错位置离谱,看到莫名 unexpected 字样先检查 do 与 end 的层次。这三类错都属"语法没错但语义错了"的暗伤,出现时优先怀疑函数值的传递链。
x -> ... 适合一行变换,配合 map/filter 最顺;args... 收集、调用侧 ... 展开,一对镜像语法;do 块把回调写成控制流,open ... do io 立刻用起来。