启示录4

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

目录
已接受RFC RFC 022:控制流:内置switch语句 RFC 088:综合结构化异常/错误处理机制 RFC 199:短路内置函数和用户定义子程序 RFC 006:使词法变量成为默认 RFC 330:全局动态变量应保持为默认 RFC 083:使常量看起来像变量 RFC 337:通用属性系统以允许用户定义、可扩展属性 RFC 173:允许foreach语句中存在多个循环变量 RFC 019:重命名local操作符 RFC 064:新的pragma 'scope'来更改Perl的默认作用域 已拒绝RFC 已撤回RFC 其他决定
 

这篇启示录主要关于大语法。骆驼书中的相应章节标题为“语句和声明”,但也可以简单地称之为“所有关于块”。基本的问题是“那些花括号究竟意味着什么?”

对于Perl 5及其早期版本,那个问题的答案就是“太多的事情”。或者说,太多的事情,规则又不一致。我们将继续使用大括号来表示我们至今为止使用的大部分内容,但通过进行一些关键性的简化,规则将会更加一致。特别是,内置函数将使用与用户定义构造相同的规则进行解析。应该可以使得用户可扩展的语法看起来就像内置语法一样。Perl 5已经走上了这条路,但并没有走到底。在Perl 6中,所有块都遵循相同的规则。实际上,每个块都可以看作是一种闭包,既可以由用户定义构造函数执行,也可以由内置函数执行。

与块结构相关的是各种利用块结构的构造。复合构造,如循环和条件语句,会显式地使用块,而声明则隐式地引用它们的包围块。这种后者的特性在Perl 5中也被不统一地应用。在Perl 6中,规则很简单:一个词法作用域的声明从声明开始到其包围块的末尾都是有效的。由于块只由大括号或当前编译单元(文件或字符串)的末尾来界定,这意味着我们不允许存在多块构造,其中词法作用域变量从块的末尾“泄漏”或“穿越”到下一个块的开始。一个右大括号(没有中间的左大括号)绝对地停止了当前的词法作用域。这直接影响到一些RFC。例如,RFC 88提议允许词法作用域从try块泄漏到相应的finally块。这是不允许的。(我们将找到另一种解决这个特定问题的方法。)

尽管词法声明可能不会从块中泄漏出来,但控制流必须能够以受控的方式从块中泄漏出来。显然,从块的末尾掉落是“最正常”的方式,但我们需要以其他“不正常”的方式退出块。Perl 5有多种退出块的方式:returnnextlastredodie等。问题是,这些不同的关键字被硬编码到转移控制到特定的内置构造,例如子程序定义、循环或eval。这与我们的统一概念相矛盾,即每个块都是一个闭包。在Perl 6中,所有这些块退出的不正常方式都统一在异常概念之下。一个return是一种有趣的异常,它被sub块捕获。一个next是一种被循环块捕获的异常。当然,die创建了一个“正常”的异常,它可以被任何选择捕获这种异常的块捕获。Perl 6不需要这个块是evaltry块。

你可能认为这种泛化意味着过高的开销,因为通常异常处理必须沿着调用堆栈查找适当的处理程序。但是,任何控制流异常都可以在其目标明显且之间没有用户定义块要退出时,内部优化为“goto”。大多数子程序返回和循环控制操作符将知道它们是从哪个子程序或循环退出的,因为从周围的词法作用域中可以很明显地看出。然而,如果当前子程序包含在其他用户定义函数中解释的闭包,那么有一个通用的异常机制是很好的,这样就可以自动完成所有必要的清理,并保持一致的语义。也就是说,我们希望用户定义的闭包处理程序不会像内置函数那样出现在用户的面前。控制流应该表现得像用户期望的那样工作,即使它实际上并不这样做。

以下是本启示录中涵盖的RFC。PSA代表“问题、解决方案、接受”,是我对如何将这个RFC纳入Perl 6的个人评分。有趣的是,这次我拒绝的RFC比接受的RFC多。我一定是在老年时变得越来越残酷和无动于衷。:-)

    RFC   PSA    Title
    ---   ---    -----
    006   acc    Lexical variables made default
    019   baa    Rename the C<local> operator
    022   abc    Control flow: Builtin switch statement
    063   rr     Exception handling syntax
    064   bdc    New pragma 'scope' to change Perl's default scoping
    083   aab    Make constants look like variables
    088   bbc    Omnibus Structured Exception/Error Handling Mechanism
    089   cdr    Controllable Data Typing
    106   dbr    Yet another lexical variable proposal: lexical variables made default
    113   rr     Better constants and constant folding
    119   bcr    Object neutral error handling via exceptions
    120   bcr    Implicit counter in for statements, possibly $#   
    167   bcr    Simplify do BLOCK Syntax
    173   bcc    Allow multiple loop variables in foreach statements
    199   abb    Short-circuiting built-in functions and user-defined subroutines
    209   cdr    Fuller integer support in Perl   
    262   cdr    Index Attribute
    279   cdr    my() syntax extensions and attribute declarations
    297   dcr    Attributes for compiler hints
    309   adr    Allow keywords in sub prototypes
    330   acc    Global dynamic variables should remain the default
    337   bcc    Common attribute system to allow user-defined, extensible attributes
    340   dcr    with takes a context
    342   bcr    Pascal-like "with"

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

接受的RFC

之前的启示录

启示录一

末日降临二

末日降临三

请注意,尽管这些RFC属于“已接受”类别,但大多数都是在重大保留条件下接受的(“c”接受评级),或者至少有一些“但是”(“b”评级)。我将尝试在这里列出所有这些保留条件,但在存在系统变化的地方,我可能会在这个文档中一般性地指出这些变化,而不试图逐项重写RFC的每个细节。实施这些功能的人必须对这些系统变化保持敏感,而不仅仅是盲目地执行RFC中所述的所有内容。

我想先谈谈例外情况,但在那之前,我必须处理switch语句,因为我认为不将异常处理程序与switch语句统一是不合理的。

RFC 022:控制流程:内置switch语句

一些OO纯粹主义者认为,每次你想使用switch语句时,都应该将switch语句的判别式做成一个类型,并使用方法调度。幸运的是,我们这里不是OO纯粹主义者,所以不要考虑这个论点。

反对在Perl 6中包含switch语句的另一个论点是,我们前五个版本中都没有它。但说我们没有错过它是错误的。实际上发生的情况是,每次我们开始讨论如何添加switch语句时,并不清楚要推进多远。Perl中的switch语句应该做的比C(或者大多数其他语言)中的switch语句更多。所以我们至今没有添加switch语句的事实,更多地说明了设计一个良好的switch语句有多难,而不是我们有多么想要一个糟糕的switch语句。最终,富有创造力的Damian Conway提出了他著名的设计,并以一个Perl 5模块作为概念验证,几乎每个人都同意他的方向是正确的(对于“正确”和“方向”的定义)。这个RFC基本上就是那个设计(不出所料,因为是Damian写的),所以它将被接受,尽管有几个调整。

首先,作为一个准语言学家,我讨厌switchcase这两个关键字。我更愿意使用在英语中读起来更好的关键字。尽管我喜欢动词名词化,但它们在需要主题化时并不像真正的动词或真正的介词那样有效。经过与Damian和其他人反复讨论几个选项后,我们决定使用given代替switch,并使用when代替case

    given EXPR {
        when EXPR { ... }
        when EXPR { ... }
        ...
    }

使用不同词汇的另一个巨大优势是,人们不会期望它的工作方式与任何他们可能熟悉的其他switch语句完全相同。

话虽如此,我应该指出,它仍然被称为“switch语句”,各个组成部分仍然是“case”。但你不需要将“switch”或“case”放入等宽字体,因为它们不是关键字。

由于Perl 5中花括号的使用非常复杂,我最初确信我们需要在表达式和代码块之间使用某种类型的分隔符,比如:=或类似的。否则,在期望运算符时遇到左花括号将会非常模糊——它将被解释为哈希索引。Damian的RFC建议在某些情况下要求括号来消除歧义。

但我已经得出结论,我宁愿(稍微)与“不重要空白”规则捣鼓一番,也不愿要求使用额外的非自然分隔符。如果我们观察当前的做法,我们会发现99%的时候,人们在写哈希索引时前面没有任何空白。而99%的时候,他们写代码块时都会在前面加上一些空白。所以我们将只通过空白来实现它。(不,我们不会走到完全使用空白的天堂——Python将是这种方法的最好/最坏例子。)

当期望操作符时,下标是唯一有效的花括号使用方式。(也就是说,下标本质上是一种后缀操作符。)相比之下,哈希构造器和块是术语,而不是操作符。因此,我们将制定一条规则:在Perl 6中,如果一个左花括号前面有空格,它将永远不会被解释为下标。如果您认为这是非常奇怪的做法,请考虑这种新的方法实际上与Perl 5已经解析插入字符串中的变量的方式是一致的。如果在花括号前有任何空格,我们将强制它以术语开头,而不是操作符,这意味着相关花括号必须限定一个哈希构造器或一个块。而且,它只包含顶层的一个`=` `&gt;`对构造器(或前端的显式`hash`关键字),所以它才是一个哈希构造器。因此,可以通过跟随一个块来明确地结束一个表达式,就像上面所示的结构。

有趣的是,这个对空格规则的微小调整也意味着我们可以简化其他类似内置结构的括号。

    if $foo { ... }
    elsif $bar { ... }
    else { ... }

    while $more { ... }

    for 1..10 { ... }

我认为放弃两个必需的标点符号字符以换取一个必需的空格是一个出色的权衡,特别是在它已经符合常见实践的情况下。当然,如果您愿意,仍然可以放入括号,只是为了怀旧。这个调整还使用户定义的结构解析具有更大的灵活性。如果您想定义自己的结构,它们应该能够遵循与内置相同的语法规则。

通过类似的逻辑链(或逻辑错误),我还想调整尾随花括号的空格规则。在C派生语言中,任何允许用户定义包含花括号的结构(例如Perl)都存在严重问题。即使是C也完全无法避免关于“我在什么时候在花括号后放置分号?”这样的挠头难题。例如,`struct`定义需要一个终止的分号,而`if`或`while`则不需要。

在Perl中,这个问题最常见于人们说“为什么我在do {}eval {}后面必须放置分号,这看起来像是一个完整的语句?”的时候。

