NAME
Sq::Core::Result - Result functions
This module is automatically loaded and accessible under Result
by loading Sq
.
DESCRIPTION
A Result value is an explicit way to represent the idea of either being succesfull or being an Error and additionally containing a value in both cases.
Error handling is a common problem in computing. We usually have functions that do some computation but maybe can fail. This especially happens with functions doing side-effects.
For example we could create a function that loads an entire file and returns it as a string. With a Result type we can return Ok($string)
in the successfull state and something like Err($error_value)
if it fails.
Like an optional value we must extract the value or check if we have an Ok or Err value. This design avoids common problems with other kinds of error handling.
- C-Style out parameter
-
In a C-style environment usually a function returns an int used as an error-code, but this design means we must usually pass a reference to the function that is used as an out parameter. I think this design is flawed it basically reduces all return values to be error codes only.
It also makes it somehow flawed that an IN argument for calling the function is used as an OUT parameter.
It also means that as a caller we usually must create some data before we can call a function. Like creating a struct that we then pass a pointer to a function. I rather like when the function just creates what is needed and returns it or some error. At least in a language with automatic memory-management this makes sense and is in general better to use.
- undef/null
-
In Perl we could return
undef
as an error case like a lot of built-in functions already do. But we are not forced to check if something was succesfull. Like we can open a file, not check if it failed, and just use the file-handle. Typically it creates a lot of error/warnings, crashes the program or makes the program does something it should not do.But returning
undef
has the problem that we cannot pass information with it, like an error-code indicating what kind of error happened or maybe some other data. But if that is all you need than useOption
instead.Perl avoids this problem by having some global accessible variables. For example we can access
$!
that contains the error of the last called function that set this variable. But sometimes some module also use other global variables, for exampleDBI
uses$DBI::err
and so on. It works, but isn't really fine. Having the error as an explicit local variable makes things easier and less error-prone.The same as in C-Style coding it also means we must check for errors after every function call, this can make code harder to read, and when we forget it, or maybe are not interested in it because we are lazy, we pay the price at a later time, wishing we would never have done that from the beginning.
- Returning tuples
-
We could return two values. For example a boolean value indicating if something is succesfull or not, and the second value as either the Ok or the Error value. This can easily be done in Perl as we can return many arguments, not just one value, but I have never seen a Perl module does this. Go as a programming language does this kind of thing but overall I think it's just a crap design.
Returning two values is just a poor-mans version of
Option
orResult
. Instead of having one value that contains all information we have two separated values instead.Result
captures both information in one value. Having them as one value also means we can work with them. PassResult
values around and be able to use functions operating onResult
values. - Exceptions
-
From my experience this is the worst solution of all. Even if it has become the most common solution in most "modern" languages. Silently a function can throw an exception but we never know why and when. Also in Perl it is not always documented if a function throws an error, like in most languages.
The worst thing that can happen is when people suddenly expect you to cath exceptions and use them as some kind of flow-control. Exceptions are also usually extremely slow when they happen. They need to capture a stack and do some kind of weird magic, and I don't like magic.
Okay I would like to shoot kamehameha like Goku, be able to fly or be able todo other stuff, but I don't like magic in my source code.
Also in Perl try/catch syntax was poorly supported for a long time and only became available since Perl 5.40. When you forget to catch exceptions, because you either don't know if a function throws one or you are just lazy, then your code crashes, usually when you don't excpect it.
A function returning a Result
means you always know when a function can throw an error. You can pass values around in the error code. Those values are all lexically scoped and don't use global variables. You also can execute a bunch of functions gather all results into an array and then process the array like you want.
For example consider you have a function downloading URLs that return the content of the URL as a string. You either get an Ok($content)
back or a Err($error_code)
. You can easily execute all of them in parallel, asnychronous and so on, gather all results and only then do processing of those who where Ok, just log the Err or do some other kind of stuff.
Code becomes better when even your Errors is just data that you can manipulate. Sq
but pretty much any programming language provides rich features to work and manipulate data. Because of this it makes sense that also Errors is just data instead of having a separated language feature with poor language features.
CONSTRUCTORS
These function creates result values. Ok
and Err
are both automatically loaded with loading Sq
.
Ok($x) : $result_x
Every value turns into an Ok
value.
my $result = Ok(10);
my $result = Ok([]);
my $result = Ok({ a => 1, b => 2});
my $result = Ok(undef);
Err($x) : $result_x
Every value turns into an Err
value.
my $result = Err(10);
my $result = Err([]);
my $result = Err({ a => 1, b => 2});
my $result = Err(undef);
METHODS
Methods work on a result value. All methods can also be called in a functional-style. This means you always can do both calling styles.
$result->map(sub($x) { ... });
Result::map($result, sub($x) { ... });
is_ok($any) : $bool
Returns a truish value when $any
is a Result
and is Ok
.
my $bool = $result->is_ok;
my $bool = Result::is_ok(Ok(10)); # 1
my $bool = Result::is_ok(Err(10)); # 0
my $bool = Result::is_ok(""); # 0
my $bool = Result::is_ok([]); # 0
is_err($any) : $bool
Returns a truish value when $any
is a Result
and is Err
.
my $bool = $result->is_err;
my $bool = Result::is_err(Ok(10)); # 0
my $bool = Result::is_err(Err(10)); # 1
my $bool = Result::is_err(""); # 0
my $bool = Result::is_err([]); # 0
match($result, Ok => sub($x) { $y }, Err => sub($x) { $y }) : $y
Pattern matches against the Result
value expecting that you pass in a function for the Ok and the Err case. Executes either the Ok or the Err function and passes the value to it. match
returns whatever those functions returns.
Instead of checking with is_ok
or is_err
you should use this function instead.
sub fetch($path) {
state $content = Hash->new(
'/' => Ok 'root',
'/etc/passwd' => Err 'invalid access',
'/var/log/text' => Ok 'some text',
);
return $content->get($path)->or(Err '404');
}
my @fetches = (
fetch('/'), fetch('/etc/passwd'),
fetch('/var/log/text'), fetch('/home/Foo/text'),
);
for my $fetch ( @fetches ) {
$fetch->match(
Ok => sub($str) { printf "Ok\n" },
Err => sub($str) { printf "Error\n" },
);
}
map($result, $f_y) : $result_y
When $result
is an Ok
value it passes the inner value to $f_y
and then wraps the return value again into an Result value.
my $add1 = sub($x) { $x + 1 };
my $result = Ok(10) ->map($add1); # Ok(11)
my $result = Err(10)->map($add1); # Err(10)
mapErr($result, $f_y) : $result_y
When $result
is an Err
value it passes the inner value to $f_y
and then wraps the return value again into an Result value.
my $add1 = sub($x) { $x + 1 };
my $result = Ok(10) ->mapErr($add1); # Ok(10)
my $result = Err(10)->mapErr($add1); # Err(11)
MODULE FUNCTIONS
These functions are concipated to be called from the module level because they don't make sense to be called as a method for various reasons.
is_result($any) : $bool
Returns a truish value when $any
is a Result
type.
if ( Result->is_result($any) ) {
...
}
my $bool = Result->is_result(Ok(10)); # 1
my $bool = Result->is_result(Err(10)); # 1
my $bool = Result->is_result(""); # 0
my $bool = Result->is_result([]); # 0