设计原则
语言哲学说明 Norm 重视什么,本页定义一个功能怎样才能进入语言、标准库或官方工具链。它们是面向演进的工程准则,不是语法功能列表。
1. 为可观察行为建立唯一规则
同一种语法在不同上下文中应保持同一种含义。名称解析、求值顺序、复制、相等、nullability 和失败传播不能由框架或后端临时重定义。
当一项能力需要特殊行为时,应先确定它属于语言、标准库还是应用平台,并在对应层建立唯一契约。实现优化和宿主 adapter 只能实现这份契约,不能成为第二套语义来源。
2. 用不同类型表达不同数据关系
结构数据、对象身份和存储位置是三种不同关系,因此分别使用 value、class 和 ref<T>。普通缺失、业务结果和系统异常同样分开表达。
如果一个抽象要求调用方根据文档猜测“这个对象究竟会不会共享”或“这个错误究竟会不会抛出”,说明类型边界还不完整。
3. 信息留在最需要它的位置
声明处决定长期契约,调用点保留理解当前操作所需的信息。
- 普通类型非空,允许 null 时写
?; - 多个参数保留标签,参数名属于公开调用约定;
- 控制流表达式在产生结果的位置写
break; - extension 必须显式声明和导入;
- 反射使用 reified 类型参数进入,不接收类型名字符串。
显式信息应当稳定且有辨识度。重复类型、无意义包装和可以可靠推断的局部细节不属于这个原则。
4. 高级能力仍然服从普通规则
新增能力不应旁路名称解析、类型检查、可见性和求值顺序。
Extension function 复用顶层函数和重载解析;Annotation 是普通 aggregate,并通过名义策略 interface 获得目标、保留和生命周期;结构序列化读取 Core 类型与字段 metadata;模块描述本身也是经过 Norm 编译和求值的程序。
如果一项能力只能依靠宏 DSL、classpath 扫描、字符串方法名或隐式全局注册才能工作,应先重新设计底层语言边界。
5. 默认行为服务长期应用代码
Norm 优先考虑后端服务、业务系统、工具和桌面应用,而不是内核、硬实时或极端类型级编程。
因此语言选择垃圾回收、非空默认、名义类型和确定赋值;标准库选择有界读取、确定性资源关闭和类型化领域异常;工具链选择自包含 CLI 与一致的编辑器语义。
默认值应让普通代码安全、清楚,少数特殊需求再通过显式 API 进入。
6. 语言、标准库、平台和工具链保持分层
| 层次 | 职责 |
|---|---|
| 语言 | 类型、值、调用、控制流和求值语义 |
| 标准库 | 集合、I/O、时间、文件、HTTP、serialization 等通用 API |
| 应用平台 | Web server、数据库、配置、依赖注入和部署模型 |
| 工具链 | 编译、Core、LSP、测试、打包和发布 |
下层不应知道上层框架。HTTP 核心 body 使用 Bytes,JSON 组合位于独立入口;反射提供结构能力,serialization 再决定映射规则。这类分层让能力可以组合,而不会把某个格式或框架写进语言。
7. 一个事实只定义一次
可生成或派生的信息不手工维护第二份。
内建 ABI 由声明式 schema 生成;编译器和 LSP 读取同一 SemanticModel;JSON、XML 与 YAML 复用同一 serialization shape;网站 base、manifest 和公开 URL 由同一配置派生。
文档负责解释概念、边界和导航。实现细节已经由代码唯一确定时,文档链接到源码或 API,而不是复制一份容易漂移的逻辑。
8. 优化位于语义之下
Core canonicalization、definition store、Truffle specialization、结构共享和运行时打包都可以改变程序的表示与执行方式,但不能改变可观察行为。
后端必须消费已解析的 canonical Core,不能重新执行语言级重载或类型推断。缓存以强类型 identity 和真实依赖作为失效边界,不能用文件时间或字符串 key 模拟语义身份。
功能进入项目前的检查
一项新能力至少应能回答:
- 它解决的是语言问题、通用系统能力还是应用框架问题?
- 调用者能否从类型和源码看出共享、失败与生命周期?
- 它是否复用现有名称解析、类型、调用和资源协议?
- 它会不会建立新的 metadata、配置或执行真相源?
- 编译器、LSP、测试和正式发行包是否观察同一行为?
- 删除旧路径后,整体概念数是否仍然可控?
不能清楚回答这些问题时,继续增加 API 通常只会把结构性问题推迟到未来。
下一篇:设计白皮书。