然而,在Perl 6中,如果最后的括号单独占一行,您就不需要这样做。也就是说,如果您将表达式块用作语句块,它就会表现得像语句一样。好处是这些规则在所有表达式块中都是一致的,无论是用户定义的还是内置的。任何表达式块结构都可以被视为语句或表达式的组成部分。这里有一个被视为表达式术语的块:

    $x = do {
        ...
    } + 1;

然而,如果您这样写:

    $x = do {
        ...
    }
    + 1;

那么加号将被错误地认为是新语句的开始。(所以不要这样做。)

请注意,这个特殊规则仅适用于以块(即闭包)作为最后一个(或唯一)参数的结构。例如`sort`和`map`这样的操作符不受影响。然而,某些过去属于语句类别的结构在Perl 6中可能成为表达式结构。例如,如果我们将`BEGIN`改为表达式结构,现在我们可以在表达式中使用`BEGIN`块来强制编译时评估非静态表达式。

    $value = BEGIN { call_me_once() } + call_me_again();

另一方面,单行`BEGIN`将需要有一个分号。

无论如何,回到switch语句。Damian的RFC提出了各种具体的dwimmery(怪异的做法),虽然其中一些非常准确,但其他可能需要调整。特别是,有一个假设是程序员将知道他们何时在处理对象引用,何时不是。但在Perl 6中,从某种层面来说,一切都将是一个对象引用。任何对象的底层特性通常由回答以下问题来确定:“这个对象响应哪些方法?”

很遗憾,这在一般情况下是一个运行时问题。但在特定情况下,我们希望能够在编译时优化这些许多的switch语句。因此,在某些情况下可能需要提供类型提示来高效地进行操作。幸运的是,大多数情况仍然相对直接。一个1显然是一个数字,而一个"foo"显然是一个字符串。但一元+可以使任何内容变成数字,一元_可以使任何内容变成字符串。一元?可以使内容变成布尔值,而一元.可以使内容变成方法调用。更复杂的思想可以用闭包块来表示。

另一个需要调整的是,“isa”匹配的概念似乎缺失,或者至少很难表达。我们需要良好的“isa”匹配来实现基于switch机制的良好异常处理。这意味着我们需要能够说出类似

    given $! {
        when Error::Overflow { ... }
        when Error::Type { ... }
        when Error::ENOTTY { ... }
        when /divide by 0/ { ... }
        ...
    }

并期望它检查$!.isa(Error::Overflow)等,以及更正常的模式匹配。在实际的异常机制中,我们不会使用关键字given,而是使用CATCH

    CATCH {
        when Error::Overflow { ... }
        when Error::Type { ... }
        when Error::ENOTTY { ... }
        when /divide by 0/ { ... }
        ...
    }

CATCH是一个类似于BEGIN的块,可以将任何块从内部到外部转换为“try”块。但CATCH的内部是一个普通的switch语句,其中判别式只是当前的异常对象,$!。关于这一点,稍后再说——请参阅下面的RFC 88。

有些人可能记得我曾经说过Perl 6将没有裸词。情况仍然是这样。像Error::Overflow这样的标记不是裸词,因为它是一个已声明的类。Perl 6识别包名为符号标记。因此,当你以Class::Name.method()的形式调用类方法时,Class::Name实际上是一个类对象(恰好转换为字符串为“Class::Name”)。但类方法可以在运行时不需要对包名的符号查找来调用,这与Perl 5不同。

由于Error::Overflow就是这样一种类对象,它可以在switch语句中与其他类型的对象区分开来,并推断出“isa”。如果我们能进一步说,任何对象都可以用任何类名作为方法名来调用,以确定它是否是那个类的成员,这将很棒,但这可能会干扰使用类名方法来实现类型转换或构造。因此,由于switch语句本身就是一种复杂的推断,我认为switch语句必须识别编译时已知的任何Class::Name,并强制它调用$!.isa(Class::Name)

另一个可能的调整将涉及将switch语句用作并行化正则表达式评估的手段。具体来说,我们希望能够在Perl中轻松地编写解析器,这意味着我们需要一种方法来匹配标记流与类似于正则表达式集。你可以把标记流想象成一种奇怪类型的字符串。因此,如果switch语句的“given”是标记流,与之匹配的正则表达式可能具有与当前解析的数据结构相关的特殊能力。此类switch语句的所有正则表达式可能都隐式地锚定到当前的解析位置,例如。可能有特殊标记指向终结符和非终结符。基本上,想象一下类似于yacc语法的某种东西,其中备选的模式/操作语法规则最自然地通过switch语句的case来表示。下一部Apocalypse将详细介绍。

另一个可能的调整是,提议的else块可能被认为是多余的。在最后的when之后跟随的代码自动是“else”。这里有一个十二进制数字转换器

    $result = given $digit {
        when "T" { 10 }
        when "E" { 11 }
        $digit;
    }

尽管如此,将所有块排列整齐可能是有用的文档,这意味着最好有一个关键字。然而,由于当我们讨论异常处理程序时会变得更加清楚的原因,我不想使用else。另外,由于对whenif的识别,不清楚一个else是否应该在块的末尾自动提供一个break,就像普通的when情况所做的那样。

所以,我宁愿借鉴更多来自C语言的default

    $result = given $digit {
        when "T" { 10 }
        when "E" { 11 }
        default  { $digit }
    }

与C语言不同,default情况必须放在最后,因为Perl的case是按顺序评估的(或者至少是假装评估)。优化器通常可以确定哪些情况可以直接跳转,但在无法确定这种情况时,情况将按顺序评估,就像级联的if/elsif/else条件一样。此外,允许在case之间插入普通代码,在这种情况下,代码只有在上面的case失败匹配时才必须执行。例如,根据打印语句的指示,这应该正常工作

    given $given {
        print "about to check $first";
        when $first { ... }
        print "didn't match $first; let's try $next";
        when $next { ... }
        print "giving up";
        default { ... }
        die "panic: shouldn't see this";
    }

我们仍然可以将when定义为if的一个变体,这使得在需要时可以在两者之间混合使用这两种构造。所以我们将保留这个等价性——当你能以更熟悉的方式定义一个不太熟悉的构造时,这总是有助于人们思考。然而,default并不完全等同于else,因为else不能独立存在。一个default更像是总是为真的if。所以上面的代码等同于

    given $given {
        print "about to check $first";
        if $given =~ $first { ...; break }
        print "didn't match $first; let's try $next";
        if $given =~ $next { ...; break }
        print "giving up";
        if 1 { ...; break; }
        die "panic: shouldn't see this";
    }

我们确实需要在RFC中重新编写关系表来处理我们提到的某些调整和简化。裸引用的比较将不复存在。起初它并不特别有用,因为它只适用于标量引用。(为了匹配身份,我们无论如何都需要在任何地方有一个显式的.id方法。我们不会依赖于默认的numify或stringify方法来产生唯一的表示。)

我已经重新排列了表格的顺序,以便按顺序应用,以便默认解释出现在后面。此外,RFC中的“匹配代码”列给出了未解决的替代方案。在这些情况下,我选择了“true”定义而不是“exists”或“defined”定义。(除了某些与散列表的集合操作,人们真的不应该使用defined/undefined区分来表示true和false,因为在Perl中,true和false都被认为是已定义的概念。)

一些表格条目区分了数组和列表。数组看起来像这样

    when [1, 3, 5, 7, 9] { "odd digit intersection" }
    when @array          { "array intersection" }

而列表看起来像这样

    when 1, 3, 5, 7, 9    { "odd digit" }
    when @foo, @bar, @baz { "intersection with at least one array" }

通常情况下,在标量上下文中,列表和数组意味着相同的东西,但when在区分显式数组和列表方面是特殊的。在when内,列表是一个递归的析取。也就是说,逗号分隔的值被作为单独的情况进行OR运算。我们可以使用其他显式的符号来表示析取,例如

    when any(1, 3, 5, 7, 9) { "odd" }

但这似乎为这种常见的case情况带来了很多麻烦。我们可以使用一些语言中使用的垂直线,但我认为逗号读起来更好。

无论如何,下面是另一个简化。下面的表格还将定义Perl 6的=~运算符的工作方式!这允许我们使用递归定义来处理与析取列表的匹配。(参见下表的第一项。)当然,出于优先级的原因,要使用=~匹配一系列东西,你必须使用括号

    $digit =~ (1, 3, 5, 7, 9) and print "That's odd!";

或者,你可以将这个表格视为=~运算符的定义,然后说switch语句是由=~定义的。也就是说,对于任何形式为

    given EXPR1 {
        when EXPR2 { ... }
    }

的switch语句,它相当于说这个

    for (scalar(EXPR1)) {
        if ($_ =~ (EXPR2)) { ... }
    }

表1:将switch值与case值匹配

    $a      $b        Type of Match Implied    Matching Code
    ======  =====     =====================    =============

    expr    list      recursive disjunction    match if $a =~ any($b)
    list    list      recursive disjunction*   match if any($a) =~ any($b)

    hash    sub(%)    hash sub truth           match if $b(%$a)
    array   sub(@)    array sub truth          match if $b(@$a)
    expr    sub($)    scalar sub truth         match if $b($a)
    expr    sub()     simple closure truth*    match if $b()

    hash    hash      hash key intersection*   match if grep exists $a{$_}, $b.keys
    hash    array     hash value slice truth   match if grep {$a{$_}} @$b
    hash    regex     hash key grep            match if grep /$b/, keys %$a
    hash    scalar    hash entry truth         match if $a{$b}

    array   array     array intersection*      match if any(@$a) =~ any(@$b)
    array   regex     array grep               match if grep /$b/, @$a
    array   number    array entry truth        match if $a[$b]
    array   expr      array as list            match if any($a) =~ $b

    object  class     class membership         match if $a.isa($b)
    object  method    method truth             match if $a.$b()

    expr    regex     pattern match            match if $a =~ /$b/
    expr    subst     substitution match       match if $a =~ subst
    expr    number    numeric equality         match if $a == $b
    expr    string    string equality          match if $a eq $b
    expr    boolean   simple expression truth* match if $b
    expr    undef     undefined                match unless defined $a

    expr    expr      run-time guessing        match if ($a =~ $b) at runtime

为了便于优化,这些区别尽可能在编译时以语法形式进行。对于每次比较,也隐含了反向比较,所以$a/$b可以看作是给定/当或当/给定。(我们不反转带有*标记的匹配项,因为在那些情况下没有意义。)

