ExamplesBy LevelBy TopicLearning Paths
074 Intermediate

074 — Currying and Partial Application (Applied)

Functional Programming

Tutorial

The Problem

This example applies currying and partial application to practical patterns — building factories, greeting generators, and transformation pipelines. Where example 005 introduced the theory, this example shows the patterns in context: make_adder(5) returns a reusable function, apply_twice demonstrates function composition from first principles.

Partial application is ubiquitous in functional programming: event handler factories in UIs, middleware pipelines in web frameworks, predicate factories in query builders, and configuration-bound operations. Understanding how to build and compose these in Rust is essential for ergonomic API design.

🎯 Learning Outcomes

  • • Build function factories with make_adder(n) -> impl Fn(i32) -> i32
  • • Use closures for multi-level currying: function returning function returning value
  • • Apply apply_twice to demonstrate functions as values
  • • Use compose to build pipelines
  • • Understand the move keyword for capturing environment in closures
  • Code Example

    //! 074: Currying and Partial Application
    //!
    //! Rust doesn't curry automatically, so we use closures that capture their
    //! arguments by `move` and return `impl Fn(_) -> _`. Each factory returns a
    //! reusable, pre-configured function — the Rust analogue of OCaml's partial
    //! application.
    
    /// Factory: returns a closure that adds `x` to its argument.
    pub fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
        move |y| x + y
    }
    
    /// Factory: returns a closure that multiplies its argument by `x`.
    pub fn make_multiplier(x: i32) -> impl Fn(i32) -> i32 {
        move |y| x * y
    }
    
    /// Two-level curried greeting builder:
    /// `make_greeting("Hello")("World")("!") == "Hello World!"`.
    pub fn make_greeting(prefix: &str) -> impl Fn(&str) -> Box<dyn Fn(&str) -> String> {
        let prefix = prefix.to_string();
        move |name: &str| {
            let prefix = prefix.clone();
            let name = name.to_string();
            Box::new(move |suffix: &str| format!("{} {}{}", prefix, name, suffix))
        }
    }
    
    /// Apply `f` twice: `f(f(x))`.
    pub fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
        f(f(x))
    }
    
    /// Function composition: returns a closure computing `f(g(x))`.
    pub fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
    where
        F: Fn(B) -> C,
        G: Fn(A) -> B,
    {
        move |x| f(g(x))
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn partial_application_with_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(3), 8);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn partial_application_with_multiplier() {
            let double = make_multiplier(2);
            let triple = make_multiplier(3);
            assert_eq!(double(7), 14);
            assert_eq!(triple(4), 12);
        }
    
        #[test]
        fn multi_level_curried_greeting() {
            let hello = make_greeting("Hello");
            let hello_world = hello("World");
            assert_eq!(hello_world("!"), "Hello World!");
        }
    
        #[test]
        fn apply_twice_reuses_partial_functions() {
            let add5 = make_adder(5);
            assert_eq!(apply_twice(&add5, 0), 10);
            assert_eq!(apply_twice(&add5, 5), 15);
    
            let double = make_multiplier(2);
            assert_eq!(apply_twice(&double, 3), 12);
        }
    
        #[test]
        fn compose_orders_matter() {
            let add5 = make_adder(5);
            let double = make_multiplier(2);
    
            let add5_then_double = compose(make_multiplier(2), make_adder(5));
            assert_eq!(add5_then_double(3), 16); // (3 + 5) * 2
    
            let double_then_add5 = compose(make_adder(5), make_multiplier(2));
            assert_eq!(double_then_add5(3), 11); // 3 * 2 + 5
    
            // Closures remain usable after composition (they're by-reference bounds).
            assert_eq!(add5(1), 6);
            assert_eq!(double(1), 2);
        }
    }

    Key Differences

  • **move requirement**: Rust requires move in the closure to capture x by value. OCaml captures by closure environment automatically (GC-managed). Without move, Rust would borrow x — problematic when the closure outlives the function.
  • **impl Fn return type**: Rust's -> impl Fn(i32) -> i32 return type is required because closures have unique anonymous types. OCaml's fun x -> ... return type is inferred as int -> int.
  • Lifetime: The returned closure borrows nothing from the outer scope (because of move), so it has 'static lifetime. OCaml closures can safely reference the outer scope due to GC.
  • Higher-order composition: compose(f, g) in Rust requires + 'static bounds if stored. OCaml's fun x -> f (g x) composes naturally.
  • OCaml Approach

    OCaml functions are automatically curried, so partial application is natural:

    let make_adder x y = x + y   (* equivalent to: let make_adder x = fun y -> x + y *)
    let add5 = make_adder 5       (* partial application: bind x=5 *)
    let _ = add5 3                (* evaluates to 8 *)
    
    let apply_twice f x = f (f x)
    let compose f g = fun x -> f (g x)
    let double_then_add5 = compose add5 (fun x -> x * 2)
    

    OCaml's implicit currying means every multi-argument function is already a curried function. Partial application is just function application with fewer arguments than the full arity.

    Full Source

    //! 074: Currying and Partial Application
    //!
    //! Rust doesn't curry automatically, so we use closures that capture their
    //! arguments by `move` and return `impl Fn(_) -> _`. Each factory returns a
    //! reusable, pre-configured function — the Rust analogue of OCaml's partial
    //! application.
    
    /// Factory: returns a closure that adds `x` to its argument.
    pub fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
        move |y| x + y
    }
    
    /// Factory: returns a closure that multiplies its argument by `x`.
    pub fn make_multiplier(x: i32) -> impl Fn(i32) -> i32 {
        move |y| x * y
    }
    
    /// Two-level curried greeting builder:
    /// `make_greeting("Hello")("World")("!") == "Hello World!"`.
    pub fn make_greeting(prefix: &str) -> impl Fn(&str) -> Box<dyn Fn(&str) -> String> {
        let prefix = prefix.to_string();
        move |name: &str| {
            let prefix = prefix.clone();
            let name = name.to_string();
            Box::new(move |suffix: &str| format!("{} {}{}", prefix, name, suffix))
        }
    }
    
    /// Apply `f` twice: `f(f(x))`.
    pub fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
        f(f(x))
    }
    
    /// Function composition: returns a closure computing `f(g(x))`.
    pub fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
    where
        F: Fn(B) -> C,
        G: Fn(A) -> B,
    {
        move |x| f(g(x))
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn partial_application_with_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(3), 8);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn partial_application_with_multiplier() {
            let double = make_multiplier(2);
            let triple = make_multiplier(3);
            assert_eq!(double(7), 14);
            assert_eq!(triple(4), 12);
        }
    
        #[test]
        fn multi_level_curried_greeting() {
            let hello = make_greeting("Hello");
            let hello_world = hello("World");
            assert_eq!(hello_world("!"), "Hello World!");
        }
    
        #[test]
        fn apply_twice_reuses_partial_functions() {
            let add5 = make_adder(5);
            assert_eq!(apply_twice(&add5, 0), 10);
            assert_eq!(apply_twice(&add5, 5), 15);
    
            let double = make_multiplier(2);
            assert_eq!(apply_twice(&double, 3), 12);
        }
    
        #[test]
        fn compose_orders_matter() {
            let add5 = make_adder(5);
            let double = make_multiplier(2);
    
            let add5_then_double = compose(make_multiplier(2), make_adder(5));
            assert_eq!(add5_then_double(3), 16); // (3 + 5) * 2
    
            let double_then_add5 = compose(make_adder(5), make_multiplier(2));
            assert_eq!(double_then_add5(3), 11); // 3 * 2 + 5
    
            // Closures remain usable after composition (they're by-reference bounds).
            assert_eq!(add5(1), 6);
            assert_eq!(double(1), 2);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn partial_application_with_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(3), 8);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn partial_application_with_multiplier() {
            let double = make_multiplier(2);
            let triple = make_multiplier(3);
            assert_eq!(double(7), 14);
            assert_eq!(triple(4), 12);
        }
    
        #[test]
        fn multi_level_curried_greeting() {
            let hello = make_greeting("Hello");
            let hello_world = hello("World");
            assert_eq!(hello_world("!"), "Hello World!");
        }
    
        #[test]
        fn apply_twice_reuses_partial_functions() {
            let add5 = make_adder(5);
            assert_eq!(apply_twice(&add5, 0), 10);
            assert_eq!(apply_twice(&add5, 5), 15);
    
            let double = make_multiplier(2);
            assert_eq!(apply_twice(&double, 3), 12);
        }
    
        #[test]
        fn compose_orders_matter() {
            let add5 = make_adder(5);
            let double = make_multiplier(2);
    
            let add5_then_double = compose(make_multiplier(2), make_adder(5));
            assert_eq!(add5_then_double(3), 16); // (3 + 5) * 2
    
            let double_then_add5 = compose(make_adder(5), make_multiplier(2));
            assert_eq!(double_then_add5(3), 11); // 3 * 2 + 5
    
            // Closures remain usable after composition (they're by-reference bounds).
            assert_eq!(add5(1), 6);
            assert_eq!(double(1), 2);
        }
    }

    Deep Comparison

    Core Insight

    In OCaml, let add x y = x + y is actually let add = fun x -> fun y -> x + y. Partial application (add 5) returns a function. Rust functions aren't curried — you use closures to simulate partial application.

    OCaml Approach

  • • All functions are automatically curried
  • let add x y = x + yadd 5 returns fun y -> 5 + y
  • • Free partial application of any prefix of arguments
  • Rust Approach

  • • Functions are NOT curried
  • • Closures capture variables: let add5 = |y| 5 + y;
  • move closures for ownership transfer
  • • Can return closures with impl Fn(T) -> U
  • Comparison Table

    FeatureOCamlRust
    Curried by defaultYesNo
    Partial applicationf x (give fewer args)Closure capturing
    Return functionAutomaticimpl Fn(T) -> U
    Closure syntaxfun x -> ...\|x\| ...

    Exercises

  • Tax calculator: Write make_tax_calculator(rate: f64) -> impl Fn(f64) -> f64 and make_discount(pct: f64) -> impl Fn(f64) -> f64. Compose them into a calculate_price pipeline.
  • Memoized factory: Write memoized_make_adder(cache: &mut HashMap<i32, Box<dyn Fn(i32) -> i32>>, n: i32) -> &Box<dyn Fn(i32) -> i32> that caches the created adder function.
  • Pipeline DSL: Using compose, build a pipeline for data transformation: trim -> lowercase -> split_words -> filter_short_words -> join_with_comma. Each step is a partial application of a generic combinator.
  • Open Source Repos