Monad laws in Raku

Introduction

I participated last week in the Wolfram Technology Conference 2025. My talk was titled “Applications of Monadic Programming” — a shorter version of a similarly named presentation “Applications of Monadic Programming, Part 1, Questions & Answers”, [AAv5], which I recorded and posted three months ago.

After the conference I decided that it is a good idea to rewrite and re-record the presentation with a Raku-centric exposition. (I have done that before, see: “Simplified Machine Learning Workflows Overview (Raku-centric)”, [AAv4].)

That effort requires to verify that the Monad laws apply to certain constructs of the Raku language. This document (notebook) defines the Monad laws and provides several verifications for different combinations of operators and coding styles.

This document (notebook) focuses on built-in Raku features that can be used in monadic programming. It does not cover Raku packages that enhance Raku’s functionality or syntax for monadic programming. Also, since Raku is a feature-rich language, not all approaches to making monadic pipelines are considered — only the main and obvious ones. (I.e. the ones I consider “main and obvious.”)

The examples in this document are very basic. Useful, more complex (yet, elegant) examples of monadic pipelines usage in Raku are given in the notebook “Monadic programming examples”, [AAn1].

Context

Before going further, let us list the applications of monadic programming we consider:

  1. Graceful failure handling
  2. Rapid specification of computational workflows
  3. Algebraic structure of written code

Remark: Those applications are discussed in [AAv5] (and its future Raku version.)

As a tools maker for Data Science (DS) and Machine Learning (ML), I am very interested in Point 1; but as a “simple data scientist” I am mostly interested in Point 2.

That said, a large part of my Raku programming has been dedicated to rapid and reliable code generation for DS and ML by leveraging the algebraic structure of corresponding software monads — i.e. Point 3. (See [AAv2, AAv3, AAv4].) For me, first and foremost, monadic programming pipelines are just convenient interfaces to computational workflows. Often I make software packages that allow “easy”, linear workflows that can have very involved computational steps and multiple tuning options.

Dictionary

  • Monadic programming
    A method for organizing computations as a series of steps, where each step generates a value along with additional information about the computation, such as possible failures, non-determinism, or side effects. See [Wk1].
  • Monadic pipeline
    Chaining of operations with a certain syntax. Monad laws apply loosely (or strongly) to that chaining.
  • Uniform Function Call Syntax (UFCS)
    A feature that allows both free functions and member functions to be called using the same object.function() method call syntax.
  • Method-like call
    Same as UFCS. A Raku example: [3, 4, 5].&f1.$f2.

Verifications overview

Raku — as expected — has multiple built-in mechanisms for doing monadic programming. A few of those mechanisms are “immediate”, other require adherence to certain coding styles or very direct and simple definitions. Not all of the Monad law verifications have to be known (or understood) by a programmer. Here is a table that summarizes them:

TypeDescription
Array and ==>Most immediate, clear-cut
&unit and &bindDefinitions according to the Monad laws; programmable semicolon
Any and andthenGeneral, built-in monad!
Styled OOPStandard and straightforward

The verification for each approach is given as an array of hashmaps with keys “name”, “input”, “expected”. The values of “input” are strings which are evaluated with the lines:

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

EVAL is used in order to have easily verifiable “single origin of truth.”

The HTML verification tables are obtained withe function proof-table, which has several formatting options. (Set the section “Setup”.)


What is a monad? (informally)

Many programmers are familiar with monadic pipelines, although, they might know them under different names. This section has monadic pipeline examples from Unix, R, and Raku that should help understanding the more formal definitions in the next section.

Unix examples

Most (old and/or Raku) programmers are familiar with Unix programming. Hence, they are familiar with monadic pipelines.

Pipeline (|)

The Unix pipeline semantics and syntax was invented and introduced soon after the first Unix release. Monadic pipelines (or uniform function call) have very similar motivation and syntax.

Here is an example of Unix pipeline in which the output of one shell program is the input for the next:

