The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

FP::Failure - failure values

SYNOPSIS

    use FP::Equal ':all'; use FP::Ops qw(the_method regex_substitute); use FP::List;
    use FP::Failure;
    is_equal \@FP::Failure::EXPORT, [qw(failure is_failure)];
    # but there is more in EXPORT_OK...
    use FP::Failure '*trace_failures';

    my $vals = do {
        local $trace_failures = 0;
        list(failure("not good"),
             failure(666),
             failure(undef),
             666,
             0,
             undef)
    };

    is_equal $vals->map(\&is_failure),
             list(1, 1, 1, undef, undef, undef);

    is_equal $vals->map(sub { my ($v) = @_; $v ? "t" : "f" }),
             list("f", "f", "f", "t", "f", "f");

    # failure dies when called in void context (for safety, failures have
    # to be ignored *explicitly*):
    is((eval { failure("hello"); 1 } || ref $@),
       'FP::Failure::Failure');

    # get the wrapped value
    is_equal $vals->filter(\&is_failure)->map(the_method "value"),
             list("not good", 666, undef);

    # get a nice message
    is_equal $vals->first->message,
             "failure: 'not good'\n";

    # record backtraces
    my $v = do {
        local $trace_failures = 1;
        failure(666, [$vals->first])
    };

    is_equal $v->message,
             "failure: 666\n  because:\n  failure: 'not good'\n";

    # request recorded backtrace to be shown
    use Path::Tiny;
    is_equal regex_substitute(sub { # cleaning up bt
                                  s/line \d+/line .../g;
                                  my $btlines = 0;
                                  $_ = join("\n",
                                           grep { not /^    \S/ or ++$btlines < 2 }
                                           split /\n/)
                              },
                              $v->message(1)),
             join("\n", "failure: 666 at ".path("lib/FP/Failure.pm")->canonpath
                        ." line ...",
                        "    (eval) at lib/FP/Repl/WithRepl.pm line ...",
                        "  because:",
                        "  failure: 'not good'");

    # Wrapper that just returns 0 unless configured to create a failure
    # object:

    use FP::Failure qw(*use_failure fails);
    use FP::Show;

    is show(do { local $use_failure = 0; fails("hi") }),
       0;
    is show(do { local $use_failure = 1; fails("hi") }),
       "Failure('hi', undef, undef)";


    # Utility container for holding both a message and values:

    use FP::Failure qw(message messagefmt);

    is failure(message "Hi", "foo", 9)->message,
       "failure: Hi: 'foo', 9\n";
    is failure(message "Hi")->message,
       "failure: Hi\n";

    # messagefmt is currently still passing everything through FP::Show;
    # what should it do, implement another fmt character?
    is failure(messagefmt "Hi %s %d", "foo", 9)->message,
       "failure: Hi 'foo' 9\n";

DESCRIPTION

Values meant to represent errors/failures and to be distinguishable from non-error values. They are overloaded to be false in boolean context (although doing a boolean test is not safe to distinguish from non-failure values, as obviously those include false as well), or checked via the `is_failure` function.

The `value` method delivers the first argument given to `failure`, `maybe_parents` the second, which is an array of the parents, meant for chaining failures (reasons why this failure happened). `message` produces a somewhat nice to read string, multi-line if parents are chained in.

Calling the constructor in void context throws the constructed failure value as an exception.

If the variable `$FP::Failure::trace_failures` is set to true (it can be imported mutably via '*trace_failures'; default: false), then a stack trace is collected with the failures and displayed with `message` (if a true value is passed to message ?). (XX: use `BACKTRACE=1` idea here, too? Implement the same in `Chj::Backtrace`, too, and FP::Repl::Trap if fitting?)

If the variable `$FP::Failure::use_failure` is set to true (it can be imported mutably via '*use_failures'; default: false), then the optionally exported wrapper function `fails` calls `failure` with its arguments, otherwise it returns `0` (fully compatible with standard Perl booleans, and a little bit faster).

TODO

Instead of using `FP::Failure::Failure` as base class, create a failure protocol (FP::Abstract::Failure) instead?

SEE ALSO

FP::Either (which wraps both cases in a shared parent type).

Implements: FP::Abstract::Pure, FP::Struct::Show

NOTE

This is alpha software! Read the status section in the package README or on the website.