程序的生命周期(执行时间线)被分成多个阶段。相位是在特定执行阶段调用的代码块。
相位§
相位块只是包含它的闭包的一个特性,并在适当的时候自动调用。这些自动调用的块被称为 *相位器*,因为它们通常标志着计算从一个阶段过渡到另一个阶段。例如,CHECK
块在编译一个编译单元结束时被调用。其他类型的相位器也可以安装;它们在适当的时候自动调用,其中一些会响应各种控制异常和退出值。例如,如果从一个块中退出成功或失败,则可能会调用一些相位器,其中 *成功* 在这种情况下定义为返回一个定义的值或列表,并且在过程中没有 Failure
或异常。
以下是总结
BEGIN # * at compile time, as soon as possible, only ever runs onceCHECK # * at compile time, as late as possible, only ever runs onceINIT # * at runtime, as soon as possible, only ever runs onceEND # at runtime, as late as possible, only ever runs onceDOC [BEGIN|CHECK|INIT] # only in documentation modeENTER # * at every block entry time, repeats on loop blocks.LEAVE # at every block exit time (even stack unwinds from exceptions)KEEP # at every successful block exit, part of LEAVE queueUNDO # at every unsuccessful block exit, part of LEAVE queueFIRST # at loop initialization time, before any ENTERNEXT # at loop continuation time, before any LEAVELAST # at loop termination time, after any LEAVEPRE # assert precondition at every block entry, before ENTERPOST # assert postcondition at every block exit, after LEAVECATCH # catch exceptions, before LEAVECONTROL # catch control exceptions, before LEAVELAST # supply tapped by whenever-block is done, runs very lastQUIT # catch async exceptions within a whenever-block, runs very lastCOMPOSE # when a role is composed into a class (Not yet implemented)CLOSE # appears in a supply block, called when the supply is closed
用 *
标记的相位器具有运行时值,如果在它们周围的表达式之前进行评估,它们只会保存它们的结果,以便在表达式中剩余部分被评估时在表达式中使用。
my = BEGIN ;our = ENTER ;
与其他语句前缀一样,这些产生值的构造可以放在块或语句的前面。
my = BEGIN now;our = ENTER rand;
大多数这些相位器将接受一个块或一个函数引用。语句形式对于将词法范围声明暴露给周围的词法范围而不会将其“困”在块中特别有用。
这些声明了与前一个示例相同的变量,具有相同的范围,但在指示的时间运行整个语句。
BEGIN my = now;ENTER our = rand;
(但是,请注意,在编译时计算的变量的值可能不会在任何周围闭包的运行时克隆下持久存在。)
大多数非产生值的相位器也可以这样使用。
END say my ;
但是,请注意
END say my = 0;
在 END
时间将变量设置为 0,因为这是“my”声明实际执行的时间。只有无参数的相位器可以使用语句形式。这意味着 CATCH
和 CONTROL
始终需要一个块,因为它们接受一个参数,该参数将 $_
设置为当前主题,以便内部能够像 switch 语句一样工作。(如果允许裸语句,$_
的临时绑定将泄漏到 CATCH
或 CONTROL
的末尾,并产生不可预测且可能非常严重的后果。异常处理程序应该减少不确定性,而不是增加不确定性。)
其中一些相位器也有相应的特性,可以设置在变量上;它们使用 will
后跟相位器名称的小写形式。这些具有将相关变量作为其主题传递到闭包中的优势。
our will enter will undo ;
只有那些可以在一个块中多次出现的相位器才有资格使用这种针对每个变量的形式;这排除了 CATCH
和其他类似 CLOSE
或 QUIT
的相位器。
相位器外部块的主题仍然可以使用 OUTER::<$_>
获取。返回值是否可修改可能是相关相位器的策略。特别是,不应在 POST
相位器中修改返回值,但 LEAVE
相位器可能更宽松。
在方法的词法范围内定义的任何相位器都是一个闭包,它除了正常的词法之外还闭包了 self
。(或者等效地,实现可以简单地将所有这些相位器转换为其预先设置的调用者是当前对象的子方法。)
当多个相位器被安排在同一时间运行时,一般的决胜原则是在声明的顺序中执行初始化相位器,而在相反的顺序中执行最终化相位器,因为设置和拆卸通常希望以相反的顺序发生。
执行顺序§
编译开始
BEGIN # at compile time, As soon as possible, only ever runs onceCHECK # at compile time, As late as possible, only ever runs onceCOMPOSE # when a role is composed into a class (Not yet implemented)
执行开始
INIT # at runtime, as soon as possible, only ever runs once
在块执行开始之前
PRE # assert precondition at every block entry, before ENTER
循环执行开始
FIRST # at loop initialization time, before any ENTER
块执行开始
ENTER # at every block entry time, repeats on loop blocks.
可能发生异常
CATCH # catch exceptions, before LEAVECONTROL # catch control exceptions, before LEAVE
循环结束,继续或完成
NEXT # at loop continuation time, before any LEAVELAST # at loop termination time, after any LEAVE
块结束
LEAVE # when blocks exits, even stack unwinds from exceptionsKEEP # at every successful block exit, part of LEAVE queueUNDO # at every unsuccessful block exit, part of LEAVE queue
块的后置条件
POST # assert postcondition at every block exit, after LEAVE
异步 whenever-block 完成
LAST # if ended normally with done, runs once after blockQUIT # catch async exceptions
程序终止
END # at runtime, ALAP, only ever runs once
程序执行相位器§
BEGIN§
在编译时运行,一旦相位器中的代码编译完成,只运行一次。
返回值可用于后续阶段。
say "About to print 3 things";for ^3# OUTPUT:# Generating BEGIN value# About to print 3 things# 3-3# 4-3# 6-3
相位器中的 ^10 .pick
只生成一次,然后在运行时由循环重复使用。请注意,BEGIN
块中的 say 在循环上方的 say 之前执行。
CHECK§
在编译时运行,尽可能晚,只运行一次。
可以有一个返回值,即使在后面的阶段也能提供。
在运行时生成的代码仍然可以触发 CHECK
和 INIT
阶段,当然这些阶段不能做需要时间倒流的事情。你需要一个虫洞才能做到这一点。
INIT§
在编译后,在主执行期间运行,尽快,只运行一次。它可以有一个返回值,即使在后面的阶段也能提供。
当阶段在不同的模块中时,INIT
和 END
阶段被视为在使用模块中 use
时声明的。但是,如果模块被使用多次,则依赖此顺序是错误的,因为阶段只在第一次被注意到时才被安装。
在运行时生成的代码仍然可以触发 CHECK
和 INIT
阶段,当然这些阶段不能做需要时间倒流的事情。你需要一个虫洞才能做到这一点。
对于克隆闭包的所有副本,INIT
只运行一次。
END§
在编译后,在主执行期间运行,尽可能晚,只运行一次。它将自动关闭所有打开的句柄。
当阶段在不同的模块中时,INIT
和 END
阶段被视为在使用模块中 use
时声明的。但是,如果模块被使用多次,则依赖此顺序是错误的,因为阶段只在第一次被注意到时才被安装。
块阶段§
在块的上下文中执行有它自己的阶段。
离开块的阶段会等到调用栈实际展开才会运行。展开只有在某个异常处理程序决定以这种方式处理异常时才会发生。也就是说,仅仅因为异常被抛出到一个栈帧之外,并不意味着我们已经正式离开了块,因为异常可能是可恢复的。无论如何,异常处理程序被指定在失败代码的动态范围内运行,无论异常是否可恢复。只有当异常没有被恢复时,栈才会展开,阶段才会被调用。
这些可以在块中多次发生。所以它们不完全是特征,它们将自己添加到实际特征中存储的列表中。如果你检查块的 ENTER
特征,你会发现它实际上是一个阶段列表,而不是一个单独的阶段。
所有这些阶段块都可以看到任何先前声明的词法变量,即使这些变量在闭包被调用时还没有被详细说明(在这种情况下,变量将评估为未定义的值)。
ENTER§
在每次块进入时运行,在循环块中重复。
可以有一个返回值,即使在后面的阶段也能提供。
从 ENTER
阶段抛出的异常将中止 ENTER
队列,但从 LEAVE
阶段抛出的异常不会。
LEAVE§
在每次块退出时运行(即使是来自异常的栈展开),除了程序突然退出时(例如使用 exit
)。
给定块的 LEAVE
阶段必须在任何 CATCH
和 CONTROL
阶段之后进行评估。这包括 LEAVE
变体,KEEP
和 UNDO
。POST
阶段在所有其他阶段之后进行评估,以保证即使 LEAVE
阶段也不能违反后置条件。
从 ENTER
阶段抛出的异常将中止 ENTER
队列,但从 LEAVE
阶段抛出的异常不会。
如果 POST
失败或任何类型的 LEAVE
块在栈展开时抛出异常,展开将继续并收集要处理的异常。当展开完成时,所有新的异常将从该点抛出。
sub answer()
注意:注意直接在例程块中的 LEAVE
阶段,因为即使在尝试使用错误参数调用例程时,它们也会被执行。
sub foo (Int)try foo rand; # OUTPUT: «oh noes!»
虽然子例程的主体没有运行,因为子例程期望一个 Int
并且 rand
返回了一个 Num
,但它的块被进入和离开(当参数绑定失败时),因此 LEAVE
阶段确实运行了。
KEEP§
在每次成功的块退出时运行,作为 LEAVE
队列的一部分(共享相同的执行顺序)。
UNDO§
在每次不成功的块退出时运行,作为LEAVE
队列的一部分(共享相同的执行顺序)。
PRE§
在每个块进入时断言一个先决条件。在ENTER
阶段之前运行。
PRE
阶段在任何ENTER
或FIRST
之前触发。
失败的PRE
和POST
阶段抛出的异常不能被同一个块中的CATCH
捕获,这意味着如果PRE
阶段失败,POST
阶段不会运行。
POST§
在每个块进入时断言一个后置条件。在LEAVE
阶段之后运行。
对于像KEEP
和POST
这样的在正常退出作用域时运行的阶段,该作用域的返回值(如果有)在阶段中可用作当前主题。
POST
块可以通过两种方式之一定义。要么相应的POST
被定义为一个单独的阶段,在这种情况下PRE
和POST
没有共享词法作用域。或者,任何PRE
阶段都可以将它对应的POST
定义为一个嵌入式阶段块,该块关闭了PRE
的词法作用域。
如果 POST
失败或任何类型的 LEAVE
块在栈展开时抛出异常,展开将继续并收集要处理的异常。当展开完成时,所有新的异常将从该点抛出。
失败的PRE
和POST
阶段抛出的异常不能被同一个块中的CATCH
捕获,这意味着如果PRE
阶段失败,POST
阶段不会运行。
循环阶段§
FIRST
、NEXT
和LAST
仅在循环的词法作用域内有意义,并且只能在循环块的顶层出现。
FIRST§
在循环初始化时运行,在ENTER
之前。
NEXT§
当循环继续时运行(通过next
或因为你到达了循环的底部并且正在循环回来),在LEAVE
之前。
NEXT
仅在正常到达循环块的末尾或执行了显式的next
时才会执行。与LEAVE
阶段不同,如果循环块通过除next
抛出的控制异常之外的任何异常退出,则不会执行NEXT
阶段。特别是,last
会绕过NEXT
阶段的评估。
LAST§
当循环因为条件满足而完成,或者当它使用last
退出时运行;它在LEAVE
之后执行。
异常处理阶段§
CATCH§
当当前块抛出异常时运行,在LEAVE
阶段之前。另请参见捕获异常.
CONTROL§
当当前块抛出控制异常时运行,在LEAVE
阶段之前。它由return
、fail
、redo
、next
、last
、done
、emit
、take
、warn
、proceed
和succeed
抛出。
say elems gather# OUTPUT:# WARNING!!! people take stuff here# Don't take my stuff# Done# 0
对象阶段§
COMPOSE§
当角色被组合到类中时运行。
异步阶段§
LAST§
当Supply
通过调用done
完成或当supply
块正常退出时运行。它在它所在的whenever
块完全完成之后运行。
此阶段重用了LAST
名称,但与LAST
循环阶段的工作方式不同。此阶段类似于在使用tap
点击供应时设置done
例程。
QUIT§
当一个 Supply
由于异常提前终止时运行。它在它所在的 whenever
块完成之后运行。
这个阶段类似于使用 tap
敲击 Supply 时设置 quit
例程。
CLOSE§
出现在供应块中。当供应关闭时调用。
DOC 阶段§
DOC§
阶段 BEGIN
、CHECK
和 INIT
仅在文档模式下运行,前提是它们以 DOC
关键字为前缀。当使用 --doc
运行时,编译器处于文档模式。
DOC INIT # prints 'init' at initialization time when in documentation mode.