如果无法在编译时确定匹配类型,则在运行时默认尝试以相同的顺序应用相同的规则,使用实际参数类型,而不是编译时类型的外观。请注意,没有与“方法”或“布尔值”对应的运行时类型。当然,这两个概念都可以在运行时作为一个闭包来表示。

实际上,每当默认行为不是您想要的行为时,都有方法可以强制参数按您想要的进行处理

    Intent      Natural           Forced
    ======      =======           ======
    array       @foo              [list] or @{expr}
    hash        %bar              {pairlist} or %{expr}
    sub(%)      { %^foo.aaa }     sub (%foo) { ... }
    sub(@)      { @^bar.bbb }     sub (@bar) { ... }
    sub($)      { $^baz.ccc }     sub ($baz) { ... }
    number      numeric literal   +expr int(expr) num(expr)
    string      string literal    _expr str(expr)
    regex       //, m//, qr//     /$(expr)/
    method      .foo(args)        { $_.$method(args) }
    boolean     $a == $b          ?expr or true expr or { expr }

方法必须使用一元点来书写,以区分其他形式。方法可以有参数。本质上,当您写

    .foo(1,2,3)

它被当作您写了

    { $_.foo(1,2,3) }

然后闭包被评估其真值。

类匹配仅在编译时知道类名时才有效。对于更复杂的情况,请使用.isa("Class")

布尔表达式通过顶层运算符的存在在编译时被识别为比较运算符或逻辑运算符。如表所示,无参数闭包(即sub ())也充当布尔值。然而,使用true函数(它是对not的反操作)可能更好地作为文档。或者使用一元?运算符(它是一元!运算符的反操作)。

可能会有人争论,布尔表达式在这里根本无处可用,如果您那样想,应该使用if。或者使用sub()闭包来强制它忽略给定。然而,“switch”的“comb”结构是编写甚至普通布尔表达式的极好方式,并且我更愿意他们能够写

    anyblock {
        when { $a == 1 } { ... }
        when { $b == 2 } { ... }
        when { $c == 3 } { ... }
        default          { ... }
    }

而不是这样

    anyblock {
        when $a == 1 { ... }
        when $b == 2 { ... }
        when $c == 3 { ... }
        default      { ... }
    }

这更适合在CATCH块中使用“when”

    CATCH {
        when $!.tag eq "foo" { ... }
        when $!.tag eq "bar" { ... }
        default              { die }
    }

要强制所有when子句都被解释为布尔值,而不在每个情况下使用布尔运算符,只需提供一个空给定,读作“给定无……”

    given () {
        when $a.isa(Ant) { ... }
        when $b.isa(Bat) { ... }
        when $c.isa(Cat) { ... }
        default          { ... }
    }

when可以被除given以外的其他主题化器使用。正如CATCH会隐含一个$!的给定一样,一个for循环(即foreach类型)也会隐含一个循环变量的给定。

    for @foo {
        when 1   { ... }
        when 2   { ... }
        when "x" { ... }
        default  { ... }
    }

通过对称性,默认情况下,given$_别名到“给定”。基本上,givenfor之间的唯一区别在于,given接受一个标量表达式,而for接受一个预先扁平化的列表并对其进行迭代。

假设您想要保留$_并将$g别名到值,您可以这样说

    given $value -> $g {
        when 1 { /foo/ }
        when 2 { /bar/ }
        when 3 { /baz/ }
    }

同样,循环的值可以别名到一个或多个循环变量。

    for @foo -> $a, $b {  # two at a time
        ...
    }

这非常类似于具有两个形式参数的子程序调用的定义,$a$b。(事实上,这正是它的意思。)您可以在形式参数上使用修饰符,就像您在子程序类型签名中那样做。这意味着别名自动声明为my变量。这也意味着您可以使用具有rw属性的修饰符来修改形式参数,这允许您通过变量修改数组的原始元素。默认循环

    for @foo { ... }

实际上编译成这样

    for @foo -> $_ is rw { ... }

由于forgiven是通过向闭包传递参数来工作的,因此将其推广到另一个方向只有一小步之遥。任何方法定义都是方法体内的主题化器,并将假定其$self对象(或您命名的任何对象)的“给定”。裸闭包将主题化其第一个参数,隐式地将其别名到$_,除非使用$^a或类似的内容。也就是说,如果您说这个

    grep { $_ eq 3 } @list

它等同于更明确地使用柯里化函数的这个更明确的使用

    grep { $^a eq 3 } @list

但是,即使是 grep 也可以使用上述别名语法

    grep -> $x { $x eq 3 } @list

在任何主题化范围之外,一个 when 会假设其给定的存储在 $_ 中,并将隐式地对该变量进行测试。这允许你在主循环中使用 when,例如,即使这个主循环是由 Perl 的 -n-p 开关提供的。每当循环作为开关运行时,结束一个案例所隐含的 break 就像 next 一样,而不是 last。如果你想这样做,请使用 last

when 是唯一一个关注当前主题化者的默认构造,无论它关联的是哪个变量。所有其他默认构造都关注一个固定变量,通常是 $_。因此,如果给定的别名不是 $_,请小心你匹配的内容。

    $_ = "foo";
    given "bar" -> $f {
        if /foo/   { ... } # true, matches against $_
        when /bar/ { ... } # true, matches against $f
    }

哦,还有一个调整。RFC 提出要重载 next 以表示“转到下一个案例”。我认为这并不明智,因为我们经常会想在使用开关语句时在循环中用循环控制。相反,我认为我们应该使用 skip 来做到这一点。(读作“跳到下一个语句。”)

类似地,如果我们创建一个单词来表示显式跳出主题化,它不应该是一个 last。我建议使用 break!当然,由于 break 是隐含的,因此从 when 案例的末尾跳出是不必要的。然而,有时你可能希望提前从 when 块中跳出。此外,由于我们允许不隐式跳出的 when 修饰符,我们可以使用显式 break 来处理这种情况。你可能会看到这样的案例

    given $x {
        warn("Odd value")        when !/xxx/;
        warn("No value"), break  when undef;

        when /aaa/ { break when 1; ... }
        when /bbb/ { break when 2; ... }
        when /ccc/ { break when 3; ... }
    }

在我看来,我们需要一个 break。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

RFC 088:综合结构化异常/错误处理机制

此 RFC 提出了一些异常处理的要求(我都同意),但我还有一些自己的额外要求

  • 异常捕获语法必须被视为一种开关语句的形式。
  • 应该很容易将任何类型的块转换为“try”块,尤其是子例程。
  • 甚至没有 try 的 try 块也必须能够指定退出时的强制清理。
  • 应该相对容易确定无论块是如何退出的,都需要多少清理。
  • 必须能够根据异常处理来基于 returnnextlast 的操作。
  • 清理机制应该很好地与设计-by-contract 下的后条件处理概念相匹配。
  • 异常捕获语法不得违反词法作用域的封装。
  • 同时,异常捕获语法不应强迫声明离开它们的自然范围。
  • 非线性控制流必须突出显示,充分利用块结构、缩进甚至关键字 case。《BEGIN》和《END》块应被视为先例。
  • 尚未抛出的异常必须是一个有用的概念。
  • 与任何其他语言的语法的兼容性特别不是一个目标。

RFC 88 非常庞大,有超过 2400 行。注释整个 RFC 会使这个末日预言太大。(Damian 说:“太晚了!”)尽管如此,我将采用引用 RFC 中的各种片段并将这些片段改写以适应我的额外要求的方法。希望这能最简洁地传达我的调整。

这是 RFC 提供的第一个示例

    exception 'Alarm';

    try {
        throw Alarm "a message", tag => "ABC.1234", ... ;
        }

    catch Alarm => { ... }

    catch Error::DB, Error::IO => { ... }

    catch $@ =~ /divide by 0/ => { ... }

    catch { ... }

    finally { ... }

这是我看如何用 Perl 6 编写这个示例

    my class X::Alarm is Exception { }     # inner class syntax?

    try {
        throw X::Alarm "a message", tag => "ABC.1234", ... ;

        CATCH {
            when X::Alarm             { ... }
            when Error::DB, Error::IO { ... }
            when /divide by 0/        { ... }
            default                   { ... }
        }
        POST { ... }
    }

外部块不必是 try 块。它可以是子例程、循环或任何其他类型的块,包括 eval 字符串或整个文件。我们将这种外部块称为 try 块,无论是否存在显式的 try 关键字。

最大的变化是将各种处理器移动到了try块内部。实际上,在我们的例子中,try关键字本身仅是文档说明,因为存在CATCHPOST块本身就足以表明需要捕获。请注意,POST块完全独立于CATCH块。(对于通过合同进行设计的程序员,POST块有一个对应的PRE块。)这些块中的任何一个都可以放置在周围块的任何位置——它们与周围的控制流无关。(当然,它们必须遵循它们所引用的任何声明。)只允许有一个CATCH,但可以有任意数量的PREPOST块。(实际上,我们可能鼓励自己在清理构造器附近放置POST块。)在特定的try块中的PRE块将按顺序评估,在块中的任何其他内容之前。虽然不鼓励在POST块之间存在顺序依赖,但POST块将按相反的顺序评估。

没有CATCHtry {}与Perl 5的eval {}等价。(实际上,在Perl 6中,eval将仅用于评估字符串,而try将仅用于评估块。)

CATCHPOST块自然位于try块的词法作用域中。它们可以安全地引用在try块中之前声明的词法作用域变量,即使在抛出异常的细化序列期间也是如此。(运行时系统将保证在细化之前,单个变量被测试为未定义(因此为假)。)

CATCH块内部的语法正好是switch语句的语法。switch语句的判别式是异常对象,$!。由于异常对象字符串化成错误消息,所以不需要显式地将when /divide by 0/$!比较。同样,显式地提及声明的类意味着进行“isa”查找,这是新switch语句的另一个内置功能。

事实上,以下形式的CATCH

    CATCH { 
        when xxx { ... }          # 1st case
        when yyy { ... }          # 2nd case
        ...                       # other cases, maybe a default
    }

 means something vaguely like:

    BEGIN {
        %MY.catcher = {
            given current_exception() -> $! {

                when xxx { ... }          # 1st case from above
                when yyy { ... }          # 2nd case from above
                ...                       # other cases, maybe a default

                die;            # rethrow $! as implicit default
            }
            $!.markclean;       # handled cleanly, in theory
        }
    }

