Skip to content

RFC-019: 类型级同像性 (Typed Homoiconicity) - 语法即类型

⚠️ 永久实验性声明: 这是一个探索性实验,用于验证"语法即类型"这一语言设计理念的可行性。本 RFC 永不合并,无论结果如何都不会进入 dev/main 分支。实验分支将在完成后被废弃或归档。

  • 实验目标:验证类型级同像性的实现难度和潜在价值
  • 止损线:6 个月无进展则放弃
  • 成功标准:能跑通至少一个用户自定义关键字(完整解析→编译→运行)

不保证会合并到 main 分支,未来可能因为各种原因被拒绝或放弃。请勿在生产环境中使用此特性。

⚠️ 定位说明: 本 RFC 是语言设计思想实验,不提供工程方案。如需实用的可扩展解析器模式,参见 Rust syn::Parse 或 Haskell parsec


摘要

本 RFC 提出一种激进的语言设计实验:让语言的语法结构本身成为类型系统的一部分

核心思想源于 Lisp 的"代码即数据"(同像性),但通过静态类型系统来实现:

  • 语法树(AST)是类型
  • 关键字是类型的预定义实例
  • 用户可以通过定义类型来扩展语言语法

这意味着:语言本身变成可组合、可扩展的"积木"。


动机

为什么做这个实验?

  1. 统一性追求:消除"关键字"这个特殊的语法元素,让一切都是类型和函数
  2. 语言可扩展性:用户可以像定义函数一样定义新的语法结构
  3. 类型安全的宏:传统宏(文本替换)是危险的,类型级同像性可以提供编译期检查
  4. 学习目的:深入理解语言设计的本质

与 Lisp 的关系

Lisp 早已实现"代码即数据":

lisp
; Lisp 代码本身就是 S-expression
(if (> x 0) "positive" "negative")

本实验的差异在于:用静态类型系统强化这个理念


提案

核心概念

1. 语法树即类型(AST as Type)

yaoxiang
// 语法树节点都是类型
If: Type = { condition: Expr, then: Block, else: Block }
While: Type = { condition: Expr, body: Block }
Return: Type = { value: Expr }
Block: Type = { statements: Array[Expr] }
Let: Type = { name: String, value: Expr, body: Expr }
Function: Type = { params: Array[Param], body: Expr }
Call: Type = { func: Expr, args: Array[Expr] }

// 基本类型
Literal: Type = { value: Int }
StringLiteral: Type = { value: String }
Variable: Type = { name: String }

2. 关键字 = 处理类型的函数

yaoxiang
// 求值器是处理这些类型的函数
eval_if: (node: If, env: Env) -> Value = ...
eval_while: (node: While, env: Env) -> Value = ...
eval_return: (node: Return, env: Env) -> Value = ...
eval_block: (node: Block, env: Env) -> Value = ...

// 编译器也可以是函数
compile_if: (node: If, ctx: CompileContext) -> IR = ...
compile_while: (node: While, ctx: CompileContext) -> IR = ...

3. 类型携带解析规则(核心创新)

这是本实验的关键:类型不仅描述数据,还携带如何解析代码的规则

yaoxiang
// 语法规则类型
SyntaxRule: Type = {
    // 如何解析这个类型的代码
    parse: (token_stream: TokenStream) -> (Self, remaining_tokens)

    // 如何将类型实例编译/求值
    compile: (node: Self, ctx: CompileContext) -> IR
    eval: (node: Self, env: Env) -> Value
}

// IF 类型的语法规则
IF: SyntaxRule = {
    // 解析 "if (cond) { then } else { else }"
    parse: (tokens: TokenStream) -> (If, remaining) = {
        consume("if")
        cond = parse_expression(tokens)
        consume("{")
        then_block = parse_block(tokens)
        consume("}")
        consume("else")
        consume("{")
        else_block = parse_block(tokens)
        consume("}")
        return If(cond, then_block, else_block), tokens
    }

    eval: (node: If, env: Env) -> Value = {
        if eval(node.condition, env) != 0 {
            return eval(node.then, env)
        } else {
            return eval(node.else, env)
        }
    }
}

4. 用户自定义语法扩展

用户可以定义自己的"关键字":

yaoxiang
// 用户定义一个新的语法结构:unless
Unless: SyntaxRule = {
    parse: (tokens: TokenStream) -> (If, remaining) = {
        consume("unless")
        cond = parse_expression(tokens)
        consume("{")
        body = parse_block(tokens)
        consume("}")
        // unless 等价于 if (!cond) { body }
        return If(Not(cond), body, Block([])), tokens
    }
}

// 使用
unless x > 0 {
    print("x is not positive")
}

// 展开为
if !(x > 0) {
    print("x is not positive")
}

示例

完整示例:自定义循环语法

yaoxiang
// 定义一个 "times" 循环:n.times { ... } 运行 n 次
TimesLoop: SyntaxRule = {
    parse: (tokens: TokenStream) -> (While, remaining) = {
        receiver = parse_expression(tokens)  // 获取数字
        consume(".times")
        consume("{")
        body = parse_block(tokens)
        consume("}")
        // 转换为 while 循环
        counter_var = gensym("i")
        return While(
            Less(Variable(counter_var), receiver),
            Block([
                body,
                Assign(counter_var, Add(Variable(counter_var), Literal(1)))
            ])
        ), tokens
    }
}