#% bash
find . -name "*nb" | grep -i chebyshev | xargs -Iaaa date -r aaa

# Fri Dec 13 07:59:16 EST 2024
# Tue Dec 24 14:24:20 EST 2024
# Sat Dec 14 07:57:41 EST 2024

That UNIX command:

  1. Finds in the current directory all files with names that finish with “nb”
  2. Picks from the list produces by 1 only the rows that contain the string “chebyshev”
  3. Gives the dates of modification of those files

Reverse-Polish calculator (dc)

One of the oldest surviving Unix language programs is dc (desktop calculator) that uses reverse-Polish notation. Here is an example of the command 3 5 + 4 * p given to dc that prints out 32, i.e. (3 + 5) * 4:

#% bash
echo '3 5 + 4 * p' | dc
# 32

We can see that dc command as a pipeline:

  • The numbers are functions that place the corresponding values in the context (which is a stack)
  • The space between the symbols is the pipeline constructor

Data wrangling

Posit‘s constellation of R packages “tidyverse” facilitates pipeline construction of data wrangling workflows. Here is an example in which columns of the data frame dfTitanic are renamed, then its rows are filtered and grouped, and finally, the corresponding group sizes are shown:

dfTitanic %>%
dplyr::rename(age = passengerAge, sex = passengerSex, class = passengerClass) %>%
dplyr::filter(age > 10) %>%
dplyr::group_by(class, sex) %>%
dplyr::count()

Here is a corresponding Raku pipeline andthen style (using subs of “Data::Reshapers”, [AAp5]):

@dsTitanic 
andthen rename-columns($_,  {passengerAge => 'age', passengerSex => 'sex', passengerSurvival => 'survival'})
andthen $_.grep(*<age> ≥ 10).List
andthen group-by($_, <sex survival>)
andthen $_».elems


What is a monad? (formally)

The monad definition

In this document a monad is any set of a symbol $m$ and two operators unit and bind that adhere to the monad laws. (See the next sub-section.) The definition is taken from [Wk1] and [PW1] and phrased in Raku terms. In order to be brief, we deliberately do not consider the equivalent monad definition based on unitjoin, and map (also given in [PW1].)

Here are operators for a monad associated with a certain class M:

  1. monad unit function is unit(x) = M.new(x)
  2. monad bind function is a rule like bind(M:D $x, &f) = &f(x) with &f($x) ~~ M:D giving True.

Note that:

  • the function bind unwraps the content of M and gives it to the function &f;
  • the functions given as second arguments to bind (see&f) are responsible to return as results instances of the monad class M.

Here is an illustration formula showing a monad pipeline:

From the definition and formula it should be clear that if for the result f(x) of bind the test f(x) ~~ M:D is True then the result is ready to be fed to the next binding operation in monad’s pipeline. Also, it is easy to program the pipeline functionality with reduce:

reduce(&bind, M.new(3), [&f1, &f2, $f3])

The monad laws

The monad laws definitions are taken from [H1] and [H3]. In the monad laws given below “⟹” is for monad’s binding operation and x↦expr is for a function in anonymous form.

Here is a table with the laws:

nameLHSRHS
Left identityunit m ⟹ ff m
Right identitym ⟹ unitm
Associativity(m ⟹ f) ⟹ gm ⟹ (x ⟼ f x ⟹ g)

Setup

Here we load packages for tabulating the verification results:

use Data::Translators;
use Hilite::Simple;

Here is a sub that is used to tabulate the Monad laws proofs:

