函数值与闭包
函数也可以当作值:赋给变量、作参数、作返回值、放进数组或结构体字段。cpro 有两种函数值:
| 类型写法 | 说明 |
|---|---|
hs(参数类型, ...) 返回类型 | 函数值:指向一个顶层函数,不携带外部变量 |
bibao(参数类型, ...) 返回类型 | 闭包:携带捕获的变量 |
bibao 的中文写法是 闭包,英文是 closure。
cpro
hs double(x z64) z64 { fanhui x * 2 }
rukou() {
bl f leixing hs(z64) z64 = double
dayin(f(21)) // 42
bl factor leixing z64 = 3
bl g leixing bibao(z64) z64 = bibao[factor](x z64) z64 {
fanhui x * factor
}
dayin(g(14)) // 42
}cpro
函数 double(x i64) i64 { 返回 x * 2 }
入口() {
变量 f 类型 函数(i64) i64 = double
打印(f(21)) // 42
变量 factor 类型 i64 = 3
变量 g 类型 闭包(i64) i64 = 闭包[factor](x i64) i64 {
返回 x * factor
}
打印(g(14)) // 42
}cpro
fn double(x i64) i64 { return x * 2 }
rukou() {
var f type fn(i64) i64 = double
print(f(21)) // 42
var factor type i64 = 3
var g type closure(i64) i64 = closure[factor](x i64) i64 {
return x * factor
}
print(g(14)) // 42
}函数值
取函数
直接写顶层函数的名字,不加括号。调用函数值和普通调用写法一样:
cpro
hs inc(x z64) z64 { fanhui x + 1 }
hs dec(x z64) z64 { fanhui x - 1 }
rukou() {
bl f leixing hs(z64) z64 = inc
dayin(f(10)) // 11
f = dec // 可以重新赋值
dayin(f(10)) // 9
}cpro
函数 inc(x i64) i64 { 返回 x + 1 }
函数 dec(x i64) i64 { 返回 x - 1 }
入口() {
变量 f 类型 函数(i64) i64 = inc
打印(f(10)) // 11
f = dec // 可以重新赋值
打印(f(10)) // 9
}cpro
fn inc(x i64) i64 { return x + 1 }
fn dec(x i64) i64 { return x - 1 }
rukou() {
var f type fn(i64) i64 = inc
print(f(10)) // 11
f = dec // 可以重新赋值
print(f(10)) // 9
}函数值的类型要写全参数类型和返回类型,没有返回值写 kongbai,会失败的函数加 kecuo:
cpro
hs() kongbai // 无参数、无返回值
hs(z64) z64 // 一个参数
hs(zifuchuan, z64) zhenjia // 两个参数
hs(zifuchuan) z64 kecuo // 可能失败cpro
函数() void // 无参数、无返回值
函数(i64) i64 // 一个参数
函数(string, i64) bool // 两个参数
函数(string) i64 可错 // 可能失败cpro
fn() void // 无参数、无返回值
fn(i64) i64 // 一个参数
fn(string, i64) bool // 两个参数
fn(string) i64 fails // 可能失败作参数、返回值,放进容器
cpro
hs add(a z64, b z64) z64 { fanhui a + b }
hs sub(a z64, b z64) z64 { fanhui a - b }
hs apply(f hs(z64, z64) z64, a z64, b z64) z64 {
fanhui f(a, b)
}
hs pick(want_add zhenjia) hs(z64, z64) z64 {
ruguo (want_add) {
fanhui add
}
fanhui sub
}
jiegouti Operation {
name leixing zifuchuan,
run leixing hs(z64, z64) z64,
}
rukou() {
dayin(apply(add, 2, 3)) // 5
dayin(pick(jia)(8, 5)) // 3
bl ops leixing [hs(z64, z64) z64] = [add, sub]
jishu (op shuyu ops) {
dayin(op(10, 3)) // 13 7
}
bl o leixing Operation = { name: "加法", run: add }
dayin(o.run(2, 3)) // 5
}cpro
函数 add(a i64, b i64) i64 { 返回 a + b }
函数 sub(a i64, b i64) i64 { 返回 a - b }
函数 apply(f 函数(i64, i64) i64, a i64, b i64) i64 {
返回 f(a, b)
}
函数 pick(want_add bool) 函数(i64, i64) i64 {
如果 (want_add) {
返回 add
}
返回 sub
}
结构体 Operation {
name 类型 string,
run 类型 函数(i64, i64) i64,
}
入口() {
打印(apply(add, 2, 3)) // 5
打印(pick(假)(8, 5)) // 3
变量 ops 类型 [函数(i64, i64) i64] = [add, sub]
计数 (op 属于 ops) {
打印(op(10, 3)) // 13 7
}
变量 o 类型 Operation = { name: "加法", run: add }
打印(o.run(2, 3)) // 5
}cpro
fn add(a i64, b i64) i64 { return a + b }
fn sub(a i64, b i64) i64 { return a - b }
fn apply(f fn(i64, i64) i64, a i64, b i64) i64 {
return f(a, b)
}
fn pick(want_add bool) fn(i64, i64) i64 {
if (want_add) {
return add
}
return sub
}
struct Operation {
name type string,
run type fn(i64, i64) i64,
}
rukou() {
print(apply(add, 2, 3)) // 5
print(pick(false)(8, 5)) // 3
var ops type [fn(i64, i64) i64] = [add, sub]
for (op in ops) {
print(op(10, 3)) // 13 7
}
var o type Operation = { name: "加法", run: add }
print(o.run(2, 3)) // 5
}注意事项
默认参数不生效:通过函数值调用时必须传满全部参数。
cpro
hs greet(name zifuchuan, times z64 = 3) kongbai { }
rukou() {
bl f leixing hs(zifuchuan, z64) kongbai = greet
f("cpro") // ✗ 需要 2 个参数
f("cpro", 3) // ✓
}cpro
函数 greet(name string, times i64 = 3) void { }
入口() {
变量 f 类型 函数(string, i64) void = greet
f("cpro") // ✗ 需要 2 个参数
f("cpro", 3) // ✓
}cpro
fn greet(name string, times i64 = 3) void { }
rukou() {
var f type fn(string, i64) void = greet
f("cpro") // ✗ 需要 2 个参数
f("cpro", 3) // ✓
}会失败的函数值同样要在 changshi 里调用:
cpro
hs parse(s zifuchuan) z64 kecuo {
fanhui sp_kecuo zifuchuan_zhuan_shuzi(s, z64)
}
rukou() {
bl f leixing hs(zifuchuan) z64 kecuo = parse
changshi {
dayin(f("123")) // 123
} buhuo (e) {
dayin(e.xinxi)
}
}cpro
函数 parse(s string) i64 可错 {
返回 上抛_可错 字符串_转_数字(s, i64)
}
入口() {
变量 f 类型 函数(string) i64 可错 = parse
尝试 {
打印(f("123")) // 123
} 捕获 (e) {
打印(e.msg)
}
}cpro
fn parse(s string) i64 fails {
return up_fails string_to_number(s, i64)
}
rukou() {
var f type fn(string) i64 fails = parse
try {
print(f("123")) // 123
} catch (e) {
print(e.msg)
}
}不能当作值的函数:结构体的方法、变参的 C 函数。函数值也不能打印。
闭包
闭包能捕获外层的变量。写法是 bibao[捕获列表](参数) 返回类型 { 函数体 }:
cpro
rukou() {
bl base leixing z64 = 100
bl f leixing bibao(z64) z64 = bibao[base](x z64) z64 {
fanhui x + base
}
dayin(f(5)) // 105
}cpro
入口() {
变量 base 类型 i64 = 100
变量 f 类型 闭包(i64) i64 = 闭包[base](x i64) i64 {
返回 x + base
}
打印(f(5)) // 105
}cpro
rukou() {
var base type i64 = 100
var f type closure(i64) i64 = closure[base](x i64) i64 {
return x + base
}
print(f(5)) // 105
}闭包的类型里不写捕获列表,只写参数和返回类型。
捕获列表必须精确
闭包里用到的每个外层变量都要写进捕获列表,写进去的每个变量都必须用到:
cpro
bl a leixing z64 = 1
bl b leixing z64 = 2
bl f leixing bibao() z64 = bibao[]() z64 { fanhui a } // ✗ 漏写 a
bl g leixing bibao() z64 = bibao[a, b]() z64 { fanhui a } // ✗ b 没用到
bl h leixing bibao() z64 = bibao[a]() z64 { fanhui a } // ✓cpro
变量 a 类型 i64 = 1
变量 b 类型 i64 = 2
变量 f 类型 闭包() i64 = 闭包[]() i64 { 返回 a } // ✗ 漏写 a
变量 g 类型 闭包() i64 = 闭包[a, b]() i64 { 返回 a } // ✗ b 没用到
变量 h 类型 闭包() i64 = 闭包[a]() i64 { 返回 a } // ✓cpro
var a type i64 = 1
var b type i64 = 2
var f type closure() i64 = closure[]() i64 { return a } // ✗ 漏写 a
var g type closure() i64 = closure[a, b]() i64 { return a } // ✗ b 没用到
var h type closure() i64 = closure[a]() i64 { return a } // ✓捕获列表为空时,请改用顶层函数。
捕获的是一份副本
捕获发生在闭包创建的那一刻,之后闭包和外层变量互不影响:
cpro
rukou() {
bl count leixing z64 = 0
bl show leixing bibao() z64 = bibao[count]() z64 {
fanhui count
}
count = 99
dayin(show()) // 0,闭包里是创建时的值
dayin(count) // 99
}cpro
入口() {
变量 count 类型 i64 = 0
变量 show 类型 闭包() i64 = 闭包[count]() i64 {
返回 count
}
count = 99
打印(show()) // 0,闭包里是创建时的值
打印(count) // 99
}cpro
rukou() {
var count type i64 = 0
var show type closure() i64 = closure[count]() i64 {
return count
}
count = 99
print(show()) // 0,闭包里是创建时的值
print(count) // 99
}闭包里修改捕获的变量,改的也是自己那份副本,不影响外层。
用 yidong 捕获
字符串、数组这类值捕获时会复制一份。之后不再需要原变量时,在捕获列表里加 yidong,直接把值交给闭包:
cpro
rukou() {
bl name leixing zifuchuan = "cpro"
bl data leixing [z64] = [1, 2, 3, 4, 5]
bl f leixing bibao() z64 = bibao[name, yidong data]() z64 {
fanhui name.changdu() + data.changdu()
}
dayin(name) // ✓ cpro,按值捕获,原变量仍可用
dayin(f()) // 9
dayin(data) // ✗ data 已经移进闭包
}cpro
入口() {
变量 name 类型 string = "cpro"
变量 data 类型 [i64] = [1, 2, 3, 4, 5]
变量 f 类型 闭包() i64 = 闭包[name, 移动 data]() i64 {
返回 name.length() + data.length()
}
打印(name) // ✓ cpro,按值捕获,原变量仍可用
打印(f()) // 9
打印(data) // ✗ data 已经移进闭包
}cpro
rukou() {
var name type string = "cpro"
var data type [i64] = [1, 2, 3, 4, 5]
var f type closure() i64 = closure[name, move data]() i64 {
return name.length() + data.length()
}
print(name) // ✓ cpro,按值捕获,原变量仍可用
print(f()) // 9
print(data) // ✗ data 已经移进闭包
}能捕获什么
- 可以捕获:所在函数的局部变量和普通参数
- 不用捕获:全局变量、顶层函数,直接使用即可
- 不能捕获:借用参数(
&T)、方法里的实例(如impl Person:p的p)——需要的话先把字段复制到局部变量再捕获 - 不能捕获自己——需要递归时用顶层函数
返回闭包
闭包持有的是副本,所以可以安全地返回:
cpro
hs make_adder(base z64) bibao(z64) z64 {
fanhui bibao[base](x z64) z64 {
fanhui x + base
}
}
rukou() {
bl add10 leixing bibao(z64) z64 = make_adder(10)
dayin(add10(5)) // 15
}cpro
函数 make_adder(base i64) 闭包(i64) i64 {
返回 闭包[base](x i64) i64 {
返回 x + base
}
}
入口() {
变量 add10 类型 闭包(i64) i64 = make_adder(10)
打印(add10(5)) // 15
}cpro
fn make_adder(base i64) closure(i64) i64 {
return closure[base](x i64) i64 {
return x + base
}
}
rukou() {
var add10 type closure(i64) i64 = make_adder(10)
print(add10(5)) // 15
}闭包里的错误要自己处理
闭包写在 changshi 块里,不代表它被调用时也在 changshi 里。闭包里调用会失败的函数,要么自己处理,要么把闭包声明为 kecuo:
cpro
rukou() {
bl s leixing zifuchuan = "42"
bl g leixing bibao() z64 kecuo = bibao[s]() z64 kecuo {
fanhui sp_kecuo zifuchuan_zhuan_shuzi(s, z64)
}
changshi {
dayin(g()) // 42
} buhuo (e) {
dayin(e.xinxi)
}
}cpro
入口() {
变量 s 类型 string = "42"
变量 g 类型 闭包() i64 可错 = 闭包[s]() i64 可错 {
返回 上抛_可错 字符串_转_数字(s, i64)
}
尝试 {
打印(g()) // 42
} 捕获 (e) {
打印(e.msg)
}
}cpro
rukou() {
var s type string = "42"
var g type closure() i64 fails = closure[s]() i64 fails {
return up_fails string_to_number(s, i64)
}
try {
print(g()) // 42
} catch (e) {
print(e.msg)
}
}函数值与闭包的转换
函数值可以自动转成闭包,反过来不行。所以写参数类型时优先用 bibao,函数值和闭包都能传进来:
cpro
hs double(x z64) z64 { fanhui x * 2 }
hs run(f bibao(z64) z64, x z64) z64 {
fanhui f(x)
}
rukou() {
dayin(run(double, 21)) // 42,函数值自动转成闭包
bl factor leixing z64 = 3
dayin(run(bibao[factor](x z64) z64 { fanhui x * factor }, 5)) // 15
}cpro
函数 double(x i64) i64 { 返回 x * 2 }
函数 run(f 闭包(i64) i64, x i64) i64 {
返回 f(x)
}
入口() {
打印(run(double, 21)) // 42,函数值自动转成闭包
变量 factor 类型 i64 = 3
打印(run(闭包[factor](x i64) i64 { 返回 x * factor }, 5)) // 15
}cpro
fn double(x i64) i64 { return x * 2 }
fn run(f closure(i64) i64, x i64) i64 {
return f(x)
}
rukou() {
print(run(double, 21)) // 42,函数值自动转成闭包
var factor type i64 = 3
print(run(closure[factor](x i64) i64 { return x * factor }, 5)) // 15
}签名必须完全一致,参数和返回类型都不做转换。
示例:map 与 filter
cpro
hs map(a [z64], f bibao(z64) z64) [z64] {
bl out leixing [z64] = []
jishu (v shuyu a) {
out.zhuijia(f(v))
}
fanhui out
}
hs filter(a [z64], keep bibao(z64) zhenjia) [z64] {
bl out leixing [z64] = []
jishu (v shuyu a) {
ruguo (keep(v)) {
out.zhuijia(v)
}
}
fanhui out
}
rukou() {
bl nums leixing [z64] = [1, 2, 3, 4, 5, 6]
bl factor leixing z64 = 10
bl limit leixing z64 = 3
dayin(map(nums, bibao[factor](x z64) z64 { fanhui x * factor }))
// [10, 20, 30, 40, 50, 60]
dayin(filter(nums, bibao[limit](x z64) zhenjia { fanhui x > limit }))
// [4, 5, 6]
}cpro
函数 map(a [i64], f 闭包(i64) i64) [i64] {
变量 out 类型 [i64] = []
计数 (v 属于 a) {
out.push(f(v))
}
返回 out
}
函数 filter(a [i64], keep 闭包(i64) bool) [i64] {
变量 out 类型 [i64] = []
计数 (v 属于 a) {
如果 (keep(v)) {
out.push(v)
}
}
返回 out
}
入口() {
变量 nums 类型 [i64] = [1, 2, 3, 4, 5, 6]
变量 factor 类型 i64 = 10
变量 limit 类型 i64 = 3
打印(map(nums, 闭包[factor](x i64) i64 { 返回 x * factor }))
// [10, 20, 30, 40, 50, 60]
打印(filter(nums, 闭包[limit](x i64) bool { 返回 x > limit }))
// [4, 5, 6]
}cpro
fn map(a [i64], f closure(i64) i64) [i64] {
var out type [i64] = []
for (v in a) {
out.push(f(v))
}
return out
}
fn filter(a [i64], keep closure(i64) bool) [i64] {
var out type [i64] = []
for (v in a) {
if (keep(v)) {
out.push(v)
}
}
return out
}
rukou() {
var nums type [i64] = [1, 2, 3, 4, 5, 6]
var factor type i64 = 10
var limit type i64 = 3
print(map(nums, closure[factor](x i64) i64 { return x * factor }))
// [10, 20, 30, 40, 50, 60]
print(filter(nums, closure[limit](x i64) bool { return x > limit }))
// [4, 5, 6]
}不支持
- 不捕获变量的匿名函数(请用顶层函数)
- 函数值之间比较是否相等
- 闭包传给 C 函数作回调,见 调用 C 库
