ExamplesBy LevelBy TopicLearning Paths
913 Intermediate

913-iterator-sum-product — Iterator Sum and Product

Functional Programming

Tutorial

The Problem

Reducing a sequence to a single number by addition or multiplication is so common that every language provides a dedicated abstraction. Python's sum(), OCaml's List.fold_left (+) 0, Haskell's sum and product. Rust's Iterator::sum() and Iterator::product() are zero-cost abstractions over fold with the identity element built in: 0 for sum, 1 for product. They work on any numeric type implementing Sum or Product and can be chained with any adapter. Factorial, sum-of-squares, inner product, and average are all expressible as one-liners using these consumers.

🎯 Learning Outcomes

  • • Use .sum() and .product() as clean alternatives to explicit .fold() calls
  • • Compute factorial using (1..=n).product()
  • • Chain .map() with .sum() for sum-of-squares and weighted sums
  • • Compute averages by combining .sum() with .len()
  • • Compare with OCaml's fold-based approach for the same computations
  • Code Example

    fn sum_ints(nums: &[i32]) -> i32      { nums.iter().copied().sum() }
    fn product_ints(nums: &[i32]) -> i32  { nums.iter().copied().product() }
    fn factorial(n: u64) -> u64           { (1..=n).product() }
    fn sum_of_squares(nums: &[i32]) -> i32 { nums.iter().map(|&x| x * x).sum() }

    Key Differences

  • Named abstractions: Rust's .sum() and .product() communicate intent clearly; OCaml uses generic fold_left — both express the same computation.
  • Type annotation: Rust sometimes needs turbofish for .sum::<f64>(); OCaml's type inference usually handles it automatically.
  • Product vs fold: Rust (1..=n).product() for factorial is more readable than OCaml's explicit fold with List.init; the computation is identical.
  • No standard sum: OCaml deliberately omits List.sum — it is considered a special case of the more general fold; Rust optimizes for the common case.
  • OCaml Approach

    OCaml has no List.sum — it uses List.fold_left (+) 0 xs. List.fold_left ( * ) 1 xs for product. Factorial: let factorial n = List.fold_left ( * ) 1 (List.init n (fun i -> i + 1)). Sum of squares: List.fold_left (fun acc x -> acc + x * x) 0 xs. Average: float_of_int (List.fold_left (+) 0 xs) /. float_of_int (List.length xs). The explicit fold is more verbose but equivalent — OCaml's pipe operator makes it readable: xs |> List.fold_left (+) 0.

    Full Source

    #![allow(clippy::all)]
    //! 274. Numeric reductions: sum() and product()
    //!
    //! `sum()` and `product()` fold iterators of numbers with + and * respectively.
    //! They're zero-cost abstractions over `fold` with the identity element built in.
    
    /// Sum a slice of integers — idiomatic: let the iterator trait do the work.
    pub fn sum_ints(nums: &[i32]) -> i32 {
        nums.iter().copied().sum()
    }
    
    /// Product of a slice of integers.
    pub fn product_ints(nums: &[i32]) -> i32 {
        nums.iter().copied().product()
    }
    
    /// Factorial via a range product — no explicit identity or accumulator needed.
    pub fn factorial(n: u64) -> u64 {
        (1..=n).product()
    }
    
    /// Sum of squares: map + sum in one expression.
    pub fn sum_of_squares(nums: &[i32]) -> i32 {
        nums.iter().map(|&x| x * x).sum()
    }
    
    /// Average of f64 prices using sum() then divide.
    pub fn average(prices: &[f64]) -> Option<f64> {
        if prices.is_empty() {
            return None;
        }
        let total: f64 = prices.iter().copied().sum();
        Some(total / prices.len() as f64)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum_ints(&[]), 0);
        }
    
        #[test]
        fn test_sum_typical() {
            assert_eq!(sum_ints(&[1, 2, 3, 4, 5]), 15);
        }
    
        #[test]
        fn test_product_empty() {
            // product of empty = multiplicative identity = 1
            assert_eq!(product_ints(&[]), 1);
        }
    
        #[test]
        fn test_product_typical() {
            assert_eq!(product_ints(&[1, 2, 3, 4, 5]), 120);
        }
    
        #[test]
        fn test_factorial_zero() {
            assert_eq!(factorial(0), 1);
        }
    
        #[test]
        fn test_factorial_five() {
            assert_eq!(factorial(5), 120);
        }
    
        #[test]
        fn test_factorial_ten() {
            assert_eq!(factorial(10), 3_628_800);
        }
    
        #[test]
        fn test_sum_of_squares() {
            assert_eq!(sum_of_squares(&[1, 2, 3, 4, 5]), 55);
        }
    
        #[test]
        fn test_average_empty() {
            assert_eq!(average(&[]), None);
        }
    
        #[test]
        fn test_average_prices() {
            let prices = [9.99f64, 14.50, 3.75, 22.00];
            let avg = average(&prices).unwrap();
            assert!((avg - 12.56).abs() < 0.01);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum_ints(&[]), 0);
        }
    
        #[test]
        fn test_sum_typical() {
            assert_eq!(sum_ints(&[1, 2, 3, 4, 5]), 15);
        }
    
        #[test]
        fn test_product_empty() {
            // product of empty = multiplicative identity = 1
            assert_eq!(product_ints(&[]), 1);
        }
    
        #[test]
        fn test_product_typical() {
            assert_eq!(product_ints(&[1, 2, 3, 4, 5]), 120);
        }
    
        #[test]
        fn test_factorial_zero() {
            assert_eq!(factorial(0), 1);
        }
    
        #[test]
        fn test_factorial_five() {
            assert_eq!(factorial(5), 120);
        }
    
        #[test]
        fn test_factorial_ten() {
            assert_eq!(factorial(10), 3_628_800);
        }
    
        #[test]
        fn test_sum_of_squares() {
            assert_eq!(sum_of_squares(&[1, 2, 3, 4, 5]), 55);
        }
    
        #[test]
        fn test_average_empty() {
            assert_eq!(average(&[]), None);
        }
    
        #[test]
        fn test_average_prices() {
            let prices = [9.99f64, 14.50, 3.75, 22.00];
            let avg = average(&prices).unwrap();
            assert!((avg - 12.56).abs() < 0.01);
        }
    }

    Deep Comparison

    OCaml vs Rust: Numeric Reductions — sum() and product()

    Side-by-Side Code

    OCaml

    let sum lst = List.fold_left (+) 0 lst
    let product lst = List.fold_left ( * ) 1 lst
    
    let factorial n = product (List.init n (fun i -> i + 1))
    let sum_squares nums = sum (List.map (fun x -> x * x) nums)
    

    Rust (idiomatic)

    fn sum_ints(nums: &[i32]) -> i32      { nums.iter().copied().sum() }
    fn product_ints(nums: &[i32]) -> i32  { nums.iter().copied().product() }
    fn factorial(n: u64) -> u64           { (1..=n).product() }
    fn sum_of_squares(nums: &[i32]) -> i32 { nums.iter().map(|&x| x * x).sum() }
    

    Rust (explicit fold — shows the identity)

    fn sum_fold(nums: &[i32]) -> i32 {
        nums.iter().copied().fold(0, |acc, x| acc + x)
    }
    
    fn product_fold(nums: &[i32]) -> i32 {
        nums.iter().copied().fold(1, |acc, x| acc * x)
    }
    

    Type Signatures

    ConceptOCamlRust
    Sum functionval sum : int list -> intfn sum_ints(nums: &[i32]) -> i32
    Product functionval product : int list -> intfn product_ints(nums: &[i32]) -> i32
    Fold with identityList.fold_left (+) 0 lstiter.fold(0, \|acc, x\| acc + x)
    Idiomatic shorthand.sum() / .product()
    Generic boundpolymorphic + / *T: Sum / T: Product

    Key Insights

  • Named intent: OCaml requires spelling out List.fold_left (+) 0 every time; Rust's .sum() and .product() name the operation and hide the identity element, making call-sites self-documenting.
  • Zero-cost abstraction: Both sum() and product() compile down to the same machine code as the explicit fold — the abstraction is pure syntax sugar with no runtime overhead.
  • Trait-driven generics: Rust uses the Sum and Product traits, so the same .sum() call works on i32, f64, u64, and even Option<T> — the type is resolved at compile time with no dynamic dispatch.
  • Ranges as iterators: Rust ranges (1..=n) implement Iterator, so (1..=n).product() expresses factorial without building an intermediate list — OCaml needs List.init to create that list first.
  • Empty-collection identity: Both languages agree on the mathematical identity: sum([]) = 0, product([]) = 1. Rust's trait implementations encode this contract; OCaml's fold_left makes it the caller's responsibility to pass the right seed.
  • When to Use Each Style

    **Use .sum() / .product() when:** you want readable, intent-revealing code and the operation is a straightforward numeric reduction — the common case. **Use .fold() when:** the identity or combining function is non-standard (e.g., fold(f64::NEG_INFINITY, f64::max) for max) or when you need to carry extra state alongside the accumulator.

    Exercises

  • Implement harmonic_sum(n: u64) -> f64 = 1 + 1/2 + 1/3 + ... + 1/n using .map() and .sum().
  • Write weighted_average(values: &[f64], weights: &[f64]) -> Option<f64> using zip, map, and sum.
  • Compute the nth Fibonacci number using (1..n).fold() with state (a, b) and compare readability with an explicit loop.
  • Open Source Repos