#| Tabulates Monad laws verification elements.
sink sub proof-table(
    @tbl is copy,              #= Array of hashmaps with keys <name input expected>
    Bool:D :$raku = True,      #= Whether .raku be invoked in the columns "output" and "expected"
    Bool:D :$html = True,      #= Whether to return HTML table
    Bool:D :$highlight = True  #= Whether to highlight the Raku code in the HTML table
    ) {
    
    if $raku {
        @tbl .= map({ $_<output> = $_<output>.raku; $_});
        @tbl .= map({ $_<expected> = $_<expected>.raku; $_});
    }
    return @tbl unless $html;

    my @field-names = <name input output expected>;
    my $res = to-html(@tbl, :@field-names, align => 'left');
    
    if $highlight {
        $res = reduce( {$^a.subst($^b.trans([ '<', '>', '&' ] => [ '&lt;', '&gt;', '&amp;' ]), $^b.&hilite)}, $res, |@tbl.map(*<input>) );
        $res = $res.subst('<pre class="nohighlights">', :g).subst('</pre>', :g)
    }
    
    return $res;
}


Array and ==>

The monad laws are satisfied in Raku for:

  • Every function f that takes an array argument and returns an array
  • The unit operation being Array
  • The feed operator (==>) being the binding operation
NameInputOutput
Left identityArray($a) ==> &f()&f($a)
Right identity$a ==> { Array($_) }()$a
Associativity LHSArray($a) ==> &f1() ==> &f2()&f2(&f1($a))
Associativity RHSArray($a) ==> { &f($_) ==> &f2() }()&f2(&f1($a))

Here is an example:

#% html

# Operators in the monad space
my &f =    { Array($_) >>~>> '_0' }
my &f1 =   { Array($_) >>~>> '_1' }
my &f2 =   { Array($_) >>~>> '_2' }

# Some object
my $a = 5; #[3, 4, 'p'];

# Verification table
my @tbl =
 { name => 'Left identity',     :input( 'Array($a) ==> &f()'                    ), :expected( &f($a)       )},
 { name => 'Right identity',    :input( '$a ==> { Array($_) }()'                ), :expected( $a           )},
 { name => 'Associativity LHS', :input( 'Array($a) ==> &f1() ==> &f2()'         ), :expected( &f2(&f1($a)) )},
 { name => 'Associativity RHS', :input( 'Array($a) ==> { &f1($_) ==> &f2() }()' ), :expected( &f2(&f1($a)) )}
;

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

@tbl ==> proof-table(:html, :raku, :highlight)

nameinputoutputexpected
Left identityArray($a) ==>&f()$[“5_0”]$[“5_0”]
Right identity$a==> { Array($_) }()$[5]5
Associativity LHSArray($a) ==>&f1() ==>&f2()$[“5_1_2”]$[“5_1_2”]
Associativity RHSArray($a) ==> { &f1($_) ==>&f2() }()$[“5_1_2”]$[“5_1_2”]

Remark: In order to keep the verification simple I did not want to extend it to cover Positional and Seq objects. In some sense, that is also covered by Any and andthen verification. (See below.)


&unit and &bind

From the formal Monad definition we can define the corresponding functions &unit and &bind and verify the Monad laws with them:

#% html

# Monad operators
my &unit = { Array($_) };
my &bind = { $^b($^a) };

# Operators in the monad space
my &f  = { Array($_) >>~>> '_0' }
my &f1 = { Array($_) >>~>> '_1' }
my &f2 = { Array($_) >>~>> '_2' }

# Some object
my $a = (3, 4, 'p');

# Verification table
my @tbl =
 { name => 'Left identity',     :input( '&bind( &unit($a), &f)'                      ), :expected( &f($a)       )},
 { name => 'Right identity',    :input( '&bind( $a, &unit)'                          ), :expected( $a           )},
 { name => 'Associativity LHS', :input( '&bind( &bind( &unit($a), &f1), &f2)'        ), :expected( &f2(&f1($a)) )},
 { name => 'Associativity RHS', :input( '&bind( &unit($a), { &bind(&f1($_), &f2) })' ), :expected( &f2(&f1($a)) )}
;

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

@tbl ==> proof-table(:html, :raku, :highlight)

