Rand Stats

Definitely

zef:librasteve

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:

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 rakuteers argue that because Raku has typed Nils, 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.

Note: you should not specify Maybe when calling nothing(Type). For example, call nothing(Int) not nothing(Maybe[Int]). The function will take care of making sure it conforms to the Maybe Type for you.

use Definitely;

multi sub foo($x) returns Maybe[Int] {
  $x ~~ Int ?? something($x) !! nothing(Int);
}
multi sub foo($x) returns Maybe {
  (0,1).roll ?? 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")

# subs that return Maybe can be chained with the bind operator >>=
sub halve(Int $x --> Maybe[Int]) {
    given $x {
        when   * %% 2 { something( $x div 2 ) }
        when ! * %% 2 { nothing(Int) }
    }
}
say (halve 3) >>= &halve;
say (something 32) >>= &halve >>= &halve >>= &halve;

Installation

zef install Definitely

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.

method is-something

method is-something() returns Bool

Returns true for Some

sub something

sub something(
    ::Type  $value
) returns Mu

Simple creation of Some objects that also match the Maybe type.

multi sub nothing

multi sub nothing(
    ::Type  $
) returns Mu

Used to create None objects when your method returns a typed Maybe.

multi sub nothing

multi sub nothing() returns Mu

Used to create None objects when your method returns an untyped Maybe.

sub unwrap

sub unwrap(
    Definitely::Maybe $maybe_obj,
    Str $message
) returns Mu

extracts the value from a Maybe object or dies with your message

multi sub bind

multi infix:«>>=»(
    Maybe $x, 
    &f
 ) returns Maybe

bind operation >>= for chaining subs that return a Maybe. (M a) -> &f(a -> M b) -> (M b), which receives a monadic value M a and a function &f that accepts values of the base type a. Bind unwraps a, applies &f to it, and returns the result of &f as a Maybe value M b (see Wikipedia Monad)