2021 年更新现在有了 RakuAST![1]
Curt's answer 捕获 chi 想要捕获的错误。这个补充答案是我对Chi's follow on question 问的初步探索:
为什么在编译/运行 Curt 的代码时,Rakudo 会等到运行时才报告三个错误中的两个?
在初步探索结束时,我将 Curt 的代码包装在 BEGIN 块中。此代码似乎在编译时报告所有错误(当然,在注释掉每个先前的错误之后)。 (Click for runnable snippet.)[2]
这是一个稻草人的答案,为 Perl 6 核心开发者准备的。
Curt 代码的初始运行导致:
===SORRY!=== ...调用 myprint(Distance) 将永远无法工作...
前导 ===SORRY!=== 表示“编译时”[3] 错误。
但如果我们注释掉失败的行并重试,我们会得到:
分配给 $d2 的类型检查失败...
此错误消息是“运行时”[3] 消息 -- 它不以 ===SORRY!=== 开头。
为什么编译器要等到“运行时”才抱怨?
答案似乎在于 Perl 6 的默认动态特性和失败的代码的结合:
my Distance $d2 = $o; # Bummer! Type check fail
当编译器在“编译时”第一次遇到此声明时,此行的 my Distance $d2 部分将被完全处理(引入一个新的词法范围符号 $d2)。但是这一行的= 部分是“运行时”运算符;初始化分配和相应的类型检查发生在“运行时”。
但开发人员可能希望在编译时强制进行类型检查,从而导致类型检查错误。现在呢?
BEGIN时间
Perl 6 通过phasers 支持程序执行空间/时间旅行。第一个移相器是BEGIN移相器,它“在编译时尽快运行”,可以这样使用:
BEGIN my Distance $d2 = $o;
如果您使用上述更改重新编译,则错误现在出现在编译时[3]:
===SORRY!=== Error while compiling...
An exception occurred while evaluating a BEGIN...
Type check failed in assignment to $d2...
如果我们现在注释掉最新的失败行并重试,我们会得到:
无法解析调用者数字(距离:)...
没有前导 ===SORRY!=== 所以这又是一个“运行时”错误。
重新运行代码,将失败行更改为:
BEGIN say $d + $o;
产量:
0
12
在标准输出上,在标准错误上我们得到:
Use of uninitialized value of type Distance in numeric context...
Use of uninitialized value of type Offset in numeric context...
呃。不仅没有编译时错误,也没有运行时错误! (运行时警告可能会泄露有关0 的游戏。因为声明$d 和$o 的my... 行没有以BEGIN 为前缀,所以这些符号尚未在编译时初始化,这是BEGIN say $d + $o; 行开始运行的时间。但这一切都没有实际意义;我们显然已经倒退了一步。)
如果我们将 Curt 的所有代码包装在一个 BEGIN 块中会发生什么?
BEGIN { ... Curt's code goes here ... }
BEGIN {
class Distance...
class Offset...
my Distance $d = Distance.new(12)...
sub myprint(Int $i) { say $i }
say $d + $o;...
}
宾果!这些错误现在都像 Curt 的原始代码一样显示出来,但报告似乎是在编译时发生的(一个接一个,在注释掉每个先前的错误之后)。
脚注
[1]在a comment I just wrote below Curt's answer,我刚刚写了[1a]: p>
嗨@chi。我才刚刚注意到您的评论Raku 编译器允许做尽可能多的静态分析它喜欢,但是,4 年后你写了这个 SO,目前唯一实用的 Raku 编译器,Rakudo,目前做的相对较少,因为分析使编译速度变慢,而 Rakudo 的首要任务是快速开始运行代码。也就是说,Rakudo 正在不断发展,在 RakuAST 工作完成后,可以预期人们会开发更深入的静态分析模块。
[1a]当然,我写了我的评论之前我看了我的答案!这反映了我的过程;当我意识到有人赞成(或反对)我的一个旧答案时,我通常会检查问题,就好像我没有回答一样——忽略我的答案——作为一种快速恢复被问/说的速度,而我仍然有一个相对新鲜的观点,不受我最初回答时所发展的观点的限制。我完全忘记了我的答案是什么,所以我自然而然地被我似乎没有注意到@chi的评论并且没有回复的事实所震惊。此外,我的直接反应是/是新的 RakuAST 工作是相关的,as jnthn explains,因此是我的评论,现在我将此信息添加到此答案中。
[2] 此处复制代码以防 glot.io 消失:
# See https://stackoverflow.com/a/44360950/1077672
BEGIN { # to end of file
class Distance
{
has Int $!value handles<Int>;
method new($value where 0 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
class Offset
{
has Int $!value handles<Int>;
method new($value where -32000 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
my Distance $d = Distance.new(12); # ok
my Offset $o = Offset.new(-1); # ok
my Distance $d2 = $o; # Bummer! Type check fail
sub myprint(Int $i) { say $i }
say $d + $o; # Bummer!, can't add those objects
myprint $d; # Bummer!, $d isn't an Int, can't print
myprint Int($d); # ok, prints 12, converting with Int
}
[3] 我害怕引用许多“编译时”和“运行时”的引用,因为它们在 Perl 6 中的含义不明确。Perl 6 允许用户代码在运行时几乎可以做任何事情,包括运行编译器,并让用户代码在编译时做任何事情,包括运行时的事情。所以从一个角度来看,一个编译阶段可以有一个或多个运行阶段,反之亦然。但从第二个角度来看,有 编译时阶段,即当您在开发会话期间坐在那里并且您刚刚运行编译器时。同样,还有 运行时阶段,例如,当您的代码“在生产中”运行时。我在哪里不吓唬引用运行时/编译时,我的意思是指第二个观点。