NAME
Sq::Core::Option - Option functions
This module is automatically loaded and accessible under Option
by loading Sq
.
DESCRIPTION
An Option is an alternative to represent undef/null values. By default every value in Perl can be undef
. The problem with working with undef
is that you need to check them but are never forced todo so. If you forget to check for undef
than typical warnings in Perl will appear or maybe even the program crashes when you try to call a method on an undef
value.
An option is a way to make it explicitly visible that a function sometimes returns no value at all. A user of the option value must explicitly work with an option and unpack the value or use some of the methods provided by the option module instead of directly extracting the value. When a value is extracted the user must write code for both cases that reduces the amount of errorness code that is possible to write.
But the whole idea on top of that is that there are special functions written that helps working and doing typical task with optional values.
Like the other modules you always can call every method in a functional-style.
By loading Sq
it automatically imports the function Some
and None
to create optional values.
my $x = Some(10);
my $y = Some(0);
my $z = None;
CONSTRUCTORS
These function creates optional values.
Some(@xs) : $opt_xs
The Some
function is automatically imported by loading Sq
. Some
can take multiple arguments and is valid as long every value is defined
and not a None
. All optionals are flattened on creation and thus it is like working with Arrays in list context. Every value in Some
must be valid, otherwise the whole turns to None
.
my $opt = Some(); # None
my $opt = Some(undef); # None
my $opt = Some(10); # Some(10)
my $opt = Some([]); # Some([])
my $opt = Some({ a => 1, b => 2}); # Some({ a => 1, b => 2})
my $opt = Some(Some(10)); # Some(10)
my $opt = Some(None); # None
my $opt = Some(Some(Some([]))); # Some([])
my $opt = Some(Some(1,2), Some(3,4)); # Some(1,2,3,4)
my $opt = Some(Some(1,2), undef); # None
my $opt = Some(Some(1,2), Some(Some(3))); # Some(1,2,3)
my $opt = Some(1,2,None); # None
None() : $opt
This indicates the absence of a value
my $opt = None();
my $opt = None;
METHODS
Methods work on an optional value. All Methods can also be called in a functional-style. This means you always can do both calling styles.
$opt->map(sub($x) { ... });
Option::map($opt, sub($x) { ... });
is_some($any) : $bool
returns a truish value when the optional contains some value. Most of the time you probably want to use an extraction method instead.
if ( $opt->is_some ) {
...
}
Calling this method in a functional-style has the benefit that it also does a proper type-checking of an optional, you can pass any value to it and get back if the value is an optional and if it is Some value.
my $bool = Option::is_some($any)
my $bool = Option::is_some(Some({})); # 1
my $bool = Option::is_some(Some(1)); # 1
my $bool = Option::is_some(None); # 0
my $bool = Option::is_some(Some(undef)); # 0
my $bool = Option::is_some(""); # 0
my $bool = Option::is_some(0); # 0
my $bool = Option::is_some([]); # 0
my $bool = Option::is_some({}); # 0
is_none($any) : $bool
returns a truish value when the optional is None. Most of the time you probably want to use an extraction method instead.
if ( $opt->is_none ) {
...
}
Calling this method in a functional-style has the benefit that it also does a proper type-checking of an optional, you can pass any value to it and get back if the value is an optional and if it is a None value.
my $bool = Option::is_none(Some({})); # 0
my $bool = Option::is_none(Some(1)); # 0
my $bool = Option::is_none(None); # 1
my $bool = Option::is_none(Some(undef)); # 1
my $bool = Option::is_none(""); # 0
my $bool = Option::is_none(0); # 0
my $bool = Option::is_none([]); # 0
my $bool = Option::is_none({}); # 0
Consider that other values than Some or None also return 0
because an empty string for example is not an optional.
match($opt, Some => sub($value){ $y }, None => sub(){ $y }) : $y
Pattern matches against the optional value. It either runs the function provided with the Some
argument or the None
argument. It's usually used to extract the value by providing two functions for each case the optional can be in. If you just want to extract the value or use a default value then use the method or
instead.
my $number =
$opt->match(
Some => sub($x) { $x + 1 },
None => sub { 0 },
);
# will be 11
my $number =
Some(10)->match(
Some => sub($x) { $x + 1 },
None => sub { 0 },
);
# will be 0
my $number =
None->match(
Some => sub($x) { $x + 1 },
None => sub { 0 },
);
or($opt, @defaults) : $x
Returns the value when it is Some
value or the @defaults
value passed to it when it is None
.
my $a = $opt ->or(10);
my $b = Some(10)->or(0); # 10
my $c = None ->or(0); # 0
my $d = Option::or($opt, 10);
my $e = Some(1,2)->or(3); # 1
my @xs = None ->or(1,2,3) # (1,2,3)
or_else($opt, $default_opt) : $opt
Similar to or
but returns $opt
as-is as long it is Some
value. Otherwise returns $default_opt
that is expected to be an optional.
my $opt = $opt ->or_else(Some("foo"));
my $opt = Some(1)->or_else(Some 2); # Some 1
my $opt = None ->or_else(Some 3); # Some 3
or_with($opt, $f_x) : $x
Same as or
but instead of passing a value that is used in the None case, a function is expected and executed. This is useful in two cases.
When the or case should create different mutable values. Also when those values only should be lazily created only when the value is
None
. Consider that when usingor
you always already create a value before theor
function is run. For just "normal" values like strings or numbers this might be okay. But when the$default
value might execute some kind of side-effect or has some expensive cost attached to it, you highly want to useor_with
.When you want to execute a piece of code containing some kind of side-effects that only should be executed when the value was
None
.
# always returns a new array-ref
my $x = $opt->or_with(sub { [] });
# returns current time in the case of None
my $x = $opt->or_with(sub { scalar localtime });
or_else_with($opt, $f_opt) : $opt
When $opt
is Some
value returns $opt
as-is. Otherwise executes and returns the result of $f_opt->()
that should generate an optional value.
my $opt = Some([1])->or_else_with(sub { Some [] }) # Some [1]
my $opt = None ->or_else_with(sub { Some [] }) # Some []
map($opt, $f_x) : $opt_x
When you have a function that don't know about optionals then you use map
to let the function run of its value. When the optional is None
then the function will not be executed, otherwise the function is executed with the value that the optional holds. Always returns an optional.
map
is good for functions with the signature 'a -> 'b
# Here $incr is a function expecting a number and returning a number
# no optional is involved. It's basically of type: number -> number
my $incr = sub($x) { $x + 1 };
my $opt = $opt ->map($incr);
my $opt = Some(0) ->map($incr); # Some(1)
my $opt = Some(10)->map($incr); # Some(11)
my $opt = None ->map($incr); # None
map2($optA, $optB, $f_x) : $opt_x
Whenever you have a function expecting two normal values, but you have two optional values, you use this function. Returns an optional value again.
my $add = sub($x,$y) { $x + $y }
# adds together both optional values or returns None if one of them is None
my $opt = Option::map2($optA, $optB, $add);
my $opt = $optA->map2($optB, $add);
my $opt = Some(10)->map2(Some(3), $add); # Some(13)
my $opt = None ->map2(Some(3), $add); # None
map3($optA, $optB, $optC, $f_x) : $opt_x
Same as map2
but for three optional values.
# adds all three optional together as long all of them are Some value
# otherwise $x will be None.
my $opt =
Option::map3($optA, $optB, $optC, sub($a, $b, $c) {
return $a + $b + $c;
});
map4($optA, $optB, $optC, $optD, $f_x) : $opt_x
Same as map3
but for four optional values.
# adds all four optional together as long all of them are Some value
# otherwise $x will be None.
my $opt =
Option::map4($optA, $optB, $optC, $optD, sub($a, $b, $c, $d) {
return $a + $b + $c + $d;
});
map_v(@opts, $f_x) : $opt_x
Expects a variable amount of optional values and a function as the last argument. Executes the function with all unpacked values when all optionals are Some
values. Otherwise returns None
.
Theoretically map_v
can replace all other mapX
functions, but consider that this function is usually slower because of its implementation compared to directly calling map
, map2
, map3
or map4
.
# Some(28)
my $opt = Option::map_v(Some 1, Some 2, Some 3, Some 4, Some 5, Some 6, Some 7, sub {
my $sum = 0;
for my $x ( @_ ) {
$sum += $x;
}
return $sum;
});
bind($opt, $f_opt) : $opt
bind
is also sometimes called chain
or then
(depending on language or module). It allows chaining together functions that expects a non optional value, but returns an optional value again.
# this function parses a string and returns an optional. Either Some integer
# when the functions succeeds or None if not.
# Type: string -> Option<int>
my $parse_int = sub($str) {
if ( $str =~ m/\A \s* (\d+) \s* \z/xms ) {
return Some($1);
}
return None;
};
# The result will be Option<int>. Some(int) or None.
my $opt = $opt->bind($parse_int);
my $opt = Some("10")->bind($parse_int); # Some(10)
my $opt = Some("ab")->bind($parse_int); # None
my $opt = None ->bind($parse_int); # None
# compare it when you have used map instead of bind
my $opt = Some("10")->map($parse_int); # Some(Some(10))
my $opt = Some("ab")->map($parse_int); # Some(None)
my $opt = None ->map($parse_int); # None
bind2($optA, $optB, $f_opt) : $opt
When you have two optional values to unpack and want them to pass it to a function that produces a new optional value.
# adds $optA and $optB together when both values are C<Some> value and
# greater than 0. returns Some sum or None.
my $opt_sum =
Option::bind2($optA, $optB, sub($a, $b) {
if ( $a > 0 && $b > 0 ) {
return Some($a + $b);
}
return None;
});
my $opt_sum =
$optA->bind2($optB, sub($a, $b) {
if ( $a > 0 && $b > 0 ) {
return Some($a + $b);
}
return None;
});
bind3($optA, $optB, $optC, $f_opt) : $opt
Unpacks three optional values and when all three are Some
values passes them to $f_opt
that returns an optional value.
my $opt_sum =
Option::bind3($optA, $optB, $optC, sub($a,$b,$c) {
if ( $a > 0 && $b > 0 && $c > 0 ) {
return Some($a + $b + $c);
}
return None;
});
bind4($optA, $optB, $optC, $optD, $f_opt) : $opt
Unpacks four optional values and when all three are Some
values passes them to $f_opt
that returns an optional value.
my $opt_sum =
Option::bind4($optA, $optB, $optC, $optD, sub($a,$b,$c,$d) {
if ( $a > 0 && $b > 0 && $c > 0 && $d > 0 ) {
return Some($a + $b + $c + $d);
}
return None;
});
bind_v(@opts, $f_opt) : $opt
Unpacks all optionals and when all values are Some
value then passes all values to function $f_opt
that returns an Optional value again.
my $sum_under_100 = sub {
my $sum = 0;
for my $x ( @_ ) {
$sum += $x;
}
return $sum <= 100
? Some($sum)
: None;
};
my $opt = Option::bind_v(Some 1, Some 2, Some 3, Some 4, Some 5, Some 6, $sum_under_100); # Some(21)
my $opt = Option::bind_v(Some 30, Some 50, Some 40, $sum_under_100); # None
validate($opt, $predicate) : $opt
Runs a predicate function (a function returning a boolish value) on the value. When $opt
is Some value and the $predicate
function returns a truish value then $opt
will not change. Otherwise returns None
instead.
my $is_normalized = sub($x) {
return $x >= 0 && $x <= 1.0 ? 1 : 0;
};
my $opt = Some(0) ->validate($is_normalized) # Some(0)
my $opt = Some(0.2)->validate($is_normalized) # Some(0.2)
my $opt = Some(2) ->validate($is_normalized) # None
my $opt = None ->validate($is_normalized) # None
check($opt, $predicate) : $bool
Similar to validate
. But instead of returning an optional that contains the information if value was valid or not it directly returns the boolean value. A None
value always return a falsish value.
my $bool = Some(0) ->check(\&is_num) # 1
my $bool = Some(1) ->check(\&is_num) # 1
my $bool = Some("0E0") ->check(\&is_num) # 1
my $bool = Some(" 100") ->check(\&is_num) # 1
my $bool = Some("0.00") ->check(\&is_num) # 1
my $bool = Some("+0.00")->check(\&is_num) # 1
my $bool = Some("f100") ->check(\&is_num) # 0
my $bool = Some(undef) ->check(\&is_num) # 0
my $bool = Some("") ->check(\&is_num) # 0
my $bool = None ->check(\&is_num) # 0
fold($opt, $state, $f_state) : $state
When you have a function expecting two normal values, but one value is an optional, then this function is probably what you wanna use. It either returns $state
when the optional is None. Otherwise it executes $f_state
by passing it the value of the $opt
and the $state
.
This function either returns $state
or whatever $f_state
returns. The type of $state
and whatever $f_state
returns should be the same, oterwise this function hardly makes any sense.
my $add = sub($x, $state) { $x + $state };
my $x = $opt ->fold(100, $add);
my $x = Some(10)->fold(100, $add); # $add->(10,100) -> 110
my $x = None ->fold(100, $add); # $state -> 100
my $x = Option::fold(Some(10), 3, sub($x,$y) { $x - $y }); # 10 - 3 = 7
fold_back($opt, $state, $f_state) : $state
Same as fold
but $state
is passed as the first argument to $f_state
instead of last. This behaviour can be more useful when Some
contains multiple values.
my $sum = Some(1,2,3)->fold_back(10, sub($state,@rest) {
# 10 + (1+2+3)
$state + Array::sum(\@rest)
});
iter($opt, $f) : void
Runs function $f
when there is Some
value otherwise does nothing when the value is None
. Usually this is done for side-effects as the funtion returns nothing.
Some("Hello")->iter(sub($str) { say $str }); # prints: "hello"
None ->iter(sub($str) { say $str }); # prints nothing
to_array($opt) : $array_x
Converts the optional into an array. In the case of Some value it turns into an array of length 1 containing the value, otherwise it is an empty array. Arrays are blessed into the Sq Array.
my $array = $opt->to_array();
my $array = Some(1)->to_array; # [1]
my $array = None ->to_array; # []
single($opt) : $opt_array
An optional can contain multiple values, but all of those values are usually also passed as multiple values to the lambda function or are returned. It is sometimes better to work with an array instead.
This function transforms the inner values into an Sq Array. This transformation also happens for a single value. When the option only has a single value that already is an array, than nothing happens. So it safe to call multiple times single
.
single
does not return a new Array or a copy when it is what it should be. It just returns $opt
as-is. Also adds Array
blessing when a normal array was passed.
my $array = Some(1,2,3)->single->get; # [1,2,3]
my $array = Some(1,2,3)->single->or(sq []); # [1,2,3]
my $sum = Some(1,2,3)->single->map(call 'sum'); # Some(6)
my $sum = Some(1,2,3)->map(sub(@xs) { Array::sum(\@xs) }); # 6
get($opt) : $x | EXCEPTION
Returns the value inside of an optional, but only when the value is Some value, otherwise throws an exception. This function should be avoided as much as possible if you want a working program. At least you should use is_some
before extracting, or even better either use match
, or
or or_with
to extract the value.
When Some
contains multiple values than only the first argument is returned in scalar context.
my $x = Some(10)->get; # 10
my $x = None ->get; # throws exception
my $x = Some(1,2,3)->get; # 1
my @xs = Some(1,2,3)->get; # (1,2,3)
dump($opt, $inline=60, $depth=0) : $str
Recursivly traverses data-structures and creates a human readable dump from it.
$inline
controls the amount of characters a data-structure is completely inlined into a single string without newlines. The higher the number the more compact your dump will be.
It currently has a bug as it also collaps whitespace in a string and it shouldn't do that. This could be avoided by setting $inline
to 0
. But consider that dumping in its current form is considered for debugging purposes, not for serializing data.
Currently it is not perfect. It only works with Perl Array/Hash and Sq Array/Hash and the Option type. Sq Array/Hash are just dumped as normal Perl Array/Hash. No other object is being dumped. It also does not dump any other object and has no configuration. Also doesn't detect cyclic data-structures. But for most practical data-structures it works good enough at the moment. Get's improved in the future.
printf "%s\n", $opt->dump;
dumpw($opt, $inline=60, $depth=0) : void
Same as dump
but instead of returning the dump as a string, automatically prints it using warn
.
$opt->dumpw;
warn $opt->dump, "\n";
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. Usually because the first argument is not just an option. Here we have a list of options. But they also wouldn't make sense to be added to an array, or maybe it will?
You could call them in a functional-style, but then you also need to pass a dummy value as first argument.
Option->all_valid($array_of_opt);
Option::all_valid(undef, $array_of_opt);
Option->is_opt($any) : $bool
returns a truish value when $any
is an optional value.
my $bool = Option->is_opt(Some({})); # 1
my $bool = Option->is_opt(Some(1)); # 1
my $bool = Option->is_opt(None); # 1
my $bool = Option->is_opt(Some(undef)); # 1
my $bool = Option->is_opt(""); # 0
my $bool = Option->is_opt(0); # 0
my $bool = Option->is_opt([]); # 0
my $bool = Option->is_opt({}): # 0
WARNING: In probably 90% of all cases this is not the function you wanna use. Some new people that are introduced to the idea of optional values usually might use it like this.
if ( Option->is_opt($any) && $any->is_some ) {
my $value = $any->get;
}
Don't do that.
- First
-
Instead of
Option->is_opt
you can useOption::is_some
orOption::is_none
in a functional-style to also check$any
value. Those functions are written in a way that they don't expect an optional value. You can directly doif ( Option::is_some($any) ) { my $value = $any->get }
This does the same and is shorter and clearer.
- Second
-
Only call
Option->is_opt
when you expect that a function can return many different values of different types and you need to check if it is an optional value. Obviously when you know that something should return an optional then trust it and just use it as an optional. That's how dynamic-typing works.That leads to the idea that very often you maybe want to check if you get Some value and do something with it. A first better aproach would be to use
match
instead.my $double = $option->match( Some => sub($x) { $x * 2 }, None => sub { 0 }, );
In this case
$double
will either be the double of$x
or0
. - Third
-
The above assumes that you know a default value to be used when the value is None. But what do you do when you don't know a default value at this place? You could return an optional again.
my $opt_double = $option->match( Some => sub($x) { Some($x * 2) }, None => sub { None }, );
but, this function is exactly what
map
does! So instead write.my $opt_double = $option->map(sub($x) { $x * 2 });
Consider
map
as a way to write code that only get's executed when you have Some value. Otherwise the code will not be executed.When the code you pass to
map
returns an optional you should usebind
instead ofmap
. - Fourth
-
The idea of working with Optional values is that you keep working with an
Option
value as long as you can. Only at the very last step where you just need to access the value then you usually either usematch
,or
,or_with
,or_else
,or_else_with
to extract the value. Alsoget
should be avoided if possible.Use
fold
when you want to extract the value and then transform it to something else. Useiter
when you want to do just some side-effect with the value.
Option->all_valid($array_of_opt)
Sometimes we have an array of optionals. Like [Some(1), Some(2), Some(3)]
. But instead of an array of optionals we basically want to turn it inside-out. We want an optional array Some([1,2,3])
instead. The idea is that we only get Some
array when all values are Some
. As soon one value in the whole array is None
we immediately just get None
. So we can validate all values in a array at once.
Usually we get an array of optionals when we call an option generating function on every element of an array. For example by using Array::map
.
# is_num() is a function automatically imported by Sq. It is just a shortcut
# for using Scalar::Util::looks_like_number()
# an option generating function
my $some_num = sub($str) { is_num($str) ? Some($str) : None };
# Some([12, 100, 13])
my $opt_nums =
Option->all_valid(
Array->new("12", "100", "13")->map($some_num)
);
# None
my $opt_nums =
Option->all_valid(
Array->new("12", "100g", "13")->map($some_num)
);
Option->all_valid_by($array, $f)
When you already have an array of optionals you use Option->all_valid
. But when you want to run an optional generating function on every element of an array and then call Option->all_valid
then you better use Option->all_valid_by
that does both steps in one operation.
# an option generating function
my $some_num = sub($str) { is_num($str) ? Some($str) : None };
# Some([12, 100, 13])
my $opt_nums =
Option->all_valid_by(Array->new("12", "100", "13"), $some_num);
# Some([12, 100, 13])
my $opt_nums =
Option->all_valid_by(["12", "100", "13"], $some_num);
# Some([12, 100, 13])
my $opt_nums =
Option->all_valid(
Array->new("12", "100", "13")->map($some_num)
);
Option->filter_valid($array)
Similar to Option->all_valid
but instead of becoming None
as soon one value is None
in an array it just filters None
values out. Additional an Sq array instead of an option is returned.
my $nums = Option->filter_valid([Some(1), Some(2), Some(3)]); # [1,2,3]
my $nums = Option->filter_valid([Some(1), Some(2), None]); # [1,2]
my $nums = Option->filter_valid([None]); # []
my $nums = Option->filter_valid([]); # []
my $opt_nums = Option->all_valid([Some(1), Some(2), Some(3)]); # Some([1,2,3])
my $opt_nums = Option->all_valid([Some(1), Some(2), None]); # None
my $opt_nums = Option->all_valid([None]) # None
my $opt_nums = Option->all_valid([]) # Some([])
Option->filter_valid_by($array, $f)
Like Option->filter_valid
but additionaly runs function $f
on every element of $array
to create an option value first.
# an option generating function
my $some_num = sub($str) { is_num($str) ? Some($str) : None };
my $nums = Option->filter_valid ([ map { $some_num->($_) } 1,2,3 ]); # [1,2,3]
my $nums = Option->filter_valid (Array->range(1,3) ->map($some_num)); # [1,2,3]
my $nums = Option->filter_valid (Array->new(qw/1 foo 2/)->map($some_num)); # [1,2]
my $nums = Option->filter_valid_by([qw/1 foo 2/], $some_num); # [1,2]
Option->extract($any=undef) : ($bool, $x)
This is a helper function used when you want to create functions that expects anonymous functions that can return optional values or undef and still behaves correctly.
As an example look at Array::choose, you can use it in three ways.
my $array = $range->choose(sub($x) { $x % 2 == 0 ? Some($x*2) : None });
my $array = $range->choose(sub($x) { $x % 2 == 0 ? $x*2 : undef });
my $array = $range->choose(sub($x) { $x % 2 == 0 ? $x*2 : () });
The lambda you pass to choose
either can return Some/None or just a normal value or (undef/empty list) and choose
still behaves correctly.
Here is the implementation of Array::choose that uses Option->extract
to implement this kind of behaviour.
sub choose($array, $f_opt) {
my $new = Array->new;
my ($is_some, $v);
for my $x ( @$array ) {
($is_some, $v) = Option->extract($f_opt->($x));
push @$new, $v if $is_some;
}
return $new;
}
It basically works the following. Whatever value you pass to extract
it returns a boolean flag indicating if you have some value or not, and the second value it returns is the actual value either directly or extracted from the optional.
Since the latest changes to Option
, choose
also could have been implemented this way.
sub choose($array, $f_opt) {
my $new = Array->new;
for my $x ( @$array ) {
my $opt = Some($f_opt->($x));
push @$new, $opt->get if $opt->is_some;
}
return $new;
}
because a call to Some
doesn't wrap an option again and again now you just can wrap any function call that should return an optional in a Some
call and then work with it like any optional value.
Option->extract
is a slightly faster version of this because no creation of an optional value is involved.