Any 设计
本文档收敛 Dujie 中 any 的语言定位、使用边界和显式收窄规则。
定位
any 是边界类型,不是万能类型,也不是动态对象系统。
它的职责是承接编译期无法完全静态确定的动态值,主要用于语言与宿主环境的边界交互。
当前最重要的使用场景:
main(props: map<string, any>) -> widgetelement(..., props: map<string, any>, ...)- 少量调试、日志和宿主互操作场景
基本原则
any 只用于动态边界
T -> any 允许
any -> T 不做隐式恢复
any 不参与普通静态运算
any 不作为默认推导结果
允许的方向
装箱
普通值可以进入 any:
- 基础类型
opt<T>list<T>map<K, V>structwidget
这里的“进入”只是语言层意义上的动态边界承载,不代表鼓励在普通静态代码里广泛使用 any。
传递
any 可以:
- 赋值给
any - 作为
any参数传递 - 作为函数返回值返回
- 放进
map<string, any>
禁止的方向
any 不允许直接参与这些操作:
- 算术运算
- 比较运算
- 逻辑运算
- 成员访问
- 下标访问
- 直接作为
if条件 - 普通函数调用
也就是说,any 不会像动态语言那样自动“猜成某种具体值”后继续运算。
显式收窄
第一阶段,any 的显式恢复机制统一使用 is。
支持的最小语法:
x is T
x is T(v)
语义:
x is T:判断运行时值是否属于T,结果为boolx is T(v):判断是否属于T;若成立,把具体值绑定到v
作用域规则:
v只在判断为真的分支中可见v不进入elsev不泄漏到if外部
例如:
let title = props["title"];
if title is string(s) {
text(s)
} else {
panic("title must be string")
}
当前稳定支持的收窄目标
第一阶段稳定支持的目标类型仅限基础类型:
intfloatboolrunestring
对 list<T>、map<K, V>、opt<T>、struct、widget 的 is 判型,后续如需开放,另行设计。
与 main 的关系
当用户把 main 写成动态入口形式时,main(props: map<string, any>) -> widget 是最主要的 any 使用边界之一。
规则:
props["k"]的结果是any- key 不存在时直接
panic - 取出值后,再通过
is做显式收窄
例如:
func main(props: map<string, any>) -> widget {
let title = props["title"];
if title is string(s) {
text(s)
} else {
panic("title must be string")
}
}
与 element 的关系
element.props 第一阶段保持:
map<string, any>
原因:
element是宿主边界- 宿主属性集合天然开放
- 第一阶段不引入联合类型
这里使用 any,不代表普通组件参数和普通静态数据模型也应使用 any。
与类型推导的关系
any 不作为“推导失败后的兜底类型”。
也就是说:
let x = none不会因为推不出类型就变成any[]、{}也不会因为缺上下文就落成list<any>或map<any, any>
只有在以下情况下才进入 any:
- 用户显式写出
any - 边界 API 明确要求
any
与插值字符串的关系
插值字符串允许任意类型。
对 any 而言,插值采用展示语义:
- 如果运行时能拿到更具体的承载值,则展示该值的展示结果
- 如果拿不到更具体信息,则可以回退成类似
<any>的摘要文本
这只是展示行为,不代表 any 在普通表达式中获得了隐式恢复能力。
与 map 键的关系
any 不允许作为 map 的 key 类型。
原因:
- key 需要稳定的键语义
any会把这层语义打散
当前不提供的能力
第一阶段不提供:
typeof(...)match- 基于
any的模式匹配系统 any的隐式字段访问any的隐式函数调用any的隐式转换到基础类型
当前结论
any 的核心定位可以概括为一句话:
any 是用于动态边界和互操作的显式擦除类型,只允许装箱、传递和显式收窄,不允许隐式恢复为具体类型。`