我什么时候想恢复Perl 6异常?

时间:2017-04-09 09:00:23

标签: exception resume perl6

也许我真正的问题是“这是否适用于 Learning Perl 6 ”?基于Should this Perl 6 CATCH block be able to change variables in the lexical scope?,似乎最简单的例子可能超出一个简单的例子。

在那个问题中,我正在处理一些看似愚蠢或更好的问题,因为我正在使用该功能而不是解决问题。

有记录的使用警告作为特殊类型的异常(“控制异常”),你得到消息,如果你喜欢可以捕获它,但也可以忽略它,它将自行恢复(虽然我很喜欢愚蠢的Where should I catch a Perl 6 warning control exception?)。

除此之外,我正在考虑调用者可以处理被调用者范围之外的失败的事情。例如,重新连接到数据库,修复丢失的目录以及被调用者不负责的其他外部资源问题。

在阅读其他语言中的这类内容时,建议主要是不使用它们,因为在“真实世界”编程中,人们往往不会真正处理问题。

C# exception handler resume next的回答似乎表明这是一种糟糕的做法和丑陋的代码。我当然还没想办法在被叫方中隐藏一堆代码。

我修改了这个例子,虽然我不相信这是一个很好的方法,或者向初学者推荐一些东西。程序启动时会查找PID文件。如果找到一个,则会抛出异常。处理该异常会检查另一个实例是否仍在运行,这可能会引发不同类型的异常。并且,有一个处理文件IO问题。诀窍是,如果其他程序没有运行({但保留其PID文件),X::MyProgram::FoundSemaphore可以恢复。

class X::MyProgram::FoundSemaphore is Exception {
    has $.filename;
    has $.this-pid = $*PID;
    has $.that-pid = $!filename.lines(1);

    method gist {
        "Found an existing semaphore file (pid {.that-pid})"
        }
    }

class X::MyProgram::StillRunning is Exception {
    has $.that-pid;
    has $.os-error;

    method gist {
        "This program is already running (pid {self.that-pid})"
        }
    }

class X::MyProgram::IO::OpenFile is Exception {
    has $.filename;

    method gist {
        "This program is already running (pid {self.that-pid})"
        }
    }

sub create-semaphore {
    state $filename = "$*PROGRAM.pid";
    END { unlink $filename }

    die X::MyProgram::FoundSemaphore.new(
        :filename($filename)
        ) if $filename.IO.e;

    my $fh = try open $filename, :w;

    # open throws Ad::Hoc, which could be more helpful
    die X::MyProgram::IO::OpenFile.new(
        :filename($filename),
        :os-error($!),  # role X::IO-ish
        ) unless $fh;

    $fh.print: $*PID;
    }

BEGIN {
    try {
        CATCH {
            when X::MyProgram::FoundSemaphore {
                my $proc = run qqw/kill -0 {.that-pid}/;
                X::MyProgram::StillRunning.new(
                    :that-pid(.that-pid) ).throw
                    if $proc.so; # exit code is 0, so, True
                unlink .filename;
                .resume;
                }
            default { say "Caught {.^name}"; exit }
            }

        create-semaphore();
        }
    }

sub MAIN ( Int $delay = 10 ) {
    put "$*PID sleeping for $delay seconds";
    sleep $delay;
    }

1 个答案:

答案 0 :(得分:6)

可恢复的异常肯定不是我在Perl 6中发现的东西。我认为我还没有在“用户空间”代码中使用它们。可恢复的异常被证明是实现emitsupply块中使用的react函数的正确方法。 take中使用的gather函数也使用可恢复的异常来实现,并且 - 正如您已经发现的那样 - warn使用它们。

我怀疑最后一个 - warn - 是典型的Perl 6用户唯一感兴趣的案例。捕获警告并将其发送到其他地方 - 可能是日志文件或日志服务器 - 是公平的合理的事情需要做。就学习Perl 6而言,这可能是可恢复异常的一个明显有用的例子。

我认为在Perl 6中利用可恢复异常的所有用例结果都被归类为“控制异常”。控制异常基本上是实现级别的正常异常:它们涉及非本地控制转移。它们在语言层面上是不同的,因为如果你的emittakewarnnextlastCATCH,那么使用Perl 6会相当尴尬所以由于default阻止了try吞下控件例外而停止了工作!

然而,它也有点“像我说的那样,而不是像我一样”:虽然Perl 6很乐意使用异常系统来实现非本地流量控制,但它在一个尘土飞扬的角落里却有点过时了。语言,而不是举起它作为一个事情的例子。并且有充分的理由:通常,使用异常来执行流量控制的代码很难遵循,并且对于可恢复的异常而言,这会增加一倍。另一个很大的风险是,使用裸CATCHdefaulttake的代码可能会吞噬这种异常 - 这使得它在更大的代码库中变得相当脆弱

我认为可恢复异常的最佳用途将转变为用户根本不会考虑异常的事情的实施策略 - 就像emitwarn一样try(并且,大多数情况下,default)。并且,与现有的可恢复异常示例一样,正在恢复的事物将是一种异常类型,专门设计用于在可恢复的情况下抛出,并且仅用于需要做的事情。然而,在Perl 6提供了一种定义自定义控件异常的方法之前,我不太愿意这样做; BEGIN DELETE FROM CUSTOMER WHERE CUS_CODE = cust_id; DELETE FROM INVOICE WHERE INVOICE.CUS_CODE = cust_id; DELETE FROM LINE WHERE EXISTS (SELECT * FROM INVOICE WHERE INVOICE.INV_NUMBER = LINE.INV_NUMBER AND invoice.CUS_CODE = cust_id); END / My.Data吞咽问题使其过于脆弱。