统一的“当前异常”是$!。在本文档中使用$@的地方,应将其读作$!。(并且过于珍贵的@@将完全被存储在$!对象内部的数组所取代,可以通过@$!$![-1]访问。)(对于遗留的Perl 5解析器,$@$?将被模拟,但那将不会对Perl 6解析器可用。)

请注意,CATCH块隐式地提供了一个重新抛出(上面的die)。如果用户提供了显式的default情况,则不会到达这一点,因为那个默认情况的break将始终绕过隐式的die。如果switch重新抛出异常(无论是显式还是隐式),则由于die将绕过标记异常为“干净捕获”的代码,所以$!不会被标记为干净。应该将任何正常控制流之外的$!视为根据RFC定义的“干净捕获”,这应该被视为一个不变量。不干净的异常只能在CATCH块内部或任何必须执行的POST块内部看到,因为当前try块没有处理它而异常正在向外部块传播。(如果当前try块在其CATCH中成功处理异常,则同一级别的任何POST块都将看到一个已经标记为干净的$!。)

RFC

eval {die "Can't foo."}; print $@; 仍然按之前的方式工作。

在Perl 6中,将看起来像这样

    try { die "Can't foo" }; print $!;

没有CATCHtry

    try { ... }

是等价的

    try { ... CATCH { default { } } }

(这也是我不想为 switch 语句的默认情况使用 else 的另一个原因——一个没有 ifelse 看起来非常奇怪……)

顺便说一下,我这里尝试做的是将 eval 的异常捕获语义与其代码解析和执行语义分开。在 Perl 6 中,没有 eval {}。而 eval $string 实际上意味着以下内容

    try { $string.parse.run }

RFC

本 RFC 不要求核心 Perl 函数使用异常来报告错误。

然而,Perl 核心函数默认将未抛出的原型异常(即,有趣的 undefined 值)用于报告失败(也就是说,可以通过 die 轻松地转换为抛出异常)。这里的“有趣的 undefined 值”,我指的是完整的异常对象,这些对象恰好从其 .defined.true 方法返回 false。但是,.str 方法成功返回错误消息,而 .int 方法返回错误代码(如果有的话)。这意味着它们的行为类似于 $! 应该有的行为。当异常被抛出时,它变为定义和真实。控制异常在干净捕获时变为 false,以避免欺骗旧式异常处理器。

RFC

这意味着除非它们被干净捕获,否则所有异常都会传播,就像在 Perl 5 中一样。要防止这种情况,请使用

    try { fragile(); } catch { } # Go on no matter what.

这将仅仅是这样

    try { fragile; }

但它们的意思是相同的,并且仍然是这样,即所有异常除非被干净捕获,否则都会传播。在这种情况下,捕获到的异常将以新的原型异常的形式继续存在于 $! 中,可以被新的 die 再次抛出,就像我们过去使用 $@ 一样。当前是否认为异常是“干净捕获的”,可以反映在 $! 对象的状态上。当 $! 通过 CATCH 的结尾时,它被标记为干净,这样后续尝试建立新的 $! 就知道可以清除旧的 @$! 栈。(如果当前的 $! 不是干净的,它应该只添加其信息而不删除旧信息——否则,CATCH 中的错误可能会删除你即将打印出的异常信息。)

RFC

    try { ... } catch <test> => { ... } finally { ... }

现在

    { ... CATCH { when <test> { ... } } POST { ... } }

(尖括号实际上并不存在——我只是在复制 RFC 的元语法。)

请注意,我们假设一个测试,它匹配从 switch 智能矩阵的“布尔”条目。如果不匹配,你可以总是将闭包花括号围绕测试包裹起来

    { ... CATCH { when { <test> } { ... } } POST { ... } }

这将强制将测试调用为一个忽略其参数的子例程,这个参数恰好是 $!,异常对象。回想一下,CATCH 语句的隐含“given”将 $! 设置为给定值。这个给定值会自动传递到任何看起来像子例程或闭包的“when”情况,这些情况可以选择忽略传递的值,或者将其作为 $_$^a 访问。

或者你可能更喜欢使用一元 true 操作符

    { ... CATCH { when true <test> { ... } } POST { ... } }

我个人觉得这比闭包更易读。

RFC

捕获子句的测试参数是可选的,下面将进行描述。

由于从条件闭包区分后续块是可能的,因此 when 子句的测试参数不是可选的。使用 default 作为默认情况。

RFC

trycatchfinally 块应该共享相同的词法作用域,就像 whilecontinue 一样。

实际上,这并不如此——即使在 Perl 5 中,whilecontinue 块也不共享相同的词法作用域。但我们将解决这个问题而无需“隧道”。(我们将把 continue 块改为一个内部的 NEXT 块,这样我们就可以从其中引用词法变量。)

RFC

请注意,try 是一个关键字,不是一个函数。这样就不需要在最后一个块的末尾使用分号。这是因为 try/catch/finally 现在看起来更像 if/elsif/else,它不需要这样的分号,而不是像 eval,它需要。)

再次,在Perl 6中,这种区分将不复存在。任何以独立行上的右大括号结束的表达式块都将被解释为语句块。而try正是一个这样的表达式块。

RFC

$@包含当前异常,而@@包含当前异常堆栈,如上所述在die中定义。unshift规则保证了$@ == $@[0]

为什么是unshift?堆栈最自然的表现方式是朝另一个方向,我可以很容易地想象一些可能会将其当作堆栈处理的处理器,移除一些条目并推送其他条目。

此外,@@是不可行的,因为关于当前异常的所有信息都应该在一个单一的数据结构中。将所有信息都放在一个地方,使得重新抛出异常而不会丢失数据变得很容易,即使异常被标记为干净捕获的。此外,我认为异常堆栈不需要那么糟糕地使用Huffman编码。

因此,$!包含当前异常,而$!.stack访问当前异常堆栈。通过重载的魔力,即使$!对象本身不是数组,它也可以用作数组,在这种情况下,@$!指的就是那个堆栈成员。push规则保证了$!.id == $![-1].id

RFC(关于exception声明)

如果给定的名称与/::/匹配,会发生类似这样的事情

    @MyError::App::DB::Foo::ISA = 'MyError::App::DB';

并且所有不存在的父类都将自动创建为从其父类继承,或者在尾部情况下的Exception。如果发现父类存在但没有从Exception继承,将抛出一个运行时错误异常。

如果我理解正确,我认为我不同意。一个包应该能够包含异常,而不必是异常类本身。当然应该有当前包内异常的缩写。我怀疑它们是某种内部类,或者是内部包的内部类,或者是类似的。

RFC

如果给定的名称不与/::/匹配(比如说只是Alarm),那么会发生以下情况

    @Alarm::ISA = 'Exception';

这意味着每个异常类isa Exception,即使类名开头没有使用Exception::

哎呀!这可能真的很糟糕。如果有两个不同的模块声明了具有不同派生的不同Alarm异常呢?

我认为我们需要说明未限定的异常是在当前包内创建的,或者也许是在当前包的X子包内创建的。如果我们有内部类,它们甚至可以是词法作用域的(因此是当前模块外的匿名异常)。这可能是一个特性,也可能不是。

我还认为,尽管它们是从该类派生的,但Exception这个名字对于大多数常见的异常来说太长了,我认为如果异常具有像X::Errno这样的简洁名称,它们将更容易被接受,这些名称是从Exception派生出来的。

    our class X::Control is Exception;
    our class X::Errno is Exception;
    our class X::NumericError is Exception;

    our class C::NEXT is X::Control;
    our class E::NOSPC is X::Errno;
    our class X::FloatingUnderflow is X::NumericError;

或者,也许这些可以

    c::NEXT
    e::NOSPC
    x::FloatingUnderflow

如果我们决定大写名称太像用户定义的包名称。但这看起来很奇怪。也许我们只是为Perl保留单字母顶级包名称。见鬼,让我们只为Perl保留所有顶级包名称。呃,等等… :-)

RFC 80建议,当系统errno号可用时,异常对象将数值化为系统errno号。这是一个可能性,尽管根据当前的切换规则,我们可能必须写

    CATCH {
        when +$ENOSPC { ... }
    }

来强制$ENOSPC进行数字比较。这很可能更好,将errno数字制成异常类,即使我们不得不写像这样的事情

    CATCH {
        when X::ENOSPC { ... }
    }

这更长,但我认为更清晰。可能应该是E::NOSPC。但无论如何,我无法想象让人们将每个异常都以前缀“Exception::”开头。这只会阻止人们使用异常。我至少愿意保留X顶级类用于异常。我认为X::已经足够独特了。

RFC

    try { my $f = open "foo"; ... } finally { $f and close $f; }

现在

    {
        my $f = open "foo"; ...
        POST { $f and close $f }
    }

请注意,$f自然处于作用域内,并保证有一个布尔值,即使异常在声明语句展开之前抛出!(实现不需要在my之前实际分配变量。POST块的代码总是可以编译知道,如果分配代码尚未到达,$f将被假设为未定义。)

我们可以做到连不带守卫都做些合理的事情。

        POST { close $f }

也许一个未定义的对象可以在POST中为你“模拟”任何方法。也许try实际上是一个一元运算符

        POST { try close $f }

或者类似的。我不知道。这需要在事务性方面进行更多的思考...

时间流逝...

实际上,现在我想通了,围绕POST块添加包装器来执行提交或回滚,这会很简单。我想称它们为KEEPUNDOKEEP块只有在块成功时才会执行。UNDO块只有在块失败时才会执行。甚至可以设想一种语法,将块绑定到特定的变量。

    UNDO $f { close $f }

毕竟,和CATCH块一样,所有这些块都只是附有特定预定义属性的花哨的BEGIN块。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

虽然很诱人,但将UNDO的执行与块本身在执行期间是否传递相关联,但恐怕这可能会留下一个变量已经设置,但后续处理可能会在启用回滚之前抛出异常的窗口。因此,可能最好直接将它们与特定变量的状态绑定,而不仅仅是将块放在声明后的某个位置。实际上,它可以在声明时通过属性直接与所涉及的变量关联。

    my $f is undo { close $f } = open $file or die;

请注意,该块确实是闭包,因为它依赖于$f的词法作用域。(这种词法作用域在Perl 6中起作用,因为$f的名字立即在语句中引入。这不同于Perl 5的方法,在Perl 5中,名字直到当前语句的末尾才引入。)

