代码之家  ›  专栏  ›  技术社区  ›  codesections

在不创建GOTO的情况下捕获异常

  •  1
  • codesections  · 技术社区  · 4 年前

    翻看我的Raku代码,我意识到我几乎从未使用过 CATCH 块来实际捕获/处理错误。相反,我用 try 块和测试未定义的值;我唯一用的东西 接住 块用于以不同的方式记录错误。我似乎不是唯一一个养成这种习惯的人——看着 接住 街上的街区 Raku docs ,除了打印一条消息之外,几乎没有一个能处理任何意义上的错误。(大多数情况也是如此。) 接住 拉库多的街区。)。

    尽管如此,我还是想更好地理解如何使用 接住 阻碍。让我来介绍几个示例函数,所有这些函数都基于以下基本思想:

    sub might-die($n) { $n %% 2 ?? 'lives' !! die 418 }
    

    现在,正如我所说,我通常会使用这个函数

    say try { might-die(3) } // 'default';
    

    但我想在这里避免这种情况,并使用 接住 函数内部的块。我的第一本能是写作

    sub might-die1($n) {
        $n %% 2 ?? 'lives' !! die 418
        CATCH { default { 'default' }}
    }
    

    但这不仅不起作用,而且(非常有用!)甚至都不会编译。显然 接住 街区是 从控制流中移除(如我所想)。因此,该块而不是三元表达式是函数中的最后一条语句。好吧,很公平。这个怎么样:

        sub might-die2($n) {
    ln1:    CATCH { default { 'default' }}
    ln2:    $n %% 2 ?? 'lives' !! die 418
        }
    

    (这些行号是 Lables .是的,它是有效的Raku,是的,它们在这里没用。但SO没有给出行号,我想要一些。)

    这至少可以编译,但它不符合我的意思。

    say might-die2(3);  # OUTPUT: «Nil»
    

    对于DWIM,我可以将此更改为

        sub might-die3($n) {
    ln1:    CATCH { default { return 'default' }}
    ln2:    $n %% 2 ?? 'lives' !! die 418
        }
    say might-die3(3);  # OUTPUT: «'default'»
    

    这两个结果表明 接住 街区是 ,就像我跳的那样,被插入到发生异常的控制流中。相反,异常会导致控制流跳转到 用于封闭范围的块。这就好像我们写过(在另一个宇宙中,Raku有一个 GOTO 操作员[编辑:或者可能不是 那个 宇宙的交替,因为我们显然 a NYI goto method .每天学习新东西

        sub might-die4($n) {
    ln0:    GOTO ln2;
    ln1:    return 'default';
    ln2:    $n %% 2 ?? 'lives' !! GOTO ln1;
        }
    

    我意识到一些例外的批评者 say 他们可以减少到 后藤 声明,但这似乎有点牵强。

    我可以(大部分)避免效仿 后藤 .resume 方法,但我不能按我喜欢的方式做。具体来说,我不会写:

        sub might-die5($n) {
    ln1:    CATCH { default { .resume('default') }}
    ln2:    $n %% 2 ?? 'lives' !! die 418
        }
    

    因为 简历 不需要争论。我 可以

        sub might-die6($n) {
    ln1:    CATCH { default { .resume }}
    ln2:    $n %% 2 ?? 'lives' !! do { die 418; 'default' }
        }
    say might-die6 3;  # OUTPUT: «'default'»
    

    至少在这个特定的例子中,这是有效的。但我忍不住觉得这更像是一种黑客行为,而不是一个实际的解决方案,而且它不会很好地推广。事实上,我忍不住觉得,我在Raku错误处理背后缺少了一些更大的洞见,这些洞见将使所有这些更好地结合在一起。(可能是因为我花了太多时间用处理错误的语言编程,没有例外?)如果您能深入了解如何用惯用的Raku语言编写上述代码,我将不胜感激。上述方法之一基本正确吗?有没有其他方法我没有考虑过?在这一切中,我对错误处理有没有更深入的了解?

    0 回复  |  直到 4 年前
        1
  •  9
  •   raiph    4 年前

    “更深入地了解错误处理”

    (在我的问题中)有一种方法基本正确吗?

    对在一般情况下,使用以下功能: try if CATCH .

    有没有其他方法我没有考虑过?

    这里有一个全新的: catch .几周前我发明了它的第一个版本,现在你的问题促使我重新思考它。我对现在的解决方式很满意;我很感谢读者对此事的反馈。

    在这一切中,我对错误处理有没有更深入的了解?

    在回答的最后,我将讨论我的一些想法。

    现在让我们按照你写的顺序来讨论你的观点。

    我几乎从不使用 接住 块来实际捕获/处理错误。

    我也是。

    相反,我用 尝试 块和未定义值的测试

    更像是这样。

    使用catchall记录错误 接住

    我唯一用的东西 接住 块用于以不同的方式记录错误。

    正当位置合理的捕获物 全部的 .我想说的是,这是一个用例 接住 很合身。

    医生

    看着 接住 街上的街区 Raku docs

    如果文件在以下方面存在误导:

    • 能力和适用性的限制 接住 / CONTROL 阻碍;和/或

    • 替代方案;和/或

    • 什么是惯用语 接住 代码在哪里 尝试 更合适(现在是我的新 接住 功能也一样?)。

    那就太不幸了。

    接住 Rakudo编译器源代码中的块

    (大多数情况也是如此。) 接住 拉库多的街区。)。

    .resume ,或 die 或者类似的),在我看来是合理的。他们都是这样吗?

    为什么 phasers 声明?

    sub might-die1($n) {
        $n %% 2 ?? 'lives' !! die 418
        CATCH { default { 'default' }}
    }
    

    这不仅不起作用,而且(非常有用!)甚至都不会编译。

    .oO(那是因为你忘了在第一个语句末尾加分号)

    (我本以为……是 接住 块[本应]从控制流中移除)

    加入俱乐部。其他人在文件bug中表达了相关的观点,所以Q和A。我过去认为当前的情况与你表达的方式一样是错误的。我想我现在很容易被争论的任何一方说服——但jnthn的观点对我来说是决定性的。


    引用文件:

    相位器块是 只是一种特质 并在适当的时候自动调用。

    这表明相位器是 一条语句,至少不是一般意义上的语句,可以推测,它将从普通的控制流中删除。

    但回到医生那里:

    移相器[可能]有一个运行时值,如果[在]周围的表达式中进行计算,它们只需保存其结果,以便在表达式中使用。。。当表达式的其余部分被计算时。

    这表明他们 可以 祝你好运 价值 在一个 普通的 控制流量感应。


    也许是 将移相器从普通控制流中移除,转而评估 Nil 如果它们没有返回值,则类似于:

    • 移相器 INIT 返回值。编译器可以坚持将结果赋给一个变量,然后显式返回该变量。但这将是非常不合时宜的。

    • Raku的理念是,一般来说,开发人员会告诉编译器做什么或不做什么,而不是反过来。相位器是一种陈述。如果将语句放在末尾,则希望它是其封闭块返回的值。(即使是 .)


    不过,总的来说,我在以下意义上支持你们:

    • 认为普通控制流不包括不返回值的相量似乎很自然。为什么要这么做?

    • 看起来IWBNI至少是编译器 警告 如果它看到一个不返回值的相位器被用作包含其他返回值语句的块的最后一条语句。

    为什么不呢 接住 块返回/注入值?

    好吧,很公平。这个怎么样:

        sub might-die2($n) {
    ln1:    CATCH { default { 'default' }}
    ln2:    $n %% 2 ?? 'lives' !! die 418
        }
    
        say might-die2(3);  # OUTPUT: «Nil»
    

    如上所述,许多相量,包括异常处理相量,都是不返回值的语句。

    我认为人们可以合理地预期:

    • 接住 移相器 返回一个值。但他们没有。我隐约记得jnthn已经解释了为什么会这样;我将把寻找这一点作为读者的练习。或者反过来说:

    • 编译器会警告说,没有返回值的移相器被放置在可能有返回值的地方。


    就好像我们写了。。。A. GOTO 操作人员

    Raku(do)不仅仅是做一个无组织的跳跃。

    (否则 简历 不起作用。)

    这似乎有点牵强

    我同意,你把事情做得太过分了P

    简历

    可恢复的例外情况当然不是我在拉库找到的东西。我认为我还没有在“用户空间”代码中使用它们。

    (来自 jnthn's answer to When would I want to resume a Raku exception? .)

    简历 不需要争论

    正当它只是在导致抛出异常的语句之后,在该语句处继续执行。 简历 不会改变失败语句的结果。

    即使 接住 块试图进行干预,但它无法以简单、自包含的方式进行干预,方法是设置赋值引发异常的变量的值,然后 简历 惯性导航与制导。查阅 Should this Raku CATCH block be able to change variables in the lexical scope? .

    (我试了几次 接住 在得出结论之前的相关方法 尝试 这是找到尸体的方法 接住 我在一开始链接的函数。如果你还没看过 接住 代码,我建议你这样做。)

    更多关于 接住 阻碍

    他们有点担心有几个原因。一是对其预期能力和适用性的刻意限制。另一个是虫子。例如,考虑:

    更深入地了解错误处理

    在这一切中,我对错误处理有没有更深入的了解?

    可能我想你已经很清楚了,但是:

    • 吻#1 在其他PLs中,您处理过错误,没有例外。成功了。你在拉库做过。它起作用了。仅当您 需要 希望 使用它们。对于大多数代码,你不会。

    • 吻#2 忽略一些本机类型用例,几乎所有结果都可以表示为有效或无效,而不会导致 semi-predicate problem ,使用以下Raku的简单组合 Truth value 提供工效学方法来区分非错误值和错误:

      • 条件: 如果 , while , 尝试 , //

      • 谓词: .so , .defined , .DEFINITE

      • 值/类型: , Failure s、 零长度复合数据结构, :D vs :U 类型约束等

    关于错误例外,我认为有几点值得考虑:

    • Raku错误异常的一个用例是覆盖与Haskell中的异常相同的基础。在这些情况下,将其作为值处理不是正确的解决方案(或者,在Raku, 可以 不可能)。

    • 其他请支持例外。Raku的超级大国之一是能够与所有其他PLs进行互操作。因此,它支持异常,除非是为了实现正确的互操作。

    • Raku包含了 失败 ,一个延迟的例外。我们的想法是,你可以两全其美。谨慎处理 失败 只是一个错误值。如果不小心处理,它就像一个常规的例外。

    更一般地说,Raku的所有功能设计为协同工作,以提供方便但高质量的错误处理,支持以下所有编码方案:

    • 快速编码。原型、探索性代码、一次性等。

    • 鲁棒性控制。逐渐缩小或扩大错误处理。

    • 选择多种多样。应该指出哪些错误?什么时候按哪个密码?如果消费代码想要表明生成代码应该更严格,该怎么办?还是更放松?如果反过来呢-- 生产 代码想发出这样的信号 消费 代码应该更小心还是可以放松?如果生成和使用代码的理念相互冲突,该怎么办?如果生成的代码无法更改(例如,它是一个库,或者是用另一种语言编写的),该怎么办?

    • 语言/代码库之间的互操作。唯一可行的方法是Raku提供高水平的控制和多样化的选择。

    • 在这些场景之间进行方便的重构。

    所有这些因素,以及更多因素,都是Raku错误处理方法的基础。

        2
  •  5
  •   Brad Gilbert    4 年前

    CATCH 这是一个非常古老的语言特征。
    它过去只存在 inside of a try
    (这可不是很放肆。)

    它也是Raku很少使用的一部分。
    也就是说,没有多少人想到 痛点 这是该功能的一部分。
    因此,很少有人做任何工作来让它变得更加放荡。

    这两个因素的结合使得 接住 是语言中一个毫无特色的部分。
    如果查看该特性的测试文件,您会注意到其中大部分是 written in 2009 当测试套件仍然是帕格斯项目的一部分时。
    (其余大部分都是多年来发现的漏洞测试。)


    有一个很好的理由是,很少有人尝试在新的行为中添加新的行为 接住 ,还有很多其他功能更适合使用。

    如果要在发生异常时替换结果

    sub may-die () {
      if Bool.pick {
        return 'normal'
      } else {
        die
      }
    }
    
    my $result;
    {
      CATCH { default { $result = 'replacement' }}
      $result = may-die();
    }
    

    使用起来容易多了 尝试 没有 接住 ,以及定义的–或 // 得到一些非常相似的东西。

    my $result = try { may-die } // 'replacement';
    

    如果你在处理软故障而不是硬异常,那就更容易了,因为你可以直接使用defined–of‘或它本身。

    sub may-fail () {
      if Bool.pick {
        return 'normal'
      } else {
        fail
      }
    }
    
    my $result = may-fail() // 'replacement';
    

    事实上,使用 接住 软失败就是把它和 尝试

    my $result;
    try {
      CATCH { default { $result = 'replacement' }}
      $result = may-fail();
    }
    

    如果你的软失败是所有失败对象的基础 Nil ,你可以使用 // is default

    my $result = may-return-nil // 'replacement';
    
    my $result is default<replacement> = may-return-nil;
    

    但是 不只是和你一起工作 接住 不管你有多 尝试 .


    真的是我平时唯一会用的时间 接住 就是我想用不同的方式处理几个不同的错误。

    {
      CATCH {
        when X::Something { … }
        when X::This      { … }
        when X::That      { … }
    
        default           { … }
      }
    
      # some code that may throw X::This
      …
      # some code that may throw X::NotSpecified (default)
      …
      # some code that may throw X::Something
      …
      # some code that may throw X::This or X::That
      …
    
      # some code that may fail instead of throw
      # (sunk so that it will throw immediately)
      sink may-fail;
    }
    

    或者,如果我想展示你是如何写出这条(糟糕的)Visual Basic代码的

    On Error Resume Next
    

    在拉库

    CATCH { default { .resume } }
    

    这当然一点也不能真正回答你的问题。

    你说你期待 接住 从控制流中移除。 关键是 接住 就是将自己插入到异常的控制流中。

    事实上这并不准确。没什么大不了的 插入 在转到调用方/外部块之前,在进行一些处理时结束控制流。可能是因为当前块的数据处于错误状态,不应再被信任。

    这仍然不能解释为什么你的代码无法编译。 你期望 接住 在语句结尾的分号上有自己特殊的语法规则。 如果它按照您预期的方式工作,它将不符合Raku中重要的[语法]规则之一,那么“应该尽可能少地使用特殊情况”。它的语法在任何方面都不像你想象的那样特别。

    接住 它只是许多具有一个重要额外功能位的移相器中的一个,它阻止了异常在调用堆栈中的传播。

    你似乎要求它改变一个表达式的结果,而这个表达式可能会抛出。

    这似乎不是个好主意。

    $a + may-die() + $b
    

    您希望能够替换来自的异常 may-die 有价值。

    $a + 42 + $b
    

    基本上,您要求能够将动作距离作为一个特性添加。

    还有一个问题,如果你真的想要 $a + may‑die 取而代之的是。

    42 + $b
    

    你的想法中没有办法具体说明这一点。

    更糟糕的是,有一种方式可能会意外发生。如果…怎么办 may‑die 开始返回失败而不是异常。然后,它只会在您尝试使用它时导致异常,例如通过将其添加到 $a .


    如果某些代码抛出异常,则该块处于不可恢复状态,需要停止执行。这么远,不远了。

    如果表达式引发异常,则执行其所在语句的结果是可疑的。
    其他语句可能依赖于该断开的语句,因此整个块也是可疑的。

    我认为,如果它允许代码继续,但对当前表达式产生不同的结果,那就不是一个好主意。尤其是如果该值可以从块内其他地方的表达式中移除。(行动距离)

    如果你能想出一些代码,这些代码可以通过 .resume(value) ,那么也许可以添加它。
    (我个人认为 leave(value) 在这种情况下会更有用。)

    .简历(价值) 似乎它对控制异常很有用。
    (被抓到) CONTROL 而不是 接住 .)

    推荐文章