当前位置: 首页 > 知识库问答 >
问题:

从Perl 6中的异常处理程序返回值

燕超
2023-03-14

我一直在尝试编写一个Perl 6表达式,它执行以下逻辑:计算子表达式并返回其值,但是如果这样做导致引发异常,则捕获异常并返回固定值。

例如,假设我想将两个数字相除,如果发生错误,则表达式的计算结果为-1。我可以用Ruby写:

quotient = begin; a / b; rescue; -1; end

在Emacs Lisp中,可以写为:

(setq quotient (condition-case nil (/ a b) (error -1))

我的第一次Perl 6尝试是这样的:

sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };

但是在这里,$quotient最终是未定义的,不管$b是否为零。

似乎忽略了CATCH返回的值,或者至少在描述异常如何工作的文档页面上,所有CATCH主体只做一些有副作用的事情,比如日志记录。

该页面提到了try作为替代方案。例如,我可以写:

my $quotient = try { might-throw($a, $b) } // -1;

我觉得这是一个相当令人失望的解决办法。首先,我正在计算的表达式可能确实有一个未定义的值,我无法将其与引发异常的情况区分开来。另一方面,我可能希望根据抛出的异常的类别返回不同的值,但是try只是将它们全部吞下。我可以将我自己的CATCH块放在try中,以区分异常,但是我回到上面的第一个案例,其中忽略了CATCH中的值。

Perl 6的异常处理能像我在上面所说的那样吗?

编辑:

目前的答案是信息丰富的,但过于狭隘地集中在除法算子的语义学上。我稍微重写了这个问题,使异常捕获的主要问题更加中心。

共有3个答案

李浩邈
2023-03-14

这似乎是一个设计和/或实现缺陷:

Rakudo愉快地将一个Int除以0,返回一个Rat。你可以。Numit(产生Inf)和。perl可以使用它,但如果您尝试,它会爆炸。Str。要点it。

相反,除以Num0e0将立即失败。

为了保持一致性,整数除以零可能也会失败。另一种选择是返回一个在字符串化时不会爆炸的常规值,但我反对它。。。

顾英发
2023-03-14

TL;DR本答案的最后一部分讨论了您的尝试中发生的其他人未能解决的问题。其余部分介绍了trys,这是一种全面解决您的问题的解决方案。

以下是几个非常简单的例子:

say trys { die }, { -1 }                          # -1

say trys { die }, { when X::AdHoc { 42 } }        # 42

trys

