Coproduct Types (Sum Types)
Tutorial Video
Text description (accessibility)
This video demonstrates the "Coproduct Types (Sum Types)" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Coproduct types (also called sum types, tagged unions, or discriminated unions) represent "one of several possibilities." They are the mathematical dual of product types. Key difference from OCaml: 1. **Built
Tutorial
The Problem
Coproduct types (also called sum types, tagged unions, or discriminated unions) represent "one of several possibilities." They are the mathematical dual of product types. In category theory, a coproduct A + B is the "either A or B" type — represented as Rust's enum with two variants. Either<A, B> is the canonical general-purpose coproduct, equivalent to Haskell's Either, OCaml's ('a, 'b) result or Either from Either libraries. Sum types are the foundation of error handling (Result), optional values (Option), and any branching data.
🎯 Learning Outcomes
Either<A, B> generalizes Result<T, E> and Option<T> to arbitrary coproductsbimap, map_left, map_right transform either side of a sum typefold collapses a coproduct by providing functions for each caseCode Example
enum Either<A, B> { Left(A), Right(B) }Key Differences
Result<T, E> is the standard coproduct; Either<A, B> is a library type; OCaml has ('a, 'b) result and ('a, 'b) Either.t separately.Result<T, E> is right-biased (map transforms Ok); Either<A, B> is symmetric; OCaml's result is also right-biased.map_left/map_right methods; Haskell/OCaml have typeclass/functor instances enabling generic fmap.Functor trait for Either requires GATs (Generic Associated Types) in Rust — complex but supported since Rust 1.65.OCaml Approach
OCaml uses result as the built-in asymmetric coproduct:
type ('a, 'b) either = Left of 'a | Right of 'b
let bimap f g = function Left a -> Left (f a) | Right b -> Right (g b)
let fold fl fr = function Left a -> fl a | Right b -> fr b
Full Source
#![allow(clippy::all)]
//! # Coproduct Types (Sum Types)
//!
//! Combine multiple types into a single sum type.
/// Either type - canonical two-way coproduct.
#[derive(Debug, Clone, PartialEq)]
pub enum Either<A, B> {
Left(A),
Right(B),
}
impl<A, B> Either<A, B> {
pub fn is_left(&self) -> bool {
matches!(self, Either::Left(_))
}
pub fn is_right(&self) -> bool {
matches!(self, Either::Right(_))
}
pub fn map_left<C>(self, f: impl FnOnce(A) -> C) -> Either<C, B> {
match self {
Either::Left(a) => Either::Left(f(a)),
Either::Right(b) => Either::Right(b),
}
}
pub fn map_right<C>(self, f: impl FnOnce(B) -> C) -> Either<A, C> {
match self {
Either::Left(a) => Either::Left(a),
Either::Right(b) => Either::Right(f(b)),
}
}
}
/// Three-way coproduct.
#[derive(Debug, Clone, PartialEq)]
pub enum Either3<A, B, C> {
First(A),
Second(B),
Third(C),
}
/// Inject into coproduct.
pub fn left<A, B>(a: A) -> Either<A, B> {
Either::Left(a)
}
pub fn right<A, B>(b: B) -> Either<A, B> {
Either::Right(b)
}
/// Eliminate coproduct.
pub fn either<A, B, C>(e: Either<A, B>, f: impl FnOnce(A) -> C, g: impl FnOnce(B) -> C) -> C {
match e {
Either::Left(a) => f(a),
Either::Right(b) => g(b),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_right() {
let l: Either<i32, &str> = left(42);
let r: Either<i32, &str> = right("hello");
assert!(l.is_left());
assert!(r.is_right());
}
#[test]
fn test_either() {
let l: Either<i32, &str> = left(42);
let result = either(l, |n| n * 2, |s| s.len() as i32);
assert_eq!(result, 84);
}
#[test]
fn test_map() {
let l: Either<i32, i32> = left(5);
let mapped = l.map_left(|n| n * 2);
assert_eq!(mapped, Either::Left(10));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_left_right() {
let l: Either<i32, &str> = left(42);
let r: Either<i32, &str> = right("hello");
assert!(l.is_left());
assert!(r.is_right());
}
#[test]
fn test_either() {
let l: Either<i32, &str> = left(42);
let result = either(l, |n| n * 2, |s| s.len() as i32);
assert_eq!(result, 84);
}
#[test]
fn test_map() {
let l: Either<i32, i32> = left(5);
let mapped = l.map_left(|n| n * 2);
assert_eq!(mapped, Either::Left(10));
}
}
Deep Comparison
Coproduct Types
A coproduct is a "tagged union" or "sum type" - a value that is one of several possibilities.
Rust
enum Either<A, B> { Left(A), Right(B) }
OCaml
type ('a, 'b) either = Left of 'a | Right of 'b
Either is the canonical two-way coproduct. Rust's Result<T, E> is essentially Either<T, E> with different names.
Exercises
fn partition_either<A, B>(items: Vec<Either<A, B>>) -> (Vec<A>, Vec<B>) that splits an Either collection into two separate lists.Either<Vec<Error>, Success> where you accumulate errors on the Left side — implement combine(a: Either<Vec<E>, A>, b: Either<Vec<E>, B>) -> Either<Vec<E>, (A, B)>.From<Result<A, B>> for Either<B, A> (note the flipped convention) and From<Either<B, A>> for Result<A, B>.