在编译过程中何时/何地发生类型检查
When / where type checking occurs in the compilation process
想知道在编译过程中(在高级别)通常何时进行类型检查(教科书与实践)。我对编译过程的大致理解是:
- 将源代码解析成 AST
- 将 AST 转换为中间表示 IR
- 优化 IR(即 SSA 表格、寄存器分配等)
- 简化IR
- 生成最终输出代码
想知道类型检查是发生在 (1) 和 (2) 之间、(2) 和 (3) 之间,还是发生在 (4) 之后,或者它是否发生在整个过程中,或者其他什么地方。我很想知道面向对象、函数式和逻辑编程的答案(按优先顺序排列),但如果我必须选择一个,那么 OO,例如像 Ruby 这样的动态类型语言,或者静态类型化的函数式语言,例如 Haskell.
静态类型检查通常在 AST 上执行,因此它要么发生在 1 和 2 之间,要么作为 2 的一部分(这意味着 IR 生成器在处理 AST 节点时调用类型检查器的函数 - 的当然 IR 生成器和类型检查器应该仍然存在于不同的 modules/files).
理论上,您可以对 IR 执行类型检查,但这通常至少会导致以下问题之一:
- IR 包含的信息不足,无法捕获您想要的所有错误。
- IR 包含的信息不足,无法在所有情况下生成最佳错误消息。例如,考虑 IR 使用相同的指令表示数组访问和指针算术。现在您想要为具有浮点索引的数组访问产生错误。如果消息是 "Floating point values not allowed as operands to pointer arithmetic",那么当代码不包含任何指针算法时会造成混淆。要求用户知道数组访问表示为指针算法以理解错误消息,这对用户来说不是很友好。
- 您为了类型检查的目的向 IR 添加了大量额外信息,使 IR 变得更加复杂和笨重,但您从中获得的只是以相同方式处理 IR 的能力你会在没有获得任何好处的情况下使用 AST。
通常使用 IR 而不是 AST 意味着您不必处理那么多情况(正是因为 IR 使用相同的指令表示不同的事物)。这是主要的好处。但是,如果您随后为了能够再次以不同的方式处理案例而跳过额外的环节,那么您不妨首先使用 AST。
因此通常首选对 AST¹ 进行类型检查。 GHC(主要 Haskell 编译器)执行 AST 类型检查。
¹ 或者至少是一些非常接近 AST 的东西——例如,在 AST 和最终的 IR 之间可能有一个表示,它在某些方面简化了事情(例如删除扁平化的嵌套表达式),而不会丢失与类型检查相关的信息。
动态类型检查发生在 运行 时间。执行这些动态类型检查的代码要么是解释器的一部分(如果有解释器),要么由代码生成器插入。
Ruby 在解释器中执行类型检查。
想知道在编译过程中(在高级别)通常何时进行类型检查(教科书与实践)。我对编译过程的大致理解是:
- 将源代码解析成 AST
- 将 AST 转换为中间表示 IR
- 优化 IR(即 SSA 表格、寄存器分配等)
- 简化IR
- 生成最终输出代码
想知道类型检查是发生在 (1) 和 (2) 之间、(2) 和 (3) 之间,还是发生在 (4) 之后,或者它是否发生在整个过程中,或者其他什么地方。我很想知道面向对象、函数式和逻辑编程的答案(按优先顺序排列),但如果我必须选择一个,那么 OO,例如像 Ruby 这样的动态类型语言,或者静态类型化的函数式语言,例如 Haskell.
静态类型检查通常在 AST 上执行,因此它要么发生在 1 和 2 之间,要么作为 2 的一部分(这意味着 IR 生成器在处理 AST 节点时调用类型检查器的函数 - 的当然 IR 生成器和类型检查器应该仍然存在于不同的 modules/files).
理论上,您可以对 IR 执行类型检查,但这通常至少会导致以下问题之一:
- IR 包含的信息不足,无法捕获您想要的所有错误。
- IR 包含的信息不足,无法在所有情况下生成最佳错误消息。例如,考虑 IR 使用相同的指令表示数组访问和指针算术。现在您想要为具有浮点索引的数组访问产生错误。如果消息是 "Floating point values not allowed as operands to pointer arithmetic",那么当代码不包含任何指针算法时会造成混淆。要求用户知道数组访问表示为指针算法以理解错误消息,这对用户来说不是很友好。
- 您为了类型检查的目的向 IR 添加了大量额外信息,使 IR 变得更加复杂和笨重,但您从中获得的只是以相同方式处理 IR 的能力你会在没有获得任何好处的情况下使用 AST。
通常使用 IR 而不是 AST 意味着您不必处理那么多情况(正是因为 IR 使用相同的指令表示不同的事物)。这是主要的好处。但是,如果您随后为了能够再次以不同的方式处理案例而跳过额外的环节,那么您不妨首先使用 AST。
因此通常首选对 AST¹ 进行类型检查。 GHC(主要 Haskell 编译器)执行 AST 类型检查。
¹ 或者至少是一些非常接近 AST 的东西——例如,在 AST 和最终的 IR 之间可能有一个表示,它在某些方面简化了事情(例如删除扁平化的嵌套表达式),而不会丢失与类型检查相关的信息。
动态类型检查发生在 运行 时间。执行这些动态类型检查的代码要么是解释器的一部分(如果有解释器),要么由代码生成器插入。
Ruby 在解释器中执行类型检查。