3.1 高级函数特性


3.1 高级函数特性

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)

do 块:把"最后一个函数参数"提到外面

很多 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 字样先检查 doend 的层次。这三类错都属"语法没错但语义错了"的暗伤,出现时优先怀疑函数值的传递链。

本节要点回顾

  • 匿名函数 x -> ... 适合一行变换,配合 map/filter 最顺;
  • 闭包封装状态,回调场景的天然选择;
  • args... 收集、调用侧 ... 展开,一对镜像语法;
  • 关键字参数用分号声明,调用时可省略;
  • do把回调写成控制流,open ... do io 立刻用起来。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U