The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

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.