Skip to content

函数值与闭包 ​

函数也可以当作值:赋给变量、作参数、作返回值、放进数组或结构体字段。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 库

cpro 目前处于测试阶段