Monad Laws
Tutorial Video
Text description (accessibility)
This video demonstrates the "Monad Laws" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. Monads extend functors with sequential composition. Key difference from OCaml: 1. **HKT requirement**: These morphisms ideally require higher
Tutorial
The Problem
Monads extend functors with sequential composition. Three laws must hold: left identity (return a >>= f == f a), right identity (m >>= return == m), and associativity ((m >>= f) >>= g == m >>= (f . (g =<<))). These laws ensure that monadic operations compose predictably. Option and Result in Rust satisfy these laws — and_then is monadic bind.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
//! # Monad Laws
//!
//! Monads must satisfy left identity, right identity, and associativity.
/// Monad operations for Option.
pub trait OptionMonad<A> {
fn pure(a: A) -> Self;
fn bind<B>(self, f: impl FnOnce(A) -> Option<B>) -> Option<B>;
}
impl<A> OptionMonad<A> for Option<A> {
fn pure(a: A) -> Self {
Some(a)
}
fn bind<B>(self, f: impl FnOnce(A) -> Option<B>) -> Option<B> {
self.and_then(f)
}
}
/// Left identity: pure(a).bind(f) == f(a)
pub fn check_left_identity<A: Clone, B: PartialEq>(a: A, f: impl Fn(A) -> Option<B>) -> bool {
let left = Option::pure(a.clone()).bind(&f);
let right = f(a);
left == right
}
/// Right identity: m.bind(pure) == m
pub fn check_right_identity<A: Clone + PartialEq>(m: Option<A>) -> bool {
let left = m.clone().bind(Option::pure);
left == m
}
/// Associativity: m.bind(f).bind(g) == m.bind(|x| f(x).bind(g))
pub fn check_associativity<A: Clone, B: Clone, C: PartialEq>(
m: Option<A>,
f: impl Fn(A) -> Option<B> + Clone,
g: impl Fn(B) -> Option<C> + Clone,
) -> bool {
let left = m.clone().bind(&f).bind(&g);
let right = m.bind(|x| f(x).bind(&g));
left == right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_identity() {
let f = |x: i32| Some(x * 2);
assert!(check_left_identity(5, f));
}
#[test]
fn test_right_identity() {
assert!(check_right_identity(Some(42)));
assert!(check_right_identity(None::<i32>));
}
#[test]
fn test_associativity() {
let f = |x: i32| Some(x + 1);
let g = |x: i32| Some(x * 2);
assert!(check_associativity(Some(5), f, g));
}
#[test]
fn test_bind_chain() {
let result = Some(5).bind(|x| Some(x + 1)).bind(|x| Some(x * 2));
assert_eq!(result, Some(12));
}
}Key Differences
recursion-schemes are widely used; in Rust and OCaml, direct recursion is more common in practice.OCaml Approach
OCaml's pattern matching and recursive types make morphism implementations natural. The Fix type and F-algebra/coalgebra patterns translate directly, though without Haskell's typeclass machinery:
(* OCaml recursive schemes use:
- Recursive variant types for F-algebras
- Higher-order functions for the morphism
- GADTs for type-safe fixed points in advanced cases *)
Full Source
#![allow(clippy::all)]
//! # Monad Laws
//!
//! Monads must satisfy left identity, right identity, and associativity.
/// Monad operations for Option.
pub trait OptionMonad<A> {
fn pure(a: A) -> Self;
fn bind<B>(self, f: impl FnOnce(A) -> Option<B>) -> Option<B>;
}
impl<A> OptionMonad<A> for Option<A> {
fn pure(a: A) -> Self {
Some(a)
}
fn bind<B>(self, f: impl FnOnce(A) -> Option<B>) -> Option<B> {
self.and_then(f)
}
}
/// Left identity: pure(a).bind(f) == f(a)
pub fn check_left_identity<A: Clone, B: PartialEq>(a: A, f: impl Fn(A) -> Option<B>) -> bool {
let left = Option::pure(a.clone()).bind(&f);
let right = f(a);
left == right
}
/// Right identity: m.bind(pure) == m
pub fn check_right_identity<A: Clone + PartialEq>(m: Option<A>) -> bool {
let left = m.clone().bind(Option::pure);
left == m
}
/// Associativity: m.bind(f).bind(g) == m.bind(|x| f(x).bind(g))
pub fn check_associativity<A: Clone, B: Clone, C: PartialEq>(
m: Option<A>,
f: impl Fn(A) -> Option<B> + Clone,
g: impl Fn(B) -> Option<C> + Clone,
) -> bool {
let left = m.clone().bind(&f).bind(&g);
let right = m.bind(|x| f(x).bind(&g));
left == right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_identity() {
let f = |x: i32| Some(x * 2);
assert!(check_left_identity(5, f));
}
#[test]
fn test_right_identity() {
assert!(check_right_identity(Some(42)));
assert!(check_right_identity(None::<i32>));
}
#[test]
fn test_associativity() {
let f = |x: i32| Some(x + 1);
let g = |x: i32| Some(x * 2);
assert!(check_associativity(Some(5), f, g));
}
#[test]
fn test_bind_chain() {
let result = Some(5).bind(|x| Some(x + 1)).bind(|x| Some(x * 2));
assert_eq!(result, Some(12));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_identity() {
let f = |x: i32| Some(x * 2);
assert!(check_left_identity(5, f));
}
#[test]
fn test_right_identity() {
assert!(check_right_identity(Some(42)));
assert!(check_right_identity(None::<i32>));
}
#[test]
fn test_associativity() {
let f = |x: i32| Some(x + 1);
let g = |x: i32| Some(x * 2);
assert!(check_associativity(Some(5), f, g));
}
#[test]
fn test_bind_chain() {
let result = Some(5).bind(|x| Some(x + 1)).bind(|x| Some(x * 2));
assert_eq!(result, Some(12));
}
}
Deep Comparison
Monad Laws
A Monad has pure (wrap) and bind (chain) operations.
Laws
pure(a).bind(f) == f(a)m.bind(pure) == mm.bind(f).bind(g) == m.bind(|x| f(x).bind(g))Rust Monads
Option<T> - Some/and_thenResult<T, E> - Ok/and_thenVec<T> - singleton/flat_map