NAME
FP::Optional - dealing with optional values
SYNOPSIS
use FP::Optional qw(perhaps_to_maybe perhaps_to_x perhaps_to_or
perhaps_to_exists
optionally poptionally);
sub perhaps_div {
my ($a, $b) = @_;
$b == 0 ? () : $a/$b
}
if (my ($r) = perhaps_div 10, 2) {
is $r, 5;
}
*maybe_div = perhaps_to_maybe \&perhaps_div;
is maybe_div(10, 2), 5;
is maybe_div(10, 0), undef;
use FP::Div qw(square);
# short-cutting evaluation for undef:
*optionally_square = optionally(\&square);
is optionally_square(2), 4;
is optionally_square(undef), undef;
DESCRIPTION
Places holding or code passing optional values do either hold or pass a 'real' value, or the absence of a value ('nothing').
There are two straight-forward ways to return 'nothing' from a function: undef and the empty list. The empty list has the advantage that it is unambiguous, but the disadvantage that the result needs to be received in list context, which can be more verbose, also, there is the danger of accidentally interpolate the result into a list of other values, which will go wrong if the values in the list have positional meaning.
Example using undef:
# assume rename_user expects pairs of usernames:
rename_users (map { maybe_uid_to_username $_ } @uidpairs)
Example using the empty list:
package Users;
my %uid_to_username;
sub perhaps_uid_to_username {
my ($uid) = @_;
exists $uid_to_username{$uid} ? $uid_to_username{$uid} : ()
}
package main;
use Users;
if (my ($user) = perhaps_uid_to_username (123)) {
...
} else {
...
}
my @existing_usernames = map { perhaps_uid_to_username $_ } @uids;
# This would be *wrong*:
# rename_users (map { perhaps_uid_to_username $_ } @uidpairs)
# Instead this wordy version would need to be used:
rename_users (map {
if (my ($name) = perhaps_uid_to_username $_) {
$name
} else {
undef
} @uidpairs);
An alternative to optional values are exceptions:
package Users;
my %uid_to_username;
sub x_uid_to_username {
my ($uid) = @_;
exists $uid_to_username{$uid} ? $uid_to_username{$uid}
: die "no such user"
}
package main;
use Users;
my $user = x_uid_to_username (123);
...
my @existing_usernames = map {
my $name;
eval { $name = x_uid_to_username $_; 1 } ? $name : ()
} @uids;
rename_users (map { x_uid_to_username $_ } @uidpairs);
The functional perl project *always* prefixes function names with `maybe_` or `perhaps_` if they optionally don't return a value, and depending on whether they do so by returning undef or the empty list. The reason is to make the user of the library directly visibly aware of it, to prevent bugs.
It also prefixes variable names with maybe_ if they are optionally undef. (If they are introduced in an `if` conditional form, then no prefixing is done as they will always be set in the scope of the variable (well, this is not strictly true as in the else branch they are visible too, but that's more like an accident of the Perl language, right?))
The functions in this module help convert between functions following these conventions.
It also offers functions to build chains that propagate failures:
- optionally (\&f [, $pos])
-
Returns a function that when receiving undef as its first argument, or $_[$pos] if $pos given, directly returns undef without calling f; otherwise calls f with the original arguments (with tail-call optimization).
- poptionally (\&f)
-
Returns a function that when not receiving any argument, directly returns (). Otherwise calls it with the original arguments (tail-call optimized).
IDEAS
Implement a binary operator (perhaps named `%%` or `otherwise`), that evaluates the left-hand side in list context, and returns the right-hand side if the result is the empty list, or the last element of the list otherwise. (Zefram says one could implement such an operator, using cv_set_call_checker() and generating a custom op based on OP_DOR.) Example code: `pass($foo->perhaps_name %% $myname)`.
SEE ALSO
`maybe` in FP::Predicates
Perl 6 error values (which are false in conditional context but carry an error message)
NOTE
This is alpha software! Read the status section in the package README or on the website.