Raku 在编译时执行哪些类型检查?将来会改变吗?

What type checks does Raku perform at compile time? May that change in the future?

目前(截至 2020 年 8 月)Rakudo 不会在编译时对函数的 return 值进行类型检查;也就是说,它不提供函数满足其 return 约束的静态保证。具体来说,以下两个函数都编译为 Raku:

sub get-int(--> Int) { 'bug' }
sub get-int($a --> Int) { 
   when $a == 5 { 'Rare bug' }
   default      { 42 }
}

我有两个相关问题:

  1. 有没有办法知道当前在编译时发生了什么(如果有的话)类型检查? (通过某人编写的列表,文档中的某处,或 Rakudo 源中的中心位置)还是比这更特别?

  2. 缺少编译时类型检查是否是有意设计的决定?还是添加更多的静态类型检查,有一天会很好,但还没有实现?

(我熟悉 Johnathan 对 The performance penalties for types/constraints in Raku? 的出色回答,其中指出“Raku 要求写入程序的类型约束最迟在 运行 时间 强制执行。”该答案描述了避免 运行 类型检查时间成本的各种方法,但没有描述在编译时完成的类型检查(如果有的话)(这肯定会避免 运行时间成本!)。)

目前很少在编译时进行类型检查;主要发生在静态优化器的 side-effect 中。今天的检查主要是关于子程序调用,其中:

  • 我们可以确定调用的数量,并且知道传递的参数数量永远不会匹配
  • 我们有文字参数,可以看出它们永远不可能与签名匹配

这是静态优化器进行更多内联工作时的遗留问题。如今,它只在编译时内联本机运算符,其余的留给 VM 的动态优化器,它在内联方面的能力要强得多,也可以取消内联(允许推测优化,但也意味着可以恢复原始堆栈跟踪,而静态优化器丢失了此信息)。

在编译时做更多的事情被认为是可取的,但是有一些实际问题需要考虑。

  1. 引入额外的检查也可能导致之前工作的代码中断。考虑一个模块,其代码路径将无法通过更严格的编译时检查,但在从未 运行 进入这种情况的系统中使用。如果它开始无法在较新版本的编译器上编译,那么在编译器升级后将无法部署该系统。一般来说,这意味着执行的检查应该随着语言版本的变化而变化。 (这仍然意味着人们应该在编写代码时声明他们正在编写的语言版本,注意。)
  2. 在编译时进行更多检查将“肯定避免 运行时间成本”可能是正确的,但推理起来并非微不足道。托管的 运行time 不能盲目地相信它在字节码中做出的承诺,因为这可能会导致内存安全违规(这会导致 SIGSEGV 或更糟)。这在像 Raku 这样的语言中非常明显,其中类型检查的语义是可编程的,但在 JVM、CLR 等上也是如此。 Raku 中最大的 type-related 胜利来自使用原生类型,这可以避免大量分配和垃圾收集工作。
  3. 实施进一步检查将增加编译器的复杂性以及编译所需的时间。其中第一个已经是一个问题;编译器前端在大约十年内没有看到任何重大的架构变化。当前为宏奠定基础的 RakuAST 工作还涉及对编译器前端的近乎重写。改进后的体系结构应该可以简化进一步 compile-time 类型检查的实施,但我们也在考虑如何并行化编译的各个方面,这可以让编译器在不增加挂钟编译时间的情况下做更多事情。

当前编译器前端大修完成后,很可能会引入更多 compile-time 检查(但仅在下一个语言版本中启用)- 至少,只要有人致力于此。

然而,在这个领域出现了一个更令人兴奋的机会:因为将有一个 API 到 Raku 程序,并且随着自定义编译器通道的计划一起出现,它也很快可以将类型检查器实现为模块!其中一些可能会导致检查,使其成为未来的 Raku 语言版本。其他人可能相当 domain-specific 并且旨在更正确地使用给定模块。其他语言可能会强制执行不符合基本语言精神的严格要求,但某些语言用户可能希望选择加入。