程序的生命周期(执行时间线)被分成多个阶段。相位是在特定执行阶段调用的代码块。

相位§

相位块只是包含它的闭包的一个特性,并在适当的时候自动调用。这些自动调用的块被称为 *相位器*,因为它们通常标志着计算从一个阶段过渡到另一个阶段。例如,CHECK 块在编译一个编译单元结束时被调用。其他类型的相位器也可以安装;它们在适当的时候自动调用,其中一些会响应各种控制异常和退出值。例如,如果从一个块中退出成功或失败,则可能会调用一些相位器,其中 *成功* 在这种情况下定义为返回一个定义的值或列表,并且在过程中没有 Failure 或异常。

以下是总结

  BEGIN {...} #  * at compile time, as soon as possible, only ever runs once 
  CHECK {...} #  * at compile time, as late as possible, only ever runs once 
   INIT {...} #  * at runtime, as soon as possible, only ever runs once 
    END {...} #  at runtime, as late as possible, only ever runs once 
    DOC [BEGIN|CHECK|INIT{...} # only in documentation mode 
 
  ENTER {...} #  * 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 queue 
   UNDO {...} #  at every unsuccessful block exit, part of LEAVE queue 
 
  FIRST {...} #  at loop initialization time, before any ENTER 
   NEXT {...} #  at loop continuation time, before any LEAVE 
   LAST {...} #  at loop termination time, after any LEAVE 
 
    PRE {...} #  assert precondition at every block entry, before ENTER 
   POST {...} #  assert postcondition at every block exit, after LEAVE 
 
  CATCH {...} #  catch exceptions, before LEAVE 
CONTROL {...} #  catch control exceptions, before LEAVE 
 
   LAST {...} #  supply tapped by whenever-block is done, runs very last 
   QUIT {...} #  catch async exceptions within a whenever-block, runs very last 
 
COMPOSE {...} #  when a role is composed into a class (Not yet implemented) 
  CLOSE {...} #  appears in a supply block, called when the supply is closed 

* 标记的相位器具有运行时值,如果在它们周围的表达式之前进行评估,它们只会保存它们的结果,以便在表达式中剩余部分被评估时在表达式中使用。

my $compiletime = BEGIN { now };
our $random = ENTER { rand };

与其他语句前缀一样,这些产生值的构造可以放在块或语句的前面。

my $compiletime = BEGIN now;
our $random = ENTER rand;

大多数这些相位器将接受一个块或一个函数引用。语句形式对于将词法范围声明暴露给周围的词法范围而不会将其“困”在块中特别有用。

这些声明了与前一个示例相同的变量,具有相同的范围,但在指示的时间运行整个语句。

BEGIN my $compiletime = now;
ENTER our $random = rand;

(但是,请注意,在编译时计算的变量的值可能不会在任何周围闭包的运行时克隆下持久存在。)

大多数非产生值的相位器也可以这样使用。

END say my $accumulator;

但是,请注意

END say my $accumulator = 0;

END 时间将变量设置为 0,因为这是“my”声明实际执行的时间。只有无参数的相位器可以使用语句形式。这意味着 CATCHCONTROL 始终需要一个块,因为它们接受一个参数,该参数将 $_ 设置为当前主题,以便内部能够像 switch 语句一样工作。(如果允许裸语句,$_ 的临时绑定将泄漏到 CATCHCONTROL 的末尾,并产生不可预测且可能非常严重的后果。异常处理程序应该减少不确定性,而不是增加不确定性。)

其中一些相位器也有相应的特性,可以设置在变量上;它们使用 will 后跟相位器名称的小写形式。这些具有将相关变量作为其主题传递到闭包中的优势。

our $h will enter { .rememberit() } will undo { .forgetit() };

只有那些可以在一个块中多次出现的相位器才有资格使用这种针对每个变量的形式;这排除了 CATCH 和其他类似 CLOSEQUIT 的相位器。

相位器外部块的主题仍然可以使用 OUTER::<$_> 获取。返回值是否可修改可能是相关相位器的策略。特别是,不应在 POST 相位器中修改返回值,但 LEAVE 相位器可能更宽松。

在方法的词法范围内定义的任何相位器都是一个闭包,它除了正常的词法之外还闭包了 self。(或者等效地,实现可以简单地将所有这些相位器转换为其预先设置的调用者是当前对象的子方法。)

当多个相位器被安排在同一时间运行时,一般的决胜原则是在声明的顺序中执行初始化相位器,而在相反的顺序中执行最终化相位器,因为设置和拆卸通常希望以相反的顺序发生。

执行顺序§

编译开始

      BEGIN {...} #  at compile time, As soon as possible, only ever runs once 
      CHECK {...} #  at compile time, As late as possible, only ever runs once 
    COMPOSE {...} #  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 LEAVE 
    CONTROL {...} #  catch control exceptions, before LEAVE 

循环结束,继续或完成

       NEXT {...} #  at loop continuation time, before any LEAVE 
       LAST {...} #  at loop termination time, after any LEAVE 

块结束

      LEAVE {...} #  when blocks exits, even stack unwinds from exceptions 
       KEEP {...} #  at every successful block exit, part of LEAVE queue 
       UNDO {...} #  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 block 
       QUIT {...} #  catch async exceptions 

程序终止

        END {...} #  at runtime, ALAP, only ever runs once 

程序执行相位器§

BEGIN§

在编译时运行,一旦相位器中的代码编译完成,只运行一次。

返回值可用于后续阶段。

say "About to print 3 things";
for ^3 {
    say ^10 .pick ~ '-' ~ BEGIN { say  "Generating BEGIN value"^10 .pick }
}
# OUTPUT: 
# Generating BEGIN value 
# About to print 3 things 
# 3-3 
# 4-3 
# 6-3

相位器中的 ^10 .pick 只生成一次,然后在运行时由循环重复使用。请注意,BEGIN 块中的 say 在循环上方的 say 之前执行。

CHECK§

在编译时运行,尽可能晚,只运行一次。

可以有一个返回值,即使在后面的阶段也能提供。

在运行时生成的代码仍然可以触发 CHECKINIT 阶段,当然这些阶段不能做需要时间倒流的事情。你需要一个虫洞才能做到这一点。

INIT§

在编译后,在主执行期间运行,尽快,只运行一次。它可以有一个返回值,即使在后面的阶段也能提供。

当阶段在不同的模块中时,INITEND 阶段被视为在使用模块中 use 时声明的。但是,如果模块被使用多次,则依赖此顺序是错误的,因为阶段只在第一次被注意到时才被安装。

在运行时生成的代码仍然可以触发 CHECKINIT 阶段,当然这些阶段不能做需要时间倒流的事情。你需要一个虫洞才能做到这一点。

对于克隆闭包的所有副本,INIT 只运行一次。

END§

在编译后,在主执行期间运行,尽可能晚,只运行一次。它将自动关闭所有打开的句柄。

当阶段在不同的模块中时,INITEND 阶段被视为在使用模块中 use 时声明的。但是,如果模块被使用多次,则依赖此顺序是错误的,因为阶段只在第一次被注意到时才被安装。

块阶段§

在块的上下文中执行有它自己的阶段。

离开块的阶段会等到调用栈实际展开才会运行。展开只有在某个异常处理程序决定以这种方式处理异常时才会发生。也就是说,仅仅因为异常被抛出到一个栈帧之外,并不意味着我们已经正式离开了块,因为异常可能是可恢复的。无论如何,异常处理程序被指定在失败代码的动态范围内运行,无论异常是否可恢复。只有当异常没有被恢复时,栈才会展开,阶段才会被调用。

这些可以在块中多次发生。所以它们不完全是特征,它们将自己添加到实际特征中存储的列表中。如果你检查块的 ENTER 特征,你会发现它实际上是一个阶段列表,而不是一个单独的阶段。

所有这些阶段块都可以看到任何先前声明的词法变量,即使这些变量在闭包被调用时还没有被详细说明(在这种情况下,变量将评估为未定义的值)。

ENTER§

在每次块进入时运行,在循环块中重复。

可以有一个返回值,即使在后面的阶段也能提供。

ENTER 阶段抛出的异常将中止 ENTER 队列,但从 LEAVE 阶段抛出的异常不会。

LEAVE§

在每次块退出时运行(即使是来自异常的栈展开),除了程序突然退出时(例如使用 exit)。

给定块的 LEAVE 阶段必须在任何 CATCHCONTROL 阶段之后进行评估。这包括 LEAVE 变体,KEEPUNDOPOST 阶段在所有其他阶段之后进行评估,以保证即使 LEAVE 阶段也不能违反后置条件。

ENTER 阶段抛出的异常将中止 ENTER 队列,但从 LEAVE 阶段抛出的异常不会。

如果 POST 失败或任何类型的 LEAVE 块在栈展开时抛出异常,展开将继续并收集要处理的异常。当展开完成时,所有新的异常将从该点抛出。

sub answer() {
    LEAVE say I say after the return value.;
 
    42 # this is the return value 
}

注意:注意直接在例程块中的 LEAVE 阶段,因为即使在尝试使用错误参数调用例程时,它们也会被执行。

sub foo (Int{
    say "Hello!";
    LEAVE say "oh noes!"
}
try foo rand# OUTPUT: «oh noes!␤»

虽然子例程的主体没有运行,因为子例程期望一个 Int 并且 rand 返回了一个 Num,但它的块被进入和离开(当参数绑定失败时),因此 LEAVE 阶段确实运行了。

KEEP§

在每次成功的块退出时运行,作为 LEAVE 队列的一部分(共享相同的执行顺序)。

UNDO§

在每次不成功的块退出时运行,作为LEAVE队列的一部分(共享相同的执行顺序)。

PRE§

在每个块进入时断言一个先决条件。在ENTER阶段之前运行。

PRE阶段在任何ENTERFIRST之前触发。

失败的PREPOST阶段抛出的异常不能被同一个块中的CATCH捕获,这意味着如果PRE阶段失败,POST阶段不会运行。

POST§

在每个块进入时断言一个后置条件。在LEAVE阶段之后运行。

对于像KEEPPOST这样的在正常退出作用域时运行的阶段,该作用域的返回值(如果有)在阶段中可用作当前主题。

POST块可以通过两种方式之一定义。要么相应的POST被定义为一个单独的阶段,在这种情况下PREPOST没有共享词法作用域。或者,任何PRE阶段都可以将它对应的POST定义为一个嵌入式阶段块,该块关闭了PRE的词法作用域。

如果 POST 失败或任何类型的 LEAVE 块在栈展开时抛出异常,展开将继续并收集要处理的异常。当展开完成时,所有新的异常将从该点抛出。

失败的PREPOST阶段抛出的异常不能被同一个块中的CATCH捕获,这意味着如果PRE阶段失败,POST阶段不会运行。

循环阶段§

FIRSTNEXTLAST仅在循环的词法作用域内有意义,并且只能在循环块的顶层出现。

FIRST§

在循环初始化时运行,在ENTER之前。

NEXT§

当循环继续时运行(通过next或因为你到达了循环的底部并且正在循环回来),在LEAVE之前。

NEXT仅在正常到达循环块的末尾或执行了显式的next时才会执行。与LEAVE阶段不同,如果循环块通过除next抛出的控制异常之外的任何异常退出,则不会执行NEXT阶段。特别是,last会绕过NEXT阶段的评估。

LAST§

当循环因为条件满足而完成,或者当它使用last退出时运行;它在LEAVE之后执行。

异常处理阶段§

CATCH§

当当前块抛出异常时运行,在LEAVE阶段之前。另请参见捕获异常.

CONTROL§

当当前块抛出控制异常时运行,在LEAVE阶段之前。它由returnfailredonextlastdoneemittakewarnproceedsucceed抛出。

say elems gather {
    CONTROL {
        when CX::Warn { say "WARNING!!! $_".resume }
        when CX::Take { say "Don't take my stuff".resume }
        when CX::Done { say "Done".resume }
    }
    warn 'people take stuff here';
    take 'keys';
    done;
}
# 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§

阶段 BEGINCHECKINIT 仅在文档模式下运行,前提是它们以 DOC 关键字为前缀。当使用 --doc 运行时,编译器处于文档模式。

DOC INIT { say 'init'  }  # prints 'init' at initialization time when in documentation mode.