实际上,如果close函数默认为$_,我们可以这样写

    my $f is undo { close } = open $file;

假设管理代码足够聪明,会将$f作为参数传递给闭包。同样,也可以用以下方式将POST块附加到变量上

    my $f is post { close } = open $file;

由于属性可以组合,可以在变量上设置多个处理程序

    my $f is post { close } is undo { unlink $file } = open ">$file" or die;

然而,没有与CATCH块对应的catch属性。

我想我们可以允许一个pre属性在变量上设置一个PRE块。

RFC

    sub attempt_closure_after_successful_candidate_file_open
    {
        my ($closure, @fileList) = @_; local (*F);
        foreach my $file (@fileList) {
            try { open F, $file; } catch { next; }
            try { &$closure(*F); } finally { close F; }
            return;
            }
        throw Exception "Can't open any file.",
               debug => @fileList . " tried.";
        }

现在

    sub attempt_closure_after_successful_candidate_file_open
      (&closure, @fileList)
    {
        foreach my $file (@fileList) {
            my $f is post { close }
                = try { open $file or die; CATCH { next } }
            &closure($f);
            return;
        }
        throw Exception "Can't open any file.",
               debug => @fileList . " tried.";
    }

请注意,CATCH中的next指的是循环,而不是CATCH块。从CATCH块中跳出是合法的,因为我们不会用next来贯穿switch情况。

然而,X::Control异常(如X::NEXT)是Exceptions的一个子集,所以

    CATCH {
        when Exception { ... }   # catch any exception
    }

将停止返回和循环退出。这可以被视为一个特性。当它被视为一个错误时,你可以说:

    CATCH {
        when X::Control { die }  # propagate control exceptions
        when Exception  { ... }  # catch all others
    }

来强制这样的控制异常向外传播。实际上,有一个非控制异常的名字会很好。然后我们可以(向Maxwell Smart致意)

    CATCH {
        when X::Chaos   { ... }  # catch non-control exceptions
    }

然后任何控制异常都将无阻碍地通过(因为默认情况下,未捕获的异常会被CATCH隐式重新抛出)。幸运的是或不幸的是,显式的default情况不会自动重新抛出控制异常。

以下是一些关于如何使用 when 表达式评估的更多示例。RFC 版本有时看起来更简洁,但请记住,Perl 6 中的“try”是任何代码块,而在 RFC 形式中,许多子例程中需要额外显式地添加一个 try 块。我更愿意建立一个文化,即期望子例程自己处理异常。

RFC

    try { ... } catch $@->{message} =~ /.../ => { ... }

现在

    try {
        ...
        CATCH {
            when $!.message =~ /.../ { ... }
        }
    }

这是因为 =~ 被视为一个布尔运算符。

RFC

    catch not &TooSevere => { ... }

现在

    when not &TooSevere { ... }

一元 not 也是一个布尔运算符。

RFC

    try { ... } catch ref $@ =~ /.../ => { ... }

现在

    try { ... CATCH { when $!.ref =~ /.../ { ... } } }

RFC

    try { ... } catch grep { $_->isa("Foo") } @@ => { ... }

现在

    try {
        ...
        CATCH {
            when grep { $_.isa(Foo) } @$! { ... }
        }
    }

我想我们也可以假设 grep 在标量上下文中是一个布尔运算符。但这有点笨拙。如果我们接受 Damian 的叠加 RFC,它可以这样编写

    try {
        ...
        CATCH {
            when true any(@$!).isa(Foo) { ... }
        }
    }

实际上,根据 =~ 表的“any”规则,我们只需说

    try {
        ...
        CATCH {
            when @$! =~ Foo { ... }
        }
    }

RFC 提出了以下用于最终化的语法

    try { my $p = P->new; my $q = Q->new; ... }
    finally { $p and $p->Done; }
    finally { $q and $q->Done; }

那个“...”掩盖了一个痛苦的世界,它可能会使 finally 子句远远偏离它们试图清理的内容。我认为使用 POST 可以使意图更清晰。而且请注意,我们也避免了 finally 所犯的“词法隧道”错误

    {
        my $p = P.new;   POST { $p and $p.Done; }
        my $q = Q.new;   POST { $q and $q.Done; }
        ...
    }

更简洁地说

    {
        my $p is post { .Done } = P.new;
        my $q is post { .Done } = Q.new;
        ...
    }

RFC

    try     { TryToFoo; }
    catch   { TryToHandle; }
    finally { TryToCleanUp; }
    catch   { throw Exception "Can't cleanly Foo."; }

我会怎么写

    try {
        try {
            TryToFoo;
            POST    { TryToCleanUp; }
            CATCH   { TryToHandle; }
        }
        CATCH   { throw Exception "Can't cleanly Foo."; }
    }

这也更清楚地向读者表明,最后的 CATCH 完全控制内部 try,而不仅仅是依赖于顺序。

RFC

实际(非子类化的)Exception 类的实例用于简单的异常,对于那些只需要说 throw Exception "My message." 的情况,没有很多额外的标记,也不需要进入异常分类的更高层次。

die "My message." 有几乎相同的效果。我认为 fail "My message."  也会默认相似,尽管返回或抛出语义取决于调用者的 use fatal 设置。

