ExamplesBy LevelBy TopicLearning Paths
601 Fundamental

Coproduct Types (Sum Types)

Functional Programming

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

  • • How Either<A, B> generalizes Result<T, E> and Option<T> to arbitrary coproducts
  • • How bimap, map_left, map_right transform either side of a sum type
  • • How fold collapses a coproduct by providing functions for each case
  • • How Rust's enums are sum types and what category theory says about them
  • • Where coproducts appear: error handling, branching data, type-safe union types
  • Code Example

    enum Either<A, B> { Left(A), Right(B) }

    Key Differences

  • Built-in vs library: Rust's 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.
  • Biased vs symmetric: Result<T, E> is right-biased (map transforms Ok); Either<A, B> is symmetric; OCaml's result is also right-biased.
  • Functor instance: Rust manually implements map_left/map_right methods; Haskell/OCaml have typeclass/functor instances enabling generic fmap.
  • GAT limit: A true polymorphic 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));
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Either chain: Implement fn partition_either<A, B>(items: Vec<Either<A, B>>) -> (Vec<A>, Vec<B>) that splits an Either collection into two separate lists.
  • Error accumulation: Build 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: Implement From<Result<A, B>> for Either<B, A> (note the flipped convention) and From<Either<B, A>> for Result<A, B>.
  • Open Source Repos