>

  • 这不是拼写错误[3]

    获取一个或多个可调用的列表(函数、lambda等)。

    将环境异常作为主题传递给每个可调用的

    依次尝试s每个可调用,直到:

    • 所有“失败”(抛出异常或以其他方式拒绝结果)。如果是这样,trys返回一个Failure,它封装了最后一个可调用的抛出的异常(或者如果可选:$list throws,则所有异常都被传递)

    或者

    • 一个成功。如果是,trys返回成功的结果。
    unit module X2;
    
    our sub trys ( **@blocks,                 #= List of code blocks.
                   :$reject = (),             #= Value(s) to be rejected.
                   :$HANDLED = True,          #= Mark `Failure` as handled?
                   :$list-throws is copy      #= List *all* throws in final `Failure` payload?
                             = False,
                 ) is export {
    
      $! = CLIENT::<$!>;                      # Set argument of first block to caller's `$!`.
      if $! and $list-throws                  # Include caller's `$!` in list of throws.
        { $list-throws = [].push: $! }        # (Reuse `$list-throws` for store. Why not? :))
    
      my $result is default(Nil);             # At least temporarily preserve a `Nil` result.
    
      for @blocks -> &block {
        $result = try { block $! }            # Try block with `$!` from previous try as topic.
        if not $! and $result ~~ $reject.any  # Promote result to exception.
          { $! = X::AdHoc.new: payload => "Rejected $result.gist()" }
        if $! and $list-throws
          { $list-throws .push: $! }
        return $result unless $!;             # Return result if block didn't throw.
      }
    
      if $list-throws
        { $! = X::AdHoc.new: payload => $list-throws }
    
      given Failure.new: $! {                 # Convert exception(s) to `Failure`.
        .handled = True if $HANDLED;
        .return
      }
    }
    

    glot.io上的代码(包括此答案中的所有trys代码)。

    use X2;
    
    # `trys` tries a list of callables, short circuiting if one "works":
    say trys {die}, {42}, {fail}                  # 42
    
    # By default, "works" means no exception thrown and result is not a `Failure`:
    say trys {die}, {fail}, {42}                  # 42
    
    # An (optional) `:reject` argument lets you specify
    # additional value(s) you want rejected if they match via infix `~~`:
    say trys :reject(Nil,/o/), {Nil}, {'no'}, {2} # 2
    
    # If no callable works, the last error is converted into a `Failure` and returned:
    say trys :reject(Nil), {Nil}                  # (HANDLED) Rejected Nil
    say trys {die}                                # (HANDLED) Died
    say trys {(42/0).Str}                         # (HANDLED) Attempt to divide by zero
    # To stop last error `Failure`s being handled, specify optional argument `:!HANDLED`:
    say (trys {(42/0).Str}, :!HANDLED) .handled;  # False
    
    # The first callable is passed the caller's current exception as its topic:
    $! = X::AdHoc.new: payload => 'foo';
    trys {.say}                                   # foo
    
    # Subsequent callables are passed the exception from the prior callable as their topic:
    trys {die 'bar'}, *.say;                      # bar
    trys {fail 'bar'}, {die "$_ baz"}, *.say;     # bar baz
    
    # Unless there's an exception in the guts of `trys`, the caller's `$!` is left alone:
    say $!;                                       # foo
    
    # To make final `Failure` payload be a *list*, specify optional argument `:list-throws`:
    say trys {die 'bar'}, :list-throws;           # (HANDLED) foo bar
    # (`list-throws` includes the caller's original `$!` if it was defined.)
    
    # Some "traps" are specific to the way `trys` works:
    
    say trys { ... } // 42;                   # "(HANDLED) Stub code executed"
    say trys { ... }, { 42 }                  # 42   <-- Do this instead.
    
    #trys 22;                                 # Type check failed ... got Int (22)
    #trys {}                                  # Type check failed ... got Hash ({})
    say trys {;}                              # Nil   <-- Use blocks instead.
    
    # Other "traps" are due to the way Raku works:
    
    # Surprise `False` result if callable has `when`s but none match:
    say do   {when rand { 42 }}               # False   <-- It's how Raku works.
    say trys {when rand { 42 }}               # False   <-- So same with `trys`.
    say trys {when rand { 42 }; Nil}          # Nil     <-- Succinct fix.
    say trys {when rand { 42 }; default {}}   # Nil     <-- Verbose fix.
    
    # Surprise `(Any)` result if callable's last/return value is explicitly `$!`:
    $! = X::AdHoc.new: payload => 'foo';
    say try {$!}                              # (Any)   <-- It's how Raku works.
    say $!;                                   # (Any)   <-- Clears `$!` *before* return!
    $! = X::AdHoc.new: payload => 'foo';
    say trys {$_}                             # (Any)   <-- `trys` has same return behaviour.
    say $!;                                   # foo     <-- (But doesn't clear caller's `$!`.)
    $! = X::AdHoc.new: payload => 'foo';
    say try {$!.self}                         # foo     <-- One way to fix with `try`.
    say $!;                                   # (Any)   <-- (Still clears caller's `$!`.)
    $! = X::AdHoc.new: payload => 'foo';
    say trys {.self}                          # foo     <-- Suggested fix with `trys`.
    say $!;                                   # foo     <-- (Doesn't clear caller's `$!`.)
    

    trys组合了tryCATCH的选定部分:

    >

  • 它就像try,因为它的可调用代码对它们的代码执行try。但是:

    >

  • 不支持报表格式<代码>trys 42将不起作用。

    它将异常转换为故障。

    它允许用户指定他们想要提升到Failures的值列表。

    这与CATCH类似,因为每个可调用的对象都传递了一个异常作为其主题。但是:

    >

  • 可调用项不是相位器块;它们不会自动调用。

    它返回一个值(与CATCH块不同)。

    每个可调用的trys可以扮演一个try角色,一个CATCH角色,或者两者兼而有之。

    我的第一次尝试是这样的:

    sub might-throw($a, $b) { die "Zero" if $b == 0; $a / $b }
    my $quotient = do { might-throw($a, $b); CATCH { default { -1 } } };
    

    一个CATCH块总是返回Nil。它是闭包正文中的最后一个语句,因此总是返回Nil。(这是一把似乎应该修理的足球枪。参见在不创建GOTO的情况下实际捕获异常中的进一步讨论)

    我可能会写例如:

    my $quotient = try { might-throw($a, $b) } // -1;
    

    我正在计算的表达式可能真的有一个未定义的值,我无法将其与引发异常的情况区分开来。

    你可以写:

    my $quotient is default(-1) = try { might-throw($a, $b) }
    

    这里发生了什么:

    >

  • 是默认值特性声明变量的默认值是什么,如果变量未初始化以及试图赋值Nil,则使用该特性。(虽然Nil在技术上是一个未定义的值,但其目的是表示“无值或良性故障”。)

    try定义为返回Nil,如果在其计算期间引发异常。

    如果要区分由于抛出异常而返回的Nil和由于Nil的普通返回而返回的Nil,这可能仍然不令人满意。或者,也许更重要的是:

    我可能希望根据抛出的异常的类别返回不同的值,但是try会将它们全部吞下。

    这需要一个解决方案,但不是CATCH

    我可以把我自己的CATCH块放在try中以区分异常,但是我又回到了上面的第一种情况

    相反,现在有了我创建的trys函数。

    [1]正如您所指出的:“当前的答案……过于狭隘地关注除法运算符的语义。”。所以我在脚注中总结了这一点,也就是说:为了支持高等数学,Raku不会自动将有理数除以零(例如1/0)视为异常/错误。Raku随后的双重延迟异常处理是一种转移注意力的方法。

    [2]捕获也是一种转移注意力的手段。即使与一起使用,它也不会返回值或注入值。恢复,因此它是完成需要完成的工作的错误工具。

    [3]有些人可能认为trys最好拼写为trys。但是我故意把它拼出来了。为什么?因为:

    >

  • 在英语中,在trytry相关的程度上,它是非常密切相关的。选择这个词的奇怪之处在于提醒人们,它不仅仅是复数形式的尝试。也就是说,粗略的意思与try有些密切相关,所以拼写它trys仍然有意义。

    我喜欢奇思妙想。显然,在阿尔巴尼亚语中,trys的意思是“按压、压缩、挤压”。与try类似,trys功能“按下”代码(“按”在“压力”的意义上),并“压缩”它(与不使用trys的冗长性相比),并“挤压”所有与异常相关的错误机制--异常s、故障s、Nils、try捕捉。继续——合并为一个。

    在立陶宛语中,trys的意思是"三"。

    >

  • 拒绝三种类型的结果:异常;失败;和用户指定的值。

    保持事情以三种方式滚动:传递调用者的$!到第一个可调用的;调用后续的可调用的最后一个异常作为他们的主题;将抛出在最后一个块的异常变成一个Failure

    解决编程中最难的事情之一命名:trystry相似,但明显不同;我在此预测很少有开发者会在他们的代码中使用阿尔巴尼亚语或立陶宛语单词trys代码;选择trys而不是try可以减少与现有代码冲突的可能性。:)

  • 韦俊英
    2023-03-14

    捕获块不起作用的原因是除以零本身并不是一个错误。Perl6会很乐意让你除以零,并将该值存储为Rat。当您想以有用的方式显示所述Rat时,问题就出现了(IEsayit)。这时,如果未处理,返回的失败将变成异常。

    所以你有几个选择。在进行$q之前,您可以检查$b

    $q = $b == 0 ?? -1 !! $a / $b; 
    

    或者,如果你想保持真实值(注意,你可以内省老鼠的分子和分母,而不会导致被零除的错误),当你说它时,你可以使用。perl。Num版本。

    两者都提供了Rat的十进制表示形式。perlgiving

     类似资料:
    • 1.1 异常处理的基本使用 try: <语句块1> except: <语句块2> try 捕获异常 except 发生异常时执行 try: <语句块1> except <异常类型名字>: <语句块2> except <异常类型名字> 发生对应异常时才会执行 1.2 异常处理的高级使用 try: <语句块1> except

    • 我有一个Groovy Spring Boot微服务,它返回一个帖子列表。请求进入控制器,控制器调用服务类中的方法,如果没有找到POST,则抛出自定义错误消息。 我已经创建了一个带有@ControllerAdvice注释的控制器,我想截获错误,并创建了一个特定于自定义错误的处理程序。它应该返回一个POJO。目前正在调用ControllerAdvice处理程序,但来自microservice的响应是5

    • 问题内容: 我在Utilities类中使用loadImage方法,并且在通过闭包返回图像时遇到了一些麻烦。基本上因为我的代码可能返回图像或错误,所以在调用该方法时将其分配给image属性是行不通的。 我在类的方法声明中使用的方法是否错误,还是应该以不同的方式调用该方法以预期潜在的不同结果?谢谢 问题答案: 将处理程序添加到您的 loadImage 函数中: 迅捷3 像这样调用func: 斯威夫特2

    • 因此,我试图以通用的方式记录spring项目的控制器返回的所有未捕获的异常。我可以使用以下异常处理程序来完成此操作: 但是,对于方法的每次调用,由于方法中抛出的异常,会创建第二个错误日志:代码来自org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolveHandlerMethodExc

    • @ExceptionHandler在控制器内调用时返回带有MyResponse对象的200响应,但从ControllerAdvice调用时返回带有一般404消息的404。我希望它返回一个带有MyResponse对象的200响应。 下面是Controller和ControllerAdvice中的异常处理代码。在测试ControllerAdvision时,我在控制器中注释了它。调试显示在Control

    • 1.我正在通过骆驼路由调用一个示例SOAP-WS,我只想在服务调用成功执行和服务调用不成功执行后获得http状态代码(响应代码,如200 OK 2.我正在通过骆驼路由调用一个示例REST-WS,我只需要在从服务调用成功执行和从服务调用不成功执行之后的http状态代码(响应代码,比如200OK),在这两种情况下,我们的业务逻辑都会被触发。我所尝试的 问题陈述: 1.在web服务调用成功或失败的情况下