nameinputoutputexpected
Left identity&bind( &unit($a),&f)$[“3_0”, “4_0”, “p_0”]$[“3_0”, “4_0”, “p_0”]
Right identity&bind( $a,&unit)$[3, 4, “p”]$(3, 4, “p”)
Associativity LHS&bind( &bind( &unit($a),&f1),&f2)$[“3_1_2”, “4_1_2”, “p_1_2”]$[“3_1_2”, “4_1_2”, “p_1_2”]
Associativity RHS&bind( &unit($a), { &bind(&f1($_),&f2) })$[“3_1_2”, “4_1_2”, “p_1_2”]$[“3_1_2”, “4_1_2”, “p_1_2”]

To achieve the “monadic pipeline look and feel” with &unit and &bind, certain infix definitions must be implemented. For example, infix<:»> ($m, &f) { &bind($m, &f) }. Here is a full verification example:

#% html

# Monad's semicolon
sub infix:<:»>($m, &f) { &bind($m, &f) }

# Some object
my $a = (1, 6, 'y');

# Verification table
my @tbl =
 { name => 'Left identity',     :input( '&unit($a) :» &f'                 ), :expected( &f($a)       )},
 { name => 'Right identity',    :input( '$a :» &unit'                     ), :expected( $a           )},
 { name => 'Associativity LHS', :input( '&unit($a) :» &f1 :» &f2'         ), :expected( &f2(&f1($a)) )}, 
 { name => 'Associativity RHS', :input( '&unit($a) :» { &f1($_) :» &f2 }' ), :expected( &f2(&f1($a)) )}
;

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

@tbl ==> proof-table(:html, :raku, :highlight)

nameinputoutputexpected
Left identity&unit($a) :» &f$[“1_0”, “6_0”, “y_0”]$[“1_0”, “6_0”, “y_0”]
Right identity$a:» &unit$[1, 6, “y”]$(1, 6, “y”)
Associativity LHS&unit($a) :» &f1:» &f2$[“1_1_2”, “6_1_2”, “y_1_2”]$[“1_1_2”, “6_1_2”, “y_1_2”]
Associativity RHS&unit($a) :» { &f1($_) :» &f2 }$[“1_1_2”, “6_1_2”, “y_1_2”]$[“1_1_2”, “6_1_2”, “y_1_2”]

To see that the “semicolon”  is programmable change the definition for infix:<:»>. For example:

sub infix:<:»>($m, &f) { say $m.raku; &bind($m, &f) }


Any and andthen

The operator andthen is similar to the feed operator ==>. For example:

my $hw = "  hello world  ";
$hw andthen .trim andthen .uc andthen .substr(0,5) andthen .say

From the documentation:

The andthen operator returns Empty if the first argument is undefined, otherwise the last argument. The last argument is returned as-is, without being checked for definedness at all. Short-circuits. The result of the left side is bound to $_ for the right side, or passed as arguments if the right side is a Callable, whose count must be 0 or 1.

Note that these two expressions are equivalent:

$a andthen .&f1 andthen .&f2;
$a andthen &f1($_) andthen &f2($_);

A main feature andthen is to return Empty if its first argument is not defined. That is, actually, very “monadic” — graceful handling of errors is one of the main reasons of use Monadic programming. It is also limiting, because the monad failure is “just” Empty. That is mostly a theoretical limitation; in practice Raku has many other elements, like, notandthen and orelse, that can shape the workflows to programmer’s desires.

The Monad laws hold for Any.new as the unit operation and andthen as the binding operation.

#% html
# Operators in the monad space
my &f  = { Array($_) >>~>> '_0' }
my &f1 = { Array($_) >>~>> '_1' }
my &f2 = { Array($_) >>~>> '_2' }

# Some object
my $a = (3, 9, 'p');

