Definitely
Definitely (Maybe)
An implementation of the Maybe Monad in Raku
DESCRIPTION
The Maybe Monad is a technique for avoiding unexpected Nil exceptions or having to explicitly test for Nil in a method's response. It removes a lot of ambiguity, which increases maintainability, and reduces surprises.
It's called "Definitely" because when you use this module's types, you'll "Definitely" know what you're working with:
Definitely::Maybe
Definitely::Some
Definitley::None
For example:
sub never-int() returns Int { Nil }
#vs
sub maybe-int() returns Maybe[Int] {...}
The never-int
function claims it'll return an Int
, but it never does.
The maybe-int
function makes it explicit that maybe you'll get an Int,
but maybe you won't.
Some
& None
both provide an .is-something
method, if you want
to explicitly test if you have something. You can also convert them to a Bool
for quick testing (Some is True, None is False). You can explicitly extract the
value from a Maybe/Some object by calling its .value
method.
None
provides a FALLBACK
method that returns the same None object. This means that you can call method
chains on it as if it were the thing you hoped for without blowing up.
Obviously, you'd only do this if your system would be ok with
nothing happening as a result of these calls. For example, logging is nice,
but you probably want your system to carry on even if the logging
mechanism is unavailable.
multi sub logger($x) returns Maybe[Logger] {
nothing(Logger)
}
logger().record_error("does nothing, and doesn't blow up")
Many racoons argue that because Raku has typed Nil
s, the Maybe Monad
is already built in. See this Stack Overflow answer
for more details. Even if they're right, people like me would argue
that there's a huge maintainability value to having code that makes
it explicit that Maybe the value you get back from a method
won't be what you were hoping for.
USAGE
The core idea is simple. When creating a function specify it's return type as
Maybe
or Maybe[Type]
. Within the function you'll use the something(Any)
and nothing()
or nothing(Type)
helper functions to provide a Maybe
/
Maybe[Type]
compatible object to your caller. The caller then has multiple
choices for how to handle the result.
use Definitely;
multi sub foo($x) returns Maybe[Int] {
$x ~~ Int ?? something($x) !! nothing(Int);
}
multi sub foo($x) returns Maybe {
2.rand.Int == 1 ?? something($x) !! nothing();
}
# explicitly handle questionable results
given foo(3) {
when $_ ~~ Some {say "'Tis a thing Papa!. Look: $_"}
default {say "'Tis nothing.'"}
}
# or, call the .is-something method
my Maybe[Int] $questionable_result = foo(3)
if $questionable_result.is-something {
# extract the value directly
return $questionable_result.value + 4;
}
# or, test truthyness (Some is True None is False)
my Maybe[Int] $questionable_result = foo(4);
?$questionable_result ?? $questionable_result.value !! die "oh no!"
# or, just assume it's good if you don't care if calls have no result
my Maybe[Logger] $maybe_log = logger();
$maybe_log.report_error("called if logger is Some, ignored if None")
AUTHORS
The seed of this comes from This post by p6Steve. masukomi) built it out into a full Maybe Monad implementation as a Raku module.
LICENSE
MIT. See LICENSE file.