Rand Stats

Monad

zef:apogee

Monad

Introduction

Implementation of a few common Monads in Raku.

You can also use the base class Monad to implement your own.

Issues and pull requests welcome.

Infix Operators

The Monad module implements two infix operators:

Monads

Monad::Either

Either monads are regularly used to represent OK (Right) & Error (Left) values.

In some languages this is called a Result monad.

use Monad;
use Monad::Either;

sub double ($i) {
    $i * 2;
}

sub bind_double ($i) {
    Monad::Either.unit($i * 2);
}

my $ok         = Monad::Either.unit(4); # Monad::Either::Right(value=4)
my $ok_doubled = $ok >>- &double;       # Monad::Either::Right(value=8)
$ok_doubled.is-right                    # True
say $ok_doubled.unwrap-right            # 8

my $not_ok         = Monad::Either.left(4);   # Monad::Either::Left(value=4)
my $not_ok_doubled = $ok >>- &double;         # Monad::Either::Left(value=8)
$ok_doubled.is-right                          # False
say $ok_doubled.unwrap-left                   # 4

my $ok_doubled_bind = $ok >>= &bind_double;     # Monad::Either::Right(value=8)
my $not_ok_bind     = $not_ok >>= &bind_double; # Monad::Either::Left(value=4)

Monad::List

List monads allow for chaining operators on lists and flattening them.

use Monad;
use Monad::List;

sub triplicate ($v) {
    ($v, $v, $v);
}

sub duplicate_half_bind ($v) {
  Monad::List.of($v, $v / 2);
}

my $list       = Monad::List.of(2, 3);               # Monad::List(value=[2,3])
my $tri_list   = $list >>- &triplicate;              # Monad::List(value=[2,2,2,3,3,3])
my $final_list = $tri_list >>= &duplicate_half_bind; # Monad::List(value=[2,1,2,1,2,1,3,1.5,3,1.5,3,1.5])

Monad::Maybe

Maybe monads model a value which may or may not be present.

In some languages, this is called Option or Optional.

use Monad;
use Monad::Maybe;

sub double ($i) { $i * 2 }
sub bind_double ($i) { Monad::Maybe.some($i * 2) }

my $some = Monad::Maybe.some(5);    # Monad::Maybe::Some(value=5)
my $none = Monad::Maybe.none();     # Monad::Maybe::None(value=Nil)

my $doubled = $some >>- &double;      # Monad::Maybe::Some(value=10)
my $binded  = $some >>= &bind_double; # Monad::Maybe::Some(value=10)

my $none_mapped = $none >>- &double;      # Monad::Maybe::None(value=Nil)
my $none_binded = $none >>= &bind_double; # Monad::Maybe::None(value=Nil)

$none.is-some # False
$none.is-none # True
$some.is-some # True
$some.is-none # False

$some.unwrap # 5
$none.unwrap # Nil

Monad::Reader

Reader monads model computations that depend on a shared environment.

use Monad;
use Monad::Reader;

sub ask-env {
    Monad::Reader.new(run => sub ($env) {
        "Hello, $env!";
    });
}

my $r = ask-env();
say $r.run("Alice"); # "Hello, Alice!"

# Reader composition
my $upper = $r.map(-> $env { $env.uc });
say $upper.run("Bob"); # "HELLO, BOB"

Monad::State

State monads thread mutable state through pure functions.

my $m             = Monad::State.unit(42);
my ($val, $state) = $m.run('init');

say $val;   # 42
say $state; # "init"

sub inc-state ($_) {
	Monad::State.new(run => sub ($s) { $s, $s + 1 })
}

my $m2              = $m.bind(&inc-state);
my ($val2, $state2) = $m2.run(10);

say $val2;   # 10
say $state2; # 11

Monad::Writer

Writer monads allow logging outside computations.

use Monad;
use Monad::Writer;

sub say_hi ($name) {
    Monad::Writer.new(value => "Hello $name", logs => "Greeted $name.")
}

my $w1 = Monad::Writer.unit("World");
my $w2 = $w1 >>= &say_hi;

say $w2.value; # "Hello World"
say $w2.logs;   # "Greeted World."

# map does not change log
my $w3 = $w2 >>- *.uc;
say $w3.value; # "HELLO WORLD"
say $w3.logs;   # "Greeted World."

More Examples

See the tests for more examples.