# Verification table
my @tbl =
{ name => 'Left identity',     :input( '$a andthen .&f'                   ), :expected( &f($a)       )},
{ name => 'Right identity',    :input( '$a andthen $_'                    ), :expected( $a           )},
{ name => 'Associativity LHS', :input( '$a andthen .&f1 andthen .&f2'     ), :expected( &f1(&f2($a)) )},
{ name => 'Associativity RHS', :input( '$a andthen { .&f1 andthen .&f2 }' ), :expected( &f1(&f2($a)) )}
;

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

@tbl ==> proof-table(:html, :raku, :highlight)

nameinputoutputexpected
Left identity$aandthen .&f$[“3_0”, “9_0”, “p_0”]$[“3_0”, “9_0”, “p_0”]
Right identity$aandthen$_$(3, 9, “p”)$(3, 9, “p”)
Associativity LHS$aandthen .&f1andthen .&f2$[“3_1_2”, “9_1_2”, “p_1_2”]$[“3_2_1”, “9_2_1”, “p_2_1”]
Associativity RHS$aandthen { .&f1andthen .&f2 }$[“3_1_2”, “9_1_2”, “p_1_2”]$[“3_2_1”, “9_2_1”, “p_2_1”]

Monad class and method call

Raku naturally supports method chaining using dot notation (.) for actual methods defined on a class or type.
Hence, a more “standard” way for doing Monadic programming is to use a monad class, say M, and method call:

  • M.new(...) plays the monad unit role — i.e. it uplifts objects into monad’s space
  • $m.f(...) (where $m ~~ M:D) plays the binding role if all methods of M return M:D objects

The axioms verification needs to be done using a particular class definition format (see the example below):

1. Left identity applies:

M.new($x).f does mean application of M.f to $x.

2. Right identity applies by using M.new

3. Associativity axiom holds

For RHS, again, method-like call (call as method) is used.

Here is an example:

#% html

# Monad class definition
my class M { 
    has $.context;
    multi method new($context) { self.bless(:$context) }
    multi method new(M:D $m) { self.bless(context => $m.context) }
    method f() { $!context = $!context >>~>> '_0'; self}
    method f1() { $!context = $!context >>~>> '_1'; self}
    method f2() { $!context = $!context >>~>> '_2'; self}
}

# Some object
my $a = 5; #[5, 3, 7];

# Verification table
my @tbl =
 { name => 'Left identity',     :input( 'M.new($a).f'              ), :expected( M.new($a).f             )},
 { name => 'Right identity',    :input( 'my M:D $x .= new($a)'     ), :expected( M.new($a)               )},
 { name => 'Associativity LHS', :input( '(M.new($a).f1).f2'        ), :expected( (M.new($a).f1).f2       )},
 { name => 'Associativity RHS', :input( 'M.new($a).&{ $_.f1.f2 }'  ), :expected( M.new($a).&{ $_.f1.f2 } )}
;

use MONKEY-SEE-NO-EVAL;
@tbl .= map({ $_<output> = EVAL($_<input>); $_ });

@tbl ==> proof-table(:html, :raku, :highlight)

nameinputoutputexpected
Left identityM.new($a).fM.new(context => “5_0”)M.new(context => “5_0”)
Right identitymy M:D $x.=new($a)M.new(context => 5)M.new(context => 5)
Associativity LHS(M.new($a).f1).f2M.new(context => “5_1_2”)M.new(context => “5_1_2”)
Associativity RHSM.new($a).&{ $_.f1.f2 }M.new(context => “5_1_2”)M.new(context => “5_1_2”)

Method-like calls

Instead of M methods f<i>(...) we can have corresponding functions &f<i>(...) and “method-like call” chains:

M.new(3).&f1.&f2.&f3

That is a manifestation of Raku’s principle “everything is an object.” Here is an example:

[6, 3, 12].&{ $_.elems }.&{ sqrt($_) }.&{ $_ ** 3 }

5.196152422706631

Remark A simpler version of the code above is: [6, 3, 12].elems.sqrt.&{ $_ ** 3 }.


Conclusion

