Monoid Pattern
Functional Programming
Tutorial
The Problem
Demonstrates the monoid algebraic structure — a type with an identity element and an associative binary operation — and how to implement it generically to reduce collections uniformly.
🎯 Learning Outcomes
fold with a monoid identity to collapse collections without special-casing empty inputsCode Example
#![allow(clippy::all)]
pub trait Monoid {
fn empty() -> Self;
fn combine(self, other: Self) -> Self;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sum(pub i32);
impl Monoid for Sum {
fn empty() -> Self {
Sum(0)
}
fn combine(self, other: Self) -> Self {
Sum(self.0 + other.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Product(pub i32);
impl Monoid for Product {
fn empty() -> Self {
Product(1)
}
fn combine(self, other: Self) -> Self {
Product(self.0 * other.0)
}
}
pub fn reduce_monoid<T: Monoid + Clone>(items: &[T]) -> T {
items
.iter()
.cloned()
.fold(T::empty(), |acc, x| acc.combine(x))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_monoid() {
let nums = vec![Sum(1), Sum(2), Sum(3)];
assert_eq!(reduce_monoid(&nums), Sum(6));
assert_eq!(Sum::empty().combine(Sum(5)), Sum(5));
}
#[test]
fn test_product_monoid() {
let nums = vec![Product(1), Product(2), Product(3)];
assert_eq!(reduce_monoid(&nums), Product(6));
assert_eq!(Product::empty().combine(Product(5)), Product(5));
}
#[test]
fn test_empty_sum() {
let nums: Vec<Sum> = vec![];
assert_eq!(reduce_monoid(&nums), Sum(0));
}
#[test]
fn test_empty_product() {
let nums: Vec<Product> = vec![];
assert_eq!(reduce_monoid(&nums), Product(1));
}
}Key Differences
empty as a module-level value vs Rust requires an associated function empty() -> SelfOCaml Approach
OCaml expresses monoids as first-class modules satisfying a MONOID signature, passing them explicitly at the call site via locally abstract types. This avoids newtypes entirely — the integer type is reused directly under different module implementations.
Full Source
#![allow(clippy::all)]
pub trait Monoid {
fn empty() -> Self;
fn combine(self, other: Self) -> Self;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sum(pub i32);
impl Monoid for Sum {
fn empty() -> Self {
Sum(0)
}
fn combine(self, other: Self) -> Self {
Sum(self.0 + other.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Product(pub i32);
impl Monoid for Product {
fn empty() -> Self {
Product(1)
}
fn combine(self, other: Self) -> Self {
Product(self.0 * other.0)
}
}
pub fn reduce_monoid<T: Monoid + Clone>(items: &[T]) -> T {
items
.iter()
.cloned()
.fold(T::empty(), |acc, x| acc.combine(x))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_monoid() {
let nums = vec![Sum(1), Sum(2), Sum(3)];
assert_eq!(reduce_monoid(&nums), Sum(6));
assert_eq!(Sum::empty().combine(Sum(5)), Sum(5));
}
#[test]
fn test_product_monoid() {
let nums = vec![Product(1), Product(2), Product(3)];
assert_eq!(reduce_monoid(&nums), Product(6));
assert_eq!(Product::empty().combine(Product(5)), Product(5));
}
#[test]
fn test_empty_sum() {
let nums: Vec<Sum> = vec![];
assert_eq!(reduce_monoid(&nums), Sum(0));
}
#[test]
fn test_empty_product() {
let nums: Vec<Product> = vec![];
assert_eq!(reduce_monoid(&nums), Product(1));
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_monoid() {
let nums = vec![Sum(1), Sum(2), Sum(3)];
assert_eq!(reduce_monoid(&nums), Sum(6));
assert_eq!(Sum::empty().combine(Sum(5)), Sum(5));
}
#[test]
fn test_product_monoid() {
let nums = vec![Product(1), Product(2), Product(3)];
assert_eq!(reduce_monoid(&nums), Product(6));
assert_eq!(Product::empty().combine(Product(5)), Product(5));
}
#[test]
fn test_empty_sum() {
let nums: Vec<Sum> = vec![];
assert_eq!(reduce_monoid(&nums), Sum(0));
}
#[test]
fn test_empty_product() {
let nums: Vec<Product> = vec![];
assert_eq!(reduce_monoid(&nums), Product(1));
}
}
Exercises
Max monoid over integers using i32::MIN as the identity and extend reduce_monoid to find the maximum of a sliceStringConcat monoid and verify that reduce_monoid on an empty slice returns an empty stringcombine_all function that takes two slices of the same monoid type and interleaves their reductions, then verify associativity holds