// 使用
5.times {
    print("Hello!")
}

// 展开为
i = 0
while i < 5 {
    print("Hello!")
    i = i + 1
}

示例:模式匹配语法

yaoxiang
// 用户定义模式匹配
Match: SyntaxRule = {
    parse: (tokens: TokenStream) -> (MatchNode, remaining) = {
        subject = parse_expression(tokens)
        consume("{")
        cases = []
        while !check("}") {
            pattern = parse_pattern(tokens)
            consume("=>")
            body = parse_expression(tokens)
            cases.push((pattern, body))
        }
        consume("}")
        return MatchNode(subject, cases), tokens
    }
}

// 使用
match x {
    0 => "zero",
    1 => "one",
    n if n > 10 => "big",
    _ => "other"
}

详细设计

系统架构

┌─────────────────────────────────────────────────────┐
│                    源代码                            │
└─────────────────┬───────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│              语法解析器 (Parser)                     │
│  - 识别关键字                                        │
│  - 找到对应的 SyntaxRule 类型                        │
│  - 调用类型的 parse 方法                             │
└─────────────────┬───────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│              AST (类型实例)                          │
│  If, While, Match, TimesLoop...                     │
└─────────────────┬───────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│              编译器/解释器                           │
│  - 调用类型的 compile/eval 方法                      │
│  - 生成目标代码或执行                                │
└─────────────────────────────────────────────────────┘

关键技术问题

1. 控制流函数化

问题:if 需要只求值一个分支,不能用普通函数调用。

解决方案:传入 thunk(延迟求值)

yaoxiang
// 编译后的内部表示
If: Type = {
    condition: Expr,
    then: () -> Value,  // thunk,延迟求值
    else: () -> Value
}

2. return 的非局部返回

问题:return 需要跳出多层函数。

解决方案:

  • 方案 A:编译期 CPS 变换
  • 方案 B:使用 Result/Either monad
  • 方案 C:限制 return 的作用域

3. 语法歧义

问题:如何区分 if(x > 0) { 1 } 是函数调用还是关键字?

解决方案:

  • 关键字使用特殊语法(如 if ... { } else { }
  • 或通过类型系统约束

4. 无限递归

问题:用户可能定义自引用的语法规则。

解决方案:编译期检测循环依赖


与现有系统的关系

与 RFC-010(统一类型语法)的关系

RFC-010 实现了 name: type = value 的统一语法,本 RFC 是其延伸:

RFC-010本 RFC
变量、函数、类型都是 name: type = value关键字也是 name: type = value
类型是值语法规则也是值
Type 是元类型SyntaxRule 是语法的元类型

与 Lisp/宏的对比

特性Lisp 宏本实验
代码表示S-expression (列表)类型实例
扩展方式defmacro定义 SyntaxRule 类型
类型安全弱(文本替换)强(类型检查)
解析时机运行时/编译时编译期
IDE 支持强(类型信息)

分支计划

实验分支

分支名: exp/typed-homoiconicity
从 dev 分支创建

重要

  • 这是一个实验性分支,不会频繁与 dev 合并
  • 可能长期独立开发
  • 不保证会合并到 main
  • 如果实验失败,分支将被废弃

开发阶段

⚠️ 实验时间上限:6 个月

阶段目标预期时间备注
Phase 1概念验证:用现有语法实现 AST 类型2 周
Phase 2实现基本的求值器2 周关键挑战:if/return 控制流
Phase 3实现 SyntaxRule 类型的解析规则3 周
Phase 4用户自定义语法扩展3 周核心目标:至少跑通一个自定义关键字
Phase 5优化和文档2 周实验结束

超时处理:如果 Phase 2(控制流实现)超过 4 周无进展,应考虑放弃。


权衡

优点

  • 极致统一:消除关键字和普通代码的界限
  • 语言可扩展:用户可以定义自己的语法
  • 类型安全:比传统宏更安全
  • 学习价值:深入理解语言本质

缺点

  • 实现复杂:需要大幅修改编译器
  • 性能担忧:运行时解释可能慢
  • 学习曲线:概念抽象,需要理解类型系统
  • 实用性存疑:可能过度工程

风险

  • 实验可能失败,无法找到实用场景
  • 实现难度超出预期
  • 与现有特性冲突

开放问题

  • [ ] 如何处理语法冲突(用户定义的规则与内置冲突)?
  • [ ] 性能优化方案?
  • [ ] 是否需要语法导入/导出机制?
  • [ ] 如何与现有模块系统集成?

附录

术语表

术语定义
同像性 (Homoiconicity)代码和数据使用同一种表示
语法树 (AST)程序的抽象语法树表示
SyntaxRule携带语法解析规则的类型
Thunk延迟求值的函数包装
CPSContinuation Passing Style,连续传递风格

参考文献


生命周期与归宿

┌─────────────┐
│   草案      │  ← 当前状态
└──────┬──────┘


       ⚠️ 永久实验性分支 (exp/typed-homoiconicity)

       可能的结果:
       ├─► 成功验证 → 归档,永不合并
       ├─► 失败 → 废弃分支
       └─► 超时 → 放弃并废弃

       ⚠️ 无论何种结果,本 RFC 永不合并

⚠️ 重要提醒: 这是一个探索性实验,永不合并。请勿在生产代码中依赖此特性。