RFC(有关 on_raise

派生类可以覆盖此方法,尝试在抛出异常之前“处理”它或对其进行其他操作。如果 on_raise 抛出或返回 true,则抛出异常,否则不抛出。可以通过在 on_raise 中重新抛出异常来修改异常并传播修改后的形式。

我首先看不到这一点。这不仅似乎在重复犯 $SIG{__DIE__} 的错误,而且将“throw”用于不抛出的操作在我看来也没有意义。抛出应保证控制的终止,否则你将运行用户代码,而这些代码本不应该运行。这就像 return 突然不返回一样!请让我们使用不同的方法生成未抛出的异常。我认为 fail 方法是正确的方法——它以某种方式终止控制流,即使只是返回一个看起来很奇怪的 undefined。

on_catch 可能更有用。

RFC

…因为作者认为,将 elsecontinue 赋值与通常与 elsecontinue 不相关的 unwind 语义混合在一起可能会造成混淆,特别是在与 elsecontinue 的本地流控制形式(可能存在于任何 { ... } 块中)混合使用时,或者在忘记在需要重新抛出的 switch 中的 else die $@ 时。

CATCH 默认会重新抛出(除非有用户指定的默认值)。

RFC

一些关于 perl6-language-error 的讨论建议完全省略 try,就像简单地写作 { } else { } 来表示正在工作的非局部流控制一样。哎呀!

《try》关键字并非为了Perl本身,而是为了方便开发者使用。它提醒开发者注意,这里正在进行某种非局部流程控制。它表明了处理远距离操作(展开语义)的意图,这满足了动机部分列出的第一个要求。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

try {}eval {}的新写法,因此当需要自我文档化时仍然可以使用。然而,由于我认为全部大写的CATCHPOST也起到了提醒开发者的作用,所以它通常是多余的。我预计开发者会习惯于许多子程序以CATCH块结束。而且,我总是倾向于在实际可行的情况下减少普通代码的括号数量。(这就是为什么package声明始终有不带括号的语法。我希望在Perl 6中为类和模块做同样的事情。)

RFC

在条件捕获子句中的逗号或=是必需的,以便表达式可以从块中解析出来,就像Perl 5解析map<expression>, <list>这样的语法。如果没有逗号,形式为catch $foo { … }可能被解释为对$foo的测试,或者对$foo{…}(哈希元素)的测试。

我们现在要求在非索引块之前必须有空白,所以这并不是一个大问题。

RFC

我们如何使Exception成为子类并控制类命名空间?例如,如果核心可以使用任何Exception::Foo,那么非核心的Exception应该怎样连接到分类中?可能核心异常可以派生自Exception::CORE,而其他人可以使用Exception::MyPackage约定。

我认为将事物定义为核心与非核心并没有什么用处——“核心”并不是异常的基本类型。我认为标准的异常分类应该是可以扩展的,以便非标准的异常可以随着时间的推移迁移到标准。我还认为,模块和类应该有自己的子包来存储异常。

RFC

我们如何向从Exception派生的类添加新的实例变量和方法,并控制这些命名空间?也许这将被新的Perl 6对象技术所覆盖。否则,我们可能还需要另一种命名方案约定。

派生类中的实例变量和方法不会干扰基类(除了通过正常隐藏重复方法名)。

RFC

Exception对象实例变量如果不指定给构造函数,应该有什么默认值?例如,tag可以默认为文件加行号。

这取决于构造函数,我猜。

RFC

如果有的话,应该在实例变量上放置哪些断言?

这很可能取决于类。

RFC

字符串化应该返回什么?

我倾向于只返回消息,并为更多信息提供不同的方法。但这在很大程度上取决于我们为所有对象定义的表示方法。而这还没有完全考虑清楚。

RFC

混合流程控制

一些参考文本在讨论异常处理时提到,实现跨越展开语义块的go to可能很困难,就像在

        try { open F, $f } catch { next; }

这个问题将需要参考内部专家。如果这个功能不可行,可以用词法状态变量来模拟。这是可以接受的。

然而,作者非常希望goto可以跨越展开边界。如果这不可能,希望可以产生某种编译时警告。

我们可以通过特殊控制异常来实现这一点,这些异常只有在有意义的时刻才会被捕获。(控制异常在类层次结构中的确切位置仍有待商榷。)无论如何,从CATCH中抛出控制异常没有问题,因为任何在CATCHPOST中抛出的异常都会在任何情况下传播到当前try块之外。

普通的 goto 应该可以工作,只要它离开当前 try 范围。通过 goto 在中间重新进入 try 可能是不可行的,甚至是不希望的。一旦事情清理完毕,失败的 try 应该从顶部重新进入。(如果 try 是循环块,从其 CATCH 跳到下一次迭代可能被认为是安全的,就像循环内有显式的 try 块一样。但我可能在这方面是错误的。)

RFC

使用 %@ 来处理内建函数的错误。

*RFC 151 提出了一种合并 $@$!$?$^E 提供的信息的机制。根据 RFC 88 的作者的观点,合并 $@$! 的工作不应进行,因为 $@ 在抛出异常时设置。

RFC 似乎没有为这种最后的断言提供理由。如果我们统一错误变量,没有参数的 die 可以简单地抛出 $! 的当前值,并且我们可以完全保持面向对象。然后 $! 表示当前错误,无论它是否被抛出。它跟踪自己的状态,即它是否处于“不干净”的状态,并且除非它是干净的,否则拒绝丢弃信息。

%@ 应该根据以下对称性论点来保留这个故障哈希。

        $@    current exception
        @@    current exception stack
        %@    current core fault information

        $@[0]        same as $@

        $@{type}     "IO::File::NotFound"
        $@{message}  "can't find file"
        $@{param}    "/foo/bar/baz.dat"
        $@{child}    $?
        $@{errno}    $!
        $@{os_err}   $^E
        $@{chunk}    That chunk thingy in some msgs.
        $@{file}     Source file name of caller.
        $@{line}     Source line number of caller.

%@ 应不应该包含严重性或致命分类。

每次调用核心 API 函数都应清除 %@,如果它返回成功。

内部,Perl 可以使用简单的结构化数据类型来保存整个规范化的 %@。处理从 %@ 读取的代码将根据动态的内部数据构建它。

如果存在 use fatal;,那么在返回之前,每个核心 API 函数应该做类似以下操作: %@ and internal_die %@;

内部 die 成为生成规范 Exception 以封装 %@ 的唯一位置,无论是否由如 use exceptions; 这样的祈使句控制这种规范 Exception 的使用。

在我看来,这个 %@ 提案似乎只是一堆不必要的复杂性。可以同样容易(且懒惰地)构造一个具有方法的原型异常对象,并且它可以直接映射到一个真实的异常,而不是这个哈希。并且对象总是可以用作哈希来访问无参数方法,如实例变量访问器。

RFC

eval

eval 的语义是,“清除 $@ 并不回滚,除非用户在 eval 之后重新死亡”。try 的语义是,“在 try 之后回滚,除非任何抛出的异常都被干净和完整地处理,在这种情况下清除 $@”。

作者认为,在 Perl 6 中,evaltry 都应该存在。这也意味着在 Perl 6 中,如何使用 eval 的传统用法仍然有效。

当然,我们仍然需要 eval $string

关于 perl6-language-errors 的讨论表明,有些人希望从 Perl 6 中移除 eval { ... } 形式,因为 Perl 中有两个异常处理方法可能会让开发者感到困惑。这实际上是有可能的,因为可以通过以下方法达到相同的效果。

        try { } catch { } # Clears $@.

        my $e;

        try { ... } catch { $e = $@; }

        # now process $e instead of $@

另一方面,由于它已经按这种方式工作,eval 是所有这些的方便同义词。

我认为没有必要保留eval {...}的确切语义。我认为让裸露的try {...}默认具有CATCH { default {} }就足够接近了。实际上,很少的Perl 5程序会关心在eval内部是否设置了$@。考虑到这一点和我们对$!的定义,从Perl 5到Perl 6的转换只需要简单地将eval {...}改为try {...},将$@改为$!(在try捕获后作为一个“干净”的异常继续存在)。或许可以尝试将外部处理程序拉入内部的CATCH块。

RFC

catch 与 else + switch

关于perl6-language-errors的讨论中,一些参与者表达了这样的观点:不仅应该使用eval而不是try,而且还应该使用else而不是多个catch块。他们认为,应该使用else { switch ... }来处理多个catch子句,如下所示:

        eval { ... }
        else {
            switch ($@) {
                case $@->isa("Exception::IO") { ... }
                case $@->my_method { ... }
                }
            }

这个问题是:如何使代码隐式地重新抛出未捕获的异常?许多支持这种模型的人认为,未捕获的异常不应该隐式地重新抛出;有人建议,程序员应该在undef $@在每一个成功的case块结束时,这样Perl就会重新抛出在else结束时仍然存在的任何$@

这个RFC允许在catch { ... }子句中使用switch,对于在catch <expr`&gt;{ … }子句中减少冗余代码的方法,但是按照本RFC中提出的机制,上面的switch功能可以写成如下形式,同时仍然在没有任何case匹配时保持自动异常传播:

        try { ... }
        catch Exception::IO => { ... }
        catch $@->my_method => { ... }

switch结构运行良好,因为每个已处理的case的隐式break跳过了由CATCH提供的默认重新抛出。没有必要发明一个并行的机制,也没有理由这么做。

RFC

机制钩子

为了可扩展性和调试,应该有钩子用于回调,当进入或退出trycatchfinally块,以及当评估条件catch时被调用。回调将被传递关于它们被调用的上下文中发生的情况的信息。

为了限制回调的效果(而不是使它们成为全局的),建议将回调指定为try语句的选项,如下所示

    try on_catch_enter => sub { ... },
        on_catch_exit  => sub { ... },
    {
        ...
        }

这些回调的(动态的,不是词法的)作用域是从它们自己的try开始,通过所有嵌套在其下的try(直到在较低级别被覆盖)。嵌套的回调应该有一种方法来链接到它们进入作用域时在作用域内的回调,可能通过将外部作用域回调的引用作为参数传递给回调。基本上,它们可以保存在“全局”变量中,这些变量可以用local来覆盖。

真糟糕。我不喜欢用本质上是对动态作用域全局变量的临时赋值来弄乱try语法。应该足够说类似于以下内容

    {
        temp &*on_catch_enter = sub { ... };
        temp &*on_catch_exit  = sub { ... };
        ...
    }

当然,前提是实现足够聪明,知道何时需要查找这些钩子。

RFC

混合模式模块

如果提供这种机制,希望提供尊重use fatal;当前状态的公共API的模块的作者可以这样做到。

在模块内部,作者可以使用词法作用域的use fatal;来明确控制是否希望内置函数抛出异常以指示错误。

然后,如果他们想支持另一种风格,并且只为公共API子程序支持,他们会做类似以下之一的事情

  • 在内部使用return,现在添加在API中使用throw的支持

         sub Foo
         {
            my $err_code = ... ; # real code goes here
    
            # Replace the old return $err_code with this:
    
            return $err_code unless $FATAL_MODE && $error_code != $ok;
    
            throw Error::Code "Couldn't Foo.", code => $err_code;
            }
    
  • 在内部使用throw,添加在API中使用return的支持

         sub Foo
         {
            try {
                # real code goes here, may execute:
    
                throw Exception "Couldn't foo.", code => $err_code;
                }
            catch !$FATAL_MODE => { return $@->{code}; }
    
            return $ok;
            }
    

天哪。机制太多了。为什么不只

    return proto Exception "Couldn't foo.", code => $err_code;

当调用模块需要时,proto 方法可以实现标准的 use fatal 语义,否则设置系统以便

    Foo() or die;

最终抛出 proto-异常。 (当前的 proto-异常可以保存在 $! 中,用于消息,前提是它在线程局部存储中。)

实际上,这一点非常重要,要尽量简化。我赞成有一个内建的函数,清楚地说明正在发生什么,无论它最终是抛出异常还是返回 undef

    fail "Couldn't foo", errno => 2;

顺便提一下,可以说所有这样的“内建函数”实际上是隐式类或对象的成员方法。在这种情况下,Exception 类...

RFC

$SIG{__DIE__}

try、catch 和 finally 子句在进入它们的作用域之前局部化并取消 $SIG{__DIE__} 的定义。如果移除 $SIG{__DIE__},则可以移除这种行为。

$SIG{__DIE__} 必须引发异常。至少,这个名字必须引发异常——我们可能为了调试目的安装一个类似的全局钩子。

RFC

遗留问题

本 RFC 对 Perl 5 行为的影响仅限于以下几点:(1) $@ 现在始终是一个 Exception 对象(可以合理地转换为字符串),它是只读的,并且只能通过 die 来设置;(2) @@ 数组现在是特殊的,也是只读的。

也许 $! 可以隐式声明为 Exception 类型。但我看不出为什么要使 $! 默认为只读。这样做只能阻止聪明人做一些我们还没想到的聪明事。而且它不能阻止愚蠢的人做愚蠢的事。无论如何,$! 只是对象的引用,对对象的访问将由类控制,而不是由 Perl 控制。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

RFC 199:短路内建函数和用户定义子程序

首先,我应该顺便提一下,很可能会

    my ($found) = grep { $_ == 1 } (1..1_000_000);

足够聪明,可以在不需要额外提示的情况下在第一个上停止,因为左侧只会要求右侧的一个值。

然而,我们需要统一内建函数与用户定义控制结构的操作。从内部角度来看,所有这些退出块的方法都将统一为异常。

对于用户定义的子程序来说,捕获适当的异常并执行正确的事情是非常容易的。例如,为了实现循环包装器(忽略解析问题),你可以写一些像这样的事情

    sub mywhile ($keyword, &condition, &block) {
        my $l = $keyword.label;
        while (&condition()) {
            &block();
            CATCH {
                my $t = $!.tag;
                when X::Control::next { die if $t && $t ne $l); next }
                when X::Control::last { die if $t && $t ne $l); last }
                when X::Control::redo { die if $t && $t ne $l); redo }
            }
        }
    }

记住,那些 die 调用只是当前异常的重新抛出,以跳过当前的 try 范围(在这个例子中是 while)。

一个块在一般情况下如何获得标签是一个有趣的问题。说关键字是标签很有道理,但如果你有两个具有相同名称的嵌套结构,这就没有帮助。在 Perl 5 中,标签被限制在语句的开始处,但那么你怎么给 grep 标签呢?是否应该有一种方法在关键字上而不是在语句上指定标签?我们可能会得到类似这样的东西

    my $found = grep:NUM { $_ == 1 and last NUM: $_ } (1..1_000_000);

另一方面,考虑到这个特性(不)经常被使用,我认为我们可以坚持使用经过考验的语句标签

    my $found = do { NUM: grep { $_ == 1 and last NUM: $_ } (1..1_000_000) };

这有两个优点:在两个地方都将标签语法与冒号匹配。我喜欢这一点。

我认为不应该让每个块隐式地有一个返回方式,否则我们将难以优化掉没有做任何块状事情的块。这是因为设置 try 环境始终有点像块状,而且实际上确实强加了我们可以避免的一些开销。

但是,如果某些结构知道如何处理标签,并且没有显式标签,那么它们可以通过关键字名称隐式标记,我认为这是可以接受的

    last grep: $_

尽管它的外观看起来像是一个方法调用,但 grep 不是一个预定义的类。我们有一个一元运算符 last,它接受一个状语修饰符,指定从循环中返回什么。

随着我们的继续进行,有趣的政策问题将是给定的结构是否对给定的异常作出响应。某些异常将必须限制其使用。例如,我们可能应该说只有显式的 sub 声明可以对 return 做出响应。人们会期望 return 退出他们认为所在的子例程,即使周围有实际是闭包在其他地方被解释的代码块。对于像 grepmapsort 这样的闭包解释器来说,可能被视为反社会的,如果它们比用户期望的更早捕捉到 X::Control::return。

至于使用数字而不是标签来指示要跳出多少层,这应该是可以的,除了我不相信按层跳出。如果问题足够复杂,以至于你需要跳出超过一层,你需要一个名称,而不是数字。那么,无论你重构代码以拥有更多的代码块层还是更少的代码块层,这都没有关系。我发现我经常需要以这种方式重构我的代码。

有可能沉迷于为 grepmap 配置各种可能的终止、重试、接受、拒绝、减少、重用、回收或任何异常。我不认为这是必要的。偶尔写自己的代码必须有一些理由。如果我们消除了编写用户定义子例程的所有理由,我们不妨收拾行李回家。但至少将循环结构视为循环是可以的。

RFC 006:词法变量默认启用

本RFC提议默认启用 strict vars。这是出于希望Perl更好地支持(或在这种情况下是说服)能够在大规模编程中成功编程的纪律。这个目标是值得赞扬的。

然而,小型编程的倡导者也有一个有效的观点:他们不希望为了写一个简洁的一行代码而去麻烦地关闭限制,因为在这样的编程中,按键非常宝贵,而且事实上,在大程序中增加清晰性的限制往往在小程序中减少清晰性。

因此,这是我们希望两全其美的领域之一,事实上,我们基本上可以做到。唯一的问题是划在哪里。一些讨论建议只有通过命令行上的 -e 开关指定的程序应免除限制。但我不想强迫每个基于文件的小脚本都进入大规模编程模式。而且我们不需要这样做。

大规模编程需要定义模块和类。典型的大程序(或应该)主要是由模块和类组成的。因此,模块和类将假定 strict vars。小型编程通常不需要定义模块和类,尽管它可能依赖于现有的模块和类。但即使是大量使用外部模块和类的程序也可能被视为临时代码。程序的主要代码通常不会被重用(在模块和类重用的意义上),这意味着我们应该在那里划线。因此,在Perl 6中,主程序不会假定 strict vars,除非你明确地做些什么来开启它,比如声明“class Main”。

RFC 330:全局动态变量应保持默认

这对主程序来说是好的,但模块和类应遵守更高的标准 use strict

RFC 083:使常量看起来像变量

记住变量和值之间的区别是很重要的。在纯面向对象的环境中,变量仅仅是值的引用,没有自己的属性——只有值本身才能说它是否是常量。一些值是自然不变的,比如字面字符串,而其他值可以是标记为常量的,或者创建时不包含可以修改对象的方法,或者类似这样的机制。在这样的环境中,在变量上使用属性用处不大。任何时间你在变量上放置属性,它都可能对你的值撒谎。

然而,Perl 并不追求成为一个纯面向对象的环境。在Perl的思考方式中,变量不仅仅是一个值的容器。相反,变量提供了一个“视图”来观察值。有时这种视图甚至可以被视为谎言。这没问题。对自己撒谎是一种有用的生存技能(除非不是这样)。我们发现,当我们觉得自己做不到的时候,重复“我认为我能”是有必要的。反过来,在心理上,将可能的活动视为禁止的通常是很有价值的。如果不需要每次都可能分配时重新决定,禁欲就更容易实践了。

在变量上的常量声明属于这一类。值本身可能或可能不是自然不变的,但我们将假装它是。从理论上讲,我们可以走得更远。我们可以检查相关对象以确保它是常量的,如果不是就崩溃,但在这种情况下,为了保持语义的一致性,这不是必需的。其他属性可能对此更为严格。例如,如果你有一个变量属性声称一个特定的多维数组形状,相关的对象最好能够提供与该视图一致的语义,如果不能,尽早崩溃可能是个好主意。这有点像强类型,但是它是可选的,因为变量属性本身是可选的。

尽管如此,这些变量属性的目的在于允许编译器推断出它无法通过其他方式推断出的关于程序的信息,并且基于这些推断,产生一个更健壮、更高效的编译时程序语义解释。也就是说,你可以在不牺牲安全性的情况下进行更多的优化。这在内联常量的情况下显然是正确的,但这个原则也适用于其他变量属性。

建议的语法是好的,只是我们将使用 is 而不是 : 来表示属性,正如在第2次Apocalypse中讨论的那样。(应该是 constant,而不是 const。)

RFC 337:通用属性系统以允许用户定义的可扩展属性

如第2次Apocalypse中已经揭示的,在Perl 6中,属性将被称为“属性”,以避免与现有的面向对象命名法中的实例变量混淆。我们还将使用 is 关键字而不是冒号。

在数组和哈希元素上设置属性让我感到困扰,尤其是当这些属性具有“公共”和“私有”这样的名称时。这在我看来是一种试图填补某些缺少的面向对象功能空缺的做法。所以,我宁愿让数组和哈希保持主要用于同质数据结构,并鼓励人们使用对象来存储不同类型的数据。这样,公共和私有就可以成为对象属性的属性,它们的声明方式将更类似于真正的变量。而且我们不需要担心 my @foo[2] 的含义,因为仍然不允许这样做。

再次强调,表示变量的对象与变量包含的任何对象是不同的。当我们说

    my Dog @dogpound is loud;

我们这里的含义是@dogpound的各个元素属于Dog类型,而不是数组变量本身属于Dog类型。但是loud属性适用于数组,而不是数组中的狗。如果数组变量需要指定类型,可以像属性一样提供。

    my Dog @dogpound is DogPound is loud;

也就是说,如果属性是已知包/类的名称,它被视为一种tie。根据上述声明,以下总是正确的:

    @dogpound.is.loud

因为即使数组对象中不包含狗,loud也是数组的属性。结果是:

    @dogpound.is.DogPound

也是正确的。这并不执行isa查找。为此,可以说:

    @dogpound.isa(Pound)

注意,您可以使用:

    @dogpound =~ Dog

来测试单个元素是否具有狗性。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

RFC 173:允许在foreach语句中允许多个循环变量

不幸的是,提议的语法也可以被解释为并行遍历

  foreach ($a, $b) (@a, @b)

此外,RFC假设键将作为两个元素传递,这已不再是必然的情况。在列表上下文中,哈希本身将返回一个键值对对象列表。我们将需要说:

    %hash.kv

以获取键与值的交替列表。(同样方法在数组上产生交替索引和值。)

我喜欢这个RFC的想法,但提议的语法并不是我所希望的。还有各种可能的语法,这些语法也可能满足RFC 120的意图

    for [$i => $elem] (@array) { }
    for {$i => $elem} (@array) { }
    for ($i, $elem) = (@array.kv) { }

但我喜欢重复绑定的感觉。我们可以使用:=绑定操作符,但由于绑定实际上是子程序形式参数执行的操作,并且我们希望保持列表靠近for,形式参数靠近闭包,我们将使用子程序声明的变体来声明for循环

    for @list -> $x { ... }         # one value at a time
    for @list -> $a, $b { ... }     # two values at a time

你可以通过以下方式取消数组的交织:

    for @xyxyxy -> $x, $y { ... }

并行遍历多个列表需要类似于多维切片的语法。也就是说,类似于比逗号绑定更松的逗号。由于我们将使用分号为此目的来分隔多维切片的维度,我们将使用类似的分号来分隔多个列表的并行遍历:因此,并行数组可以像这样遍历

    for @xxx; @yyy; @zzz -> $x; $y; $z { ... }

如果右边有分号,则必须与左边相同数量。

每个“流”都是单独考虑的,因此您可以像这样每次遍历两个数组,每次两个元素:

    for @ababab; @cdcdcd -> $a, $b; $c, $d { ... }

如果右边没有分号,则按顺序在流中取值。所以你可以这样说

    for @aaaa; @bbbb -> $a, $b { ... }

并且最终意味着与逗号是分号一样的东西,但仅仅因为右边的变量数量碰巧与右边的流数量相同。这不必是这种情况。为了跨三个流逐个获取值,您可以这样说

    for @a; @b; @c -> $x { ... }

每个由分号分隔的表达式都被认为是生成值的列表,因此可以在左边使用逗号或“无限”范围。以下将无限期地(或至少非常长时间)打印“a0”,“b2”,“c3”,等等:

    for 0 .. Inf; "a" .. "z" x 1000 -> $i; $a {
        print "$a$i";
    }

RFC 019:重命名局部操作符

我们将使用temp作为临时操作符。

此外,我们将在对象中存储更多的全局状态(例如文件对象)。因此,应该可以临时化(即检查点/恢复)对象的属性,或者至少可以被视为lvalue的任何属性。

RFC 064:新的pragma 'scope'以更改Perl的默认作用域

我不能阻止人们进行实验,但我自己并不特别想进行这个实验。我使用 my 也有其原因。因此,我在原则上接受这个RFC,但仅限于原则上。标准Perl声明将以 myour 明确标注。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

被拒绝的RFC

仅仅因为我拒绝了这些RFC,并不意味着它们没有解决一个有效需求。通常一个RFC被拒绝仅仅是因为我认为有更好的方法来做。被拒绝的RFC和我借鉴了想法的RFC,以及被接受但附带重大保留意见的RFC之间往往没有太大区别。

我们已经说得够多了,所以这些描述将会很简短。如果您不理解评论,请阅读RFC。

RFC 089:可控数据类型

这几乎是我们长期以来为Perl所做的计划。然而,其中一些具体细节并不理想。

如果您声明了一个常量,它就是一个常量。默认允许对该常量的警告是没有意义的。修改常量应该是致命的。否则,您将失去所有的优化可能性。

由于历史原因,以下赋值在

     my ($a, $b) = new Foo;

将不会自动分配到 $a$b 上。如果您想那样做,可以使用 ^= 超级赋值,也许吧。

约束列表有些模糊的吸引力,但似乎过于机制化,而可能的收益却不多。如果您真的想定义一个多态的数据类型,为什么不直接定义一个多态类型呢?

总的来说,这个RFC在变量约束和价值约束之间似乎存在很多混淆。为了使约束对编译器有用,它们必须针对变量,并且在运行时不能“推送”约束。

关于通过子程序调用进行别名,请注意,声明的参数将默认为常量。

所以,尽管我拒绝了这项RFC,但我们将肯定会有一个与RFC中的一些表格相似的声明语法。

RFC 106:另一个词法变量提案:使词法变量成为默认

是的,确实如此,像Ruby这样的其他广受赞誉的语言也进行了词法变量的隐式声明,但我觉得这是一个错误,其结果直到事情变得复杂时才会显现。(这表明Ruby中存在这种弱点的解决方案,即通过伪造赋值来强制声明变量。)

我不喜欢词法变量的隐式声明,因为它往往会破坏它们的主要用途,即捕获打字错误。意外声明额外的变量名真是太容易了。同样,意外扩展变量作用域也太容易了。您可能有一系列各自拥有自己词法变量的独立子程序,突然发现由于您在模块初始化代码中意外使用了相同的变量名,它们都是同一个变量。

当你仔细想想,要求在声明时使用 my 是一种正交性。否则,你会发现在默认的作用域规则中任意地绑定到内部作用域、外部作用域或子程序作用域。这些都是次优选择。我也不认同根据需要可选地使用 my 来消除歧义的观点。Perl给你很多上吊的绳子,但这是错误类型的绳子,因为它模糊了必要的视觉区别。声明应该看起来像声明,不仅对程序员,而且对任何在程序员之后阅读程序的人,无论是碳基的还是硅基的。

而且,归根结底,我相信带有 my 的声明是适当Huffman编码的。声明一个词法变量应该比对其赋值更困难。声明一个全局变量应该比声明一个词法变量更困难(至少在类和模块内部)。

RFC 119:通过异常进行对象中性错误处理

目标很好,但我不想再有一个独立的异常处理系统。简单是通过统一实现的。此外,提出的语法对我来说过于纠缠。让我看看,我该如何解释我的意思?

对我来说,带外信息在视觉上并不突出,我也不喜欢把它当作控制流来思考。尽管如此,我认为我们最终得到的东西解决了这份RFC中指出的许多问题。RFC基本上要求在语句级别实现POSTKEEPUNDO的功能。虽然POSTKEEPUNDO块不能附加到任何语句,但我认为允许在范围声明中包含postkeepundo属性是足够强大的,并且为编译器提供了可附着的具体行动。将这些行动附加到特定变量上有一丝精确性——状态以事务瞬时的方式绑定到变量上。我担心如果我们像RFC所建议的那样将事务性操作附加到语句上,那么将不清楚何时应将语句的状态变化视为成功,因为事务无法“知道”哪个操作是关键的。

尽管如此,这份RFC中的一些想法将体现在postkeepundo属性块中。

RFC 120:for语句中的隐式计数器,可能为$#。

我对此有所偏见,仅仅是因为我多次因为隐式变量带来的隐式开销而受到伤害。我认为如果你需要一个索引,你应该声明一个,这样如果没声明,编译器就知道不必为它设置。

另一个问题是人们会不断询问它

    for (@foo,@bar) { print $# }

应该代表什么意思。

我预计我们最终会得到我们之前讨论过的类似的东西

    for @array.kv -> $i, $elem { ... }

RFC 262:索引属性

如今,每个人都有使用:的需要…

这个似乎不是非常有用,它面临着与RFC 120提案类似的问题。我认为除非我们知道了循环结构的编译时间信息,否则不可能有效地跟踪每个包含对象中的值容器,这在使用用户定义的控制结构时是有问题的。

那么如果一项是多个列表的成员呢?

再次,我宁愿有声明,这样我们就知道是否要承担开销。这样,我们就不必在不能进行完整的静态分析时进行优化。

RFC 167:简化do BLOCK语法

我认为在do块上的“do”有助于强调花括号内的闭包将被立即执行。否则,Perl(或用户(或两者))可能会混淆,不知道是否有人试图编写稍后执行的闭包,特别是如果该块是可能想要返回闭包的子程序中的最后一个项目。事实上,我们可能会将裸块在语句级别定为过于模糊,不建议使用。当你想要一个一次性循环时,请使用for 1 {}或类似的结构,当你想要返回闭包时,请使用returnsub

我们将通过调整{...}的定义来解决;问题,而不是通过调整do

RFC 209:Perl中的完整整数支持。

旧的use integer祈使句是一个漏洞。我认为我宁愿使用类型和表示规范来为编译时选择单个声明,或者在需要无限精度时使用替代对象构造函数进行运行时选择。我不反对使用祈使句来更改默认设置,但我认为当你有这个能力时,更具体一点通常是更好的。你可以使用祈使句强制你的程序具有词法作用域,但数据想流向哪里就流向哪里,因此你的词法作用域模块必须能够理性地处理任何扔给它的数据,即使它不是你偏好的精确形式。

顺便说一下,RFC在断言32位整数精度在以浮点数表示时丢失时是误导性的。这只有在使用32位浮点数时才是真的。Perl始终使用64位双精度浮点数,这提供了大约15位的整数精度。(当然,64位整数也存在这个问题。)

以上所述,Perl 6 将肯定会更好地支持各种类型的整数。但我认为,用pragma重新定义“整数”是什么,并不能为试图理解程序的人提供好的文档。最好是声明类型为MagicNum,或者其他什么类型。

当然,我可能错了。如果是这样,就写你的pragma,享受相应的乐趣。

RFC 279:my()语法扩展和属性声明

我们已经在Apocalypse 2中讨论过这个问题。

RFC假设类型总是分布在my列表上。这对于函数签名是不必要的,因为函数签名需要对每个形式参数使用单个类型。

再次,我认为在运行时在变量上设置属性没什么意义。

对我来说,能够按词法声明数组元素的类型更没有意义。这是对象的地盘,而不是假扮成结构体的数组。

RFC 297:编译器提示的属性

抱歉,我们不能仅仅因为用户决定通过不同的翻译器运行程序,就让语义突然发生巨大的变化。我认为其中有一个快乐的中间地带,我们可以让解释器和编译器都有相同的语义。

RFC 309:允许子原型中的关键字

这个RFC被拒绝仅仅是因为它还不够远。我们最终需要的可能是允许一个类似正则表达式的语法符号来解析,这可能不同于参数声明。(然后又可能不是。)无论如何,我认为需要某种明确的正则表达式符号,而不是将标识符提升为标记匹配器。我们可能以后需要在签名中使用标识符来表示其他东西,所以我们保留它们。

RFC 340:with接受一个上下文

这看起来像是一个寻找问题的解决方案。即使我们最终得到像Perl 5那样明确的上下文堆栈,我也不认为我们处理它的数量值得使用关键字。(而且我讨厌“return with;”这种毫无必要的晦涩语言结构。)

话虽如此,如果有人实现了(作为用户定义的代码)RFC 342(和拒绝)中提出的Pascalish with,并且如果caller函数(或类似函数)返回足够的信息来建立对相关调用帧的词法作用域的引用,那么类似的东西也可以作为用户代码实现。我不能决定这是不是一个好主意,或者这是不是一个坏主意。无论如何,我会警告任何人这样做可能会非常混乱,就像goto-considered-harmful一样,尽管在这种情况下是通过替换作用域而不是控制流。

请注意,某些类似于这样的机制将对于模块向词法作用域导出(参见Apocalypse 2中的%MY)是必要的。然而,词法作用域的修改只允许在相关词法作用域的编译时进行,因为我们需要小心保持词法作用域提供的封装。将词法变量转换回动态变量往往会破坏这种安全性。

因此,我认为我们将坚持使用不携带词法作用域的闭包和延续。

RFC 342:类似于Pascal的"with"

我期望Perl的解析能力足够强大,以至于你可以写一个“with”,如果你需要的话。

编者按:这篇启示录已经过时,但出于历史原因仍保留在此。有关最新信息,请参阅摘要04

撤回的RFC

RFC 063:异常处理语法

RFC 113:更好的常量和常量折叠


其他决定

C样式for循环

由于Perl 6新for语法的句法歧义,通用C风格的for循环的关键字将更改为loop。现在for总是意味着“foreach”。表达式“pill”现在是可选的,所以你不再需要像这样写一个无限循环

    for (;;) {
        ...
    }

现在你可以这样写

    loop {
        ...
    }

C样式do {} while EXPR不再受支持

在Perl 5中,当你在仅由do {}组成的语句上使用while语句修饰符时,会发生某种神奇的事情,这个块会在条件评估之前被评估一次。这个特殊案例的结构,很少使用,而且经常被误解,将不再存在于Perl 6中,实际上将产生一个编译时错误,以防止人们尝试使用它。在Perl 5代码中有这样的

    do {
        ...
    } while CONDITION;

Perl 6 代码将使用一种使控制流程更加明确的构造。

    loop {
        ...
        last unless CONDITION;
    }

裸块

在 Perl 5 中,裸块(用作语句的块)是一次性循环。在 Perl 6 中,块是闭包。自动执行任何闭包在 void 上下文中的操作是可能的,但不幸的是,当闭包用作外部块的最后一个语句时,你想要返回还是执行闭包并不明确。因此,无论是否在 void 上下文中,将闭包用作语句级别的操作都将被视为错误。使用 do {} 来创建“一次性”块,并使用显式的 returnsub 来返回闭包的引用。

continue块

continue 块的名称已更改为 NEXT,并将其移动到它修改的块内部,以像 POST 块一样工作。除此之外,这还允许 NEXT 块在 NEXT 块之后引用循环内部声明的词法变量。广义循环

    loop (EXPR1; EXPR2; EXPR3) { ... }

现在可以定义为等同于

    EXPR1;
    while EXPR2 {
        NEXT { EXPR3 }
        ...
    }

(除了在 EXPR3 中声明的任何变量将具有不同的词法作用域)。NEXT 块仅在尝试循环的下一个迭代之前调用。当循环完成并即将退出时,它不会被调用。使用 POST 来实现这一点。

好了,现在就到这里吧。你可能想知道,我正在从第二个半年度 Perl Whirl 航游中发布这个信息,在加勒比海的某处的 Veendam 船上。如果这艘船在百慕大三角洲消失,你不必担心即将到来的 Exegesis,因为 Damian 也在这艘船上。但就目前而言,Perl 6 正在航行,天气很棒,希望你在那里。

标签

反馈

这篇文章有什么问题吗?通过在 GitHub 上打开一个问题或拉取请求来帮助我们。