It is encouraging — both readability-wise and usability-wise — that Raku code can be put into easy to read and understand pipeline-like computational steps. Raku supports that in its Functional Programming (FP) and Object-Oriented Programming (OOP) paradigms. The support can be also seen from these programming-idiomatic and design-architectural points of view:

  • Any computation via:
    • andthen and ==>
    • Method-like calls or UFCS
  • For special functions and (gradually typed) arguments via:
    • sub and infix
    • OOP

Caveats

There are a few caveats to be kept in mind when using andthen and ==> (in Raku’s language version “6.d”.)

does it run?andthen==>
no(^100).pick xx 5 andthen .List andthen { say "max {$_.max}"; $_} andthen $_».&is-prime(^100).pick xx 5 ==> {.List} ==> { say "max {$_.max}"; $_} ==> { $_».&is-prime }
yes(^100).pick xx 5 andthen .List andthen { say "max {$_.max}"; $_}($_) andthen $_».&is-prime(^100).pick xx 5 ==> {.List}() ==> { say "max {$_.max}"; $_}() ==> { $_».&is-prime }()

References

Articles, blog posts

[Wk1] Wikipedia entry: Monad (functional programming), URL: https://en.wikipedia.org/wiki/Monad_(functional_programming) .

[Wk2] Wikipedia entry: Monad transformer, URL: https://en.wikipedia.org/wiki/Monad_transformer .

[H1] Haskell.org article: Monad laws, URL: https://wiki.haskell.org/Monad_laws.

[SH2] Sheng Liang, Paul Hudak, Mark Jones, “Monad transformers and modular interpreters”, (1995), Proceedings of the 22nd ACM SIGPLAN-SIGACT symposium on Principles of programming languages. New York, NY: ACM. pp. 333–343. doi:10.1145/199448.199528.

[PW1] Philip Wadler, “The essence of functional programming”, (1992), 19’th Annual Symposium on Principles of Programming Languages, Albuquerque, New Mexico, January 1992.

[RW1] Hadley Wickham et al., dplyr: A Grammar of Data Manipulation, (2014), tidyverse at GitHub, URL: https://github.com/tidyverse/dplyr . (See also, http://dplyr.tidyverse.org .)

[AA1] Anton Antonov, “Monad code generation and extension”, (2017), MathematicaForPrediction at WordPress.

[AAn1] Anton Antonov, “Monadic programming examples”, (2025), RakuForPrediction-blog at GitHub.

Packages

[AAp1] Anton Antonov, MonadMakers, Wolfram Language paclet, (2023), Wolfram Language Paclet Repository.

[AAp2] Anton Antonov, StatStateMonadCodeGeneratoreNon, R package, (2019-2024),
GitHub/@antononcube.

[AAp3] Anton Antonov, DSL::English::DataQueryWorkflows, Raku package, (2020-2024),
GitHub/@antononcube.

[AAp4] Anton Antonov, FunctionalParsers, Raku package, (2023-2024),
GitHub/@antononcube.

[AAp5] Anton Antonov, Data::Reshapers, Raku package, (2022-2025),
GitHub/@antononcube.

Videos

[AAv1] Anton Antonov, Monadic Programming: With Application to Data Analysis, Machine Learning and Language Processing, (2017), Wolfram Technology Conference 2017 presentation. YouTube/WolframResearch.

[AAv2] Anton Antonov, Raku for Prediction, (2021), The Raku Conference 2021.

[AAv3] Anton Antonov, Simplified Machine Learning Workflows Overview, (2022), Wolfram Technology Conference 2022 presentation. YouTube/WolframResearch.

[AAv4] Anton Antonov, Simplified Machine Learning Workflows Overview (Raku-centric), (2022), Wolfram Technology Conference 2022 presentation. YouTube/@AAA4prediction.

[AAv5] Anton Antonov, Applications of Monadic Programming, Part 1, Questions & Answers, (2025), YouTube/@AAA4prediction.