8.6 KiB
07 · 函数与调用
0/22 项已确认。当前推进 07-01;本分类尚未完成选择。
本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。
07-01 函数作为值【进行中】
- 待讨论
明确能否赋值、存入数据、作为参数和返回结果,以及受限情形。
07-02 函数定义与创建
- 待讨论
明确具名函数、匿名函数是否支持;定义何时创建函数、何时可见,重复定义怎样处理。
07-03 可调用目标
- 待讨论
明确哪些值可以调用,调用非函数如何失败;方法、可调用对象若存在,明确接入边界。依赖:对象与类型模型。
07-04 调用的求值策略与顺序
- 待讨论
明确实参是立即求值还是延迟求值,调用目标与实参的求值顺序,以及求值中断后的行为。
07-05 参数传递与修改
- 待讨论
明确形参赋值、修改实参所指数据分别有什么效果;是否支持引用、借用或其他传递方式。依赖:变量与内存模型、类型系统。
07-06 位置参数
- 待讨论
明确按什么顺序绑定,是否支持仅限位置的参数;数量不足或过多怎样处理。
07-07 命名参数
- 待讨论
明确是否支持、参数名是否属于公开接口、与位置参数如何混用,以及未知名称与重复绑定的处理。
07-08 默认参数
- 待讨论
明确求值时机、可访问的名字、默认值之间的依赖、可变默认值的共享行为,以及省略与显式传空值的区别。
07-09 可变参数与展开
- 待讨论
明确是否支持剩余位置参数、剩余命名参数及调用展开;规定收集形式、顺序、重复名称与非法展开。
07-10 绑定失败的统一规则
- 待讨论
明确绑定失败何时发现、是否执行函数体、此前哪些表达式可能已执行,以及错误如何报告。依赖:错误处理。
07-11 返回与无结果
- 待讨论
明确显式返回、执行到末尾、无结果与空值、多结果及接收数量不符的行为。
07-12 递归与名字绑定
- 待讨论
明确自递归、相互递归、局部递归函数是否支持,以及重绑定函数名后递归调用指向什么。依赖:作用域与模块。
07-13 尾调用与调用深度
- 待讨论
明确是否保证尾调用、哪些位置算尾位置,以及调用深度耗尽怎样失败。依赖:控制流与运行时。
07-14 闭包的名字解析
- 待讨论
明确是否支持访问外层局部名字,采用词法作用域还是其他规则,以及遮蔽和未绑定名字的行为。依赖:作用域。
07-15 闭包捕获与共享状态
- 待讨论
明确捕获值还是变量、自动还是显式捕获,以及多个闭包、循环变量与外部修改之间的关系。依赖:变量与内存模型。
07-16 闭包逃逸与生命周期
- 待讨论
明确闭包能否比创建它的调用活得更久,捕获的资源如何保持有效,不能逃逸时如何拒绝。依赖:资源管理与类型系统。
07-17 函数的调用接口
- 待讨论
明确函数签名包含哪些信息、怎样检查兼容性;重载或泛型若存在,明确调用选择的接口。依赖:类型系统。
07-18 函数身份与比较
- 待讨论
明确函数是否可比较、复制后是否保持身份、重复创建的闭包有什么关系,以及能否作为键。依赖:值与相等性模型。
07-19 非普通控制流的调用边界
- 待讨论
明确异常、异步、生成器等若被采用,对调用与返回产生什么影响;只规定函数侧接口。依赖:错误处理、并发与控制流。
07-20 内置函数与外部函数
- 待讨论
明确它们是否遵守同样的参数、返回和错误规则,以及无法兼容时如何声明差异。依赖:内置与标准库、外部互操作。
07-21 语言调用语义与底层约定
- 待讨论
明确哪些行为属于语言保证,哪些由实现决定;外部调用需要哪些额外约定,避免把栈、寄存器布局误当语言规则。
07-22 跨分类一致性验收
- 待讨论
用代表性程序核对上述规则,并解决与其他分类的冲突;没有未解决的阻塞后完成。
当前问题的方案与语言对照
以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。
当前仍是 07-01:函数能否作为值,即能否保存、传参、返回,以及通过变量调用。闭包捕获规则仍留在后续任务。
五种语言怎样做
下面的例子都假定已经定义了一个“输入整数、返回整数”的 increment 函数。取舍是针对我们设计的分析,不代表这些语言的历史设计动机。
| 语言 | 规则与具体行为 | 好处、代价及对我们的启发 |
|---|---|---|
| Python | 函数是对象。f = increment 保存函数,f(3) 调用它;也能放入列表、传参和返回。官方教程 |
行为组合直接;单靠语言运行时的动态检查,部分不合法调用到执行时才发现。支持函数值,不要求我们采用动态类型或 Python 的对象模型。 |
| JS | 函数是可调用的对象。例如 const f = increment; f(3),也能作为参数和结果。ECMAScript 规范 |
回调与组合方便;接口的正确使用需要额外约束。函数具有对象属性,是可单独选择的能力,不必随函数值一起引入。 |
| TS | 延续 JS 的函数值行为,并增加静态函数类型,例如 const f: (x: number) => number = increment。类型标注被擦除,实际执行的是 JS,不会自动获得运行时参数校验。函数类型、类型擦除 |
能提前检查许多接口错误;需要设计类型兼容规则,且静态检查不能代替外部输入验证。函数值能力和检查时机可以分开决定。 |
| Go | 支持有静态类型的函数值,例如 var f func(int) int = increment,随后 f(3);函数也可传参、返回。函数类型变量的零值为 nil。Go 规范 |
调用接口明确;需要处理空函数值等边界。可以支持函数值,同时保留较直接的签名模型;我们不必继承 nil。 |
| Rust | 支持保存、传递和返回函数相关的值,但区分函数项、函数指针和闭包。例如 let f: fn(i32) -> i32 = increment;。捕获环境的闭包不能直接当作普通 fn 指针,通常通过 Fn 等接口使用。Rust 官方书、函数指针规则 |
能明确表达不同调用能力;类型与接口规则更多。一等函数不要求所有可调用值具有同一种类型或底层表示。 |
这些语言都支持广泛的函数值能力,区别主要在类型、对象能力和环境捕获。Rust 的类型区分并不意味着它只支持我们下面的 B。
我们当前的三个选项
- A:函数只作为调用目标。 可以直接调用,不能作为普通数据保存、传递或返回。规则较少,但通用回调需要其他机制。
- B:支持受限的函数引用。 只开放明确的一部分能力,例如仅允许引用静态定义的函数。适合有具体限制目标的语言,需要说明每条限制的理由。
- C:函数是一等值。 允许保存、传参和返回,遵守一般的类型与生命周期规则。随后分别决定匿名函数、闭包和函数类型。
我仍建议 C:它让排序比较器、事件回调、可替换的处理步骤等接口容易表达,同时保留后续设计空间。
选择 C 后,下面的基础行为应成立:
chosen = increment // 保存,不执行
apply(chosen, 3) // apply 内部调用 chosen,得到 4
return increment // 可以返回已有函数
调用非函数应被拒绝;拒绝发生在静态检查还是运行时,依赖类型系统。返回捕获局部变量的函数是否允许,仍待闭包任务决定。
你选择 A、B,还是 C?
讨论来源
《语言设计 07:函数与调用》,整理日期 2026-10-28。章节编号沿用原清单。