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:
>>= for bind. The function you bind to should take an unwrapped value & return a Monad.>>- for map. The function you map to should take an unwrapped value and return an unwrapped value.
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.