ExamplesBy LevelBy TopicLearning Paths
051 Intermediate

051 — Applying a Function Twice

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "051 — Applying a Function Twice" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Applying a function twice — `f(f(x))` — is a simple but illuminating exercise in higher-order functions and partial application. Key difference from OCaml: 1. **Automatic currying**: OCaml's `twice f x` is automatically `twice f` (partial application). Rust needs `twice_partial(f)` as a separate function returning a closure.

Tutorial

The Problem

Applying a function twice — f(f(x)) — is a simple but illuminating exercise in higher-order functions and partial application. It demonstrates that functions are first-class values: twice takes a function as a parameter and returns a value (or a new function via partial application). This is the simplest non-trivial higher-order function, used as an introduction in OCaml's CS3110 course at Cornell.

The twice combinator generalizes to apply_n_times(f, n, x) which applies f exactly n times — the basis for iterative computation in functional languages, Church numerals (see example 098), and fixed-point iteration in numerical methods.

🎯 Learning Outcomes

  • • Write a function that takes another function as a parameter
  • • Implement both twice(f, x) -> T and the partially applied twice_partial(f) -> Fn(T) -> T
  • • Use impl Fn(T) -> T as a generic function type
  • • Understand the difference between Fn, FnMut, and FnOnce for this use case
  • • Demonstrate that function-returning functions (curried style) work in Rust via closures
  • • Use F: Fn(T) -> T (not FnOnce) as the trait bound since the function is called twice in twice(f, x)
  • • Return impl Fn(T) -> T from twice_partial(f) using move |x| f(f(x))
  • Code Example

    pub fn twice<T, F>(f: F, x: T) -> T
    where
        F: Fn(T) -> T,
    {
        f(f(x))
    }
    
    // Usage (both arguments supplied):
    twice(double, 3)   // 12
    twice(square, 2)   // 16

    Key Differences

  • Automatic currying: OCaml's twice f x is automatically twice f (partial application). Rust needs twice_partial(f) as a separate function returning a closure.
  • **Fn vs FnOnce**: Rust distinguishes single-use (FnOnce) and multi-use (Fn) closures. Calling f twice requires F: Fn(T) -> T. OCaml has no such distinction.
  • Type inference: Both infer T from the function's return type. Rust infers the closure type from how f is called.
  • **move closure**: twice_partial captures f by move into the returned closure. The move keyword transfers ownership of f into the closure.
  • **Fn vs FnOnce:** twice requires F: Fn(T) -> T (not FnOnce) because it calls f twice. FnOnce can only be called once — using it here would cause a compile error on the second call.
  • **Return type impl Fn:** twice_partial returns impl Fn(T) -> T. The concrete closure type is unnameable, so impl Fn is used. In a trait object context, Box<dyn Fn(T) -> T> would be needed.
  • Church numerals: twice applied to itself computes 4 times: twice(twice, f) applies f four times. This is the basis of Church numerals: 0 = id, 1 = once, 2 = twice, n = apply_n.
  • **OCaml's @@:** OCaml's function application operator @@ reads as f @@ x instead of f x. twice f @@ x applies twice f to x. Rust has no equivalent — use f(x) or the explicit pipe function.
  • OCaml Approach

    OCaml's version from CS3110: let twice f x = f (f x). This is automatically curried: let quad = twice double — partially applies twice with double, producing a function int -> int that quadruples its argument. let apply_n_times f n x = if n = 0 then x else apply_n_times f (n-1) (f x) generalizes to n applications.

    Full Source

    #![allow(clippy::all)]
    //! # Applying a Function Twice
    //! OCaml CS3110 — Higher-order function that applies a function twice,
    //! demonstrating currying and partial application.
    
    // ── Implementation 1: Idiomatic Rust ────────────────────────────────────────
    //
    // Generic over the argument type T and any function F that maps T → T.
    // `F: Fn(T) -> T` means f can be called repeatedly (shared reference semantics).
    // Ownership: x is moved in, the intermediate result of f(x) is moved into f(…).
    
    /// Apply `f` to `x` twice: `f(f(x))`.
    pub fn twice<T, F>(f: F, x: T) -> T
    where
        F: Fn(T) -> T,
    {
        f(f(x))
    }
    
    // ── Implementation 2: Partial application (curried style) ────────────────────
    //
    // Returns a closure that captures `f` by move.
    // This matches OCaml's `let quad = twice double` — bind the function,
    // get back a new function waiting for the argument.
    //
    // `impl Fn(T) -> T` in return position: the compiler knows the concrete
    // closure type; we hide it behind `impl Trait` so callers stay generic.
    
    /// Partially apply `twice`: bind `f`, return a new `Fn(T) -> T`.
    ///
    /// Example: `let quad = twice_partial(double);` — then `quad(3) == 12`.
    pub fn twice_partial<T, F>(f: F) -> impl Fn(T) -> T
    where
        F: Fn(T) -> T,
    {
        // `f` is moved into the closure.  Because F: Fn (not FnOnce), calling
        // the closure multiple times is safe — f itself is never consumed.
        move |x| f(f(x))
    }
    
    // ── Implementation 3: Function-pointer variant ───────────────────────────────
    //
    // `fn(i32) -> i32` is a bare function pointer, not a closure.
    // This is less general (no captured environment) but zero-overhead and
    // useful when you only need named free functions like `double` / `square`.
    
    /// Apply a bare function pointer twice (no closure capture).
    pub fn twice_fp(f: fn(i32) -> i32, x: i32) -> i32 {
        f(f(x))
    }
    
    // ── Named helpers matching the OCaml originals ───────────────────────────────
    
    pub fn double(x: i32) -> i32 {
        2 * x
    }
    
    pub fn square(x: i32) -> i32 {
        x * x
    }
    
    // ── Tests ────────────────────────────────────────────────────────────────────
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        // --- twice (generic) ---
    
        #[test]
        fn test_twice_double() {
            // double(double(3)) = double(6) = 12
            assert_eq!(twice(double, 3), 12);
        }
    
        #[test]
        fn test_twice_square() {
            // square(square(2)) = square(4) = 16
            assert_eq!(twice(square, 2), 16);
        }
    
        #[test]
        fn test_twice_with_closure() {
            // Works with any Fn(T) -> T, including closures
            let increment = |x: i32| x + 1;
            assert_eq!(twice(increment, 5), 7);
        }
    
        #[test]
        fn test_twice_identity() {
            assert_eq!(twice(|x: i32| x, 42), 42);
        }
    
        // --- twice_partial (curried / partial application) ---
    
        #[test]
        fn test_partial_quad() {
            // quad = twice double — bind double, get a new function
            let quad = twice_partial(double);
            assert_eq!(quad(3), 12);
            assert_eq!(quad(0), 0);
            assert_eq!(quad(-1), -4);
        }
    
        #[test]
        fn test_partial_fourth() {
            // fourth = twice square — x^4
            let fourth = twice_partial(square);
            assert_eq!(fourth(2), 16);
            assert_eq!(fourth(3), 81);
        }
    
        #[test]
        fn test_partial_reusable() {
            // The returned closure can be called multiple times
            let quad = twice_partial(|x: i32| 2 * x);
            let results: Vec<i32> = (1..=4).map(|x| quad(x)).collect();
            assert_eq!(results, vec![4, 8, 12, 16]);
        }
    
        // --- twice_fp (function pointer) ---
    
        #[test]
        fn test_fp_double() {
            assert_eq!(twice_fp(double, 3), 12);
        }
    
        #[test]
        fn test_fp_square() {
            assert_eq!(twice_fp(square, 2), 16);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        // --- twice (generic) ---
    
        #[test]
        fn test_twice_double() {
            // double(double(3)) = double(6) = 12
            assert_eq!(twice(double, 3), 12);
        }
    
        #[test]
        fn test_twice_square() {
            // square(square(2)) = square(4) = 16
            assert_eq!(twice(square, 2), 16);
        }
    
        #[test]
        fn test_twice_with_closure() {
            // Works with any Fn(T) -> T, including closures
            let increment = |x: i32| x + 1;
            assert_eq!(twice(increment, 5), 7);
        }
    
        #[test]
        fn test_twice_identity() {
            assert_eq!(twice(|x: i32| x, 42), 42);
        }
    
        // --- twice_partial (curried / partial application) ---
    
        #[test]
        fn test_partial_quad() {
            // quad = twice double — bind double, get a new function
            let quad = twice_partial(double);
            assert_eq!(quad(3), 12);
            assert_eq!(quad(0), 0);
            assert_eq!(quad(-1), -4);
        }
    
        #[test]
        fn test_partial_fourth() {
            // fourth = twice square — x^4
            let fourth = twice_partial(square);
            assert_eq!(fourth(2), 16);
            assert_eq!(fourth(3), 81);
        }
    
        #[test]
        fn test_partial_reusable() {
            // The returned closure can be called multiple times
            let quad = twice_partial(|x: i32| 2 * x);
            let results: Vec<i32> = (1..=4).map(|x| quad(x)).collect();
            assert_eq!(results, vec![4, 8, 12, 16]);
        }
    
        // --- twice_fp (function pointer) ---
    
        #[test]
        fn test_fp_double() {
            assert_eq!(twice_fp(double, 3), 12);
        }
    
        #[test]
        fn test_fp_square() {
            assert_eq!(twice_fp(square, 2), 16);
        }
    }

    Deep Comparison

    Side-by-Side: Applying a Function Twice

    OCaml

    let twice f x = f (f x)
    
    let double x = 2 * x
    let square x = x * x
    
    let quad   = twice double   (* partial application — no argument given *)
    let fourth = twice square
    
    let () =
      Printf.printf "quad 3   = %d\n" (quad 3);    (* 12 *)
      Printf.printf "fourth 2 = %d\n" (fourth 2)   (* 16 *)
    

    Rust — Implementation 1: Generic (direct form)

    pub fn twice<T, F>(f: F, x: T) -> T
    where
        F: Fn(T) -> T,
    {
        f(f(x))
    }
    
    // Usage (both arguments supplied):
    twice(double, 3)   // 12
    twice(square, 2)   // 16
    

    Rust — Implementation 2: Partial application (curried style)

    pub fn twice_partial<T, F>(f: F) -> impl Fn(T) -> T
    where
        F: Fn(T) -> T,
    {
        move |x| f(f(x))  // f captured by move into the returned closure
    }
    
    // Usage — mirrors OCaml exactly:
    let quad   = twice_partial(double);   // quad: impl Fn(i32) -> i32
    let fourth = twice_partial(square);
    
    quad(3)     // 12
    fourth(2)   // 16
    

    Rust — Implementation 3: Function pointer variant

    pub fn twice_fp(f: fn(i32) -> i32, x: i32) -> i32 {
        f(f(x))
    }
    
    // fn(i32) -> i32 — bare function pointer, no captured environment.
    twice_fp(double, 3)   // 12
    twice_fp(square, 2)   // 16
    

    Concept Map

    OCaml conceptRust equivalent
    Curried function ('a -> 'a) -> 'a -> 'aTwo-argument generic fn or impl Fn return
    let quad = twice doublelet quad = twice_partial(double)
    Polymorphic 'aGeneric type parameter T
    All functions implicitly curriedClosures capture by move; explicit impl Fn(T) -> T
    Function value (no distinction)Fn trait (closure) vs fn pointer

    Why Fn and not FnOnce?

    FnOnce means the closure can only be called once (it consumes captured values). Here f is called twice inside the body, so it must be Fn (callable by shared reference, any number of times). FnMut would also work but is unnecessarily broad when no mutation is needed.

    Ownership note on twice_partial

    // f is moved into the returned closure.
    // The closure itself is Fn because F: Fn — it can be called many times.
    // Each call to the returned closure borrows f immutably to invoke f(f(x)).
    move |x| f(f(x))
    

    The intermediate value f(x): T is owned by the stack frame and then moved into the second call f(…). No heap allocation is required.

    Exercises

  • Apply n times: Write apply_n<T: Clone, F: Fn(T) -> T>(f: F, n: usize, x: T) -> T that applies f exactly n times. Handle n=0 (return x unchanged).
  • Fixed point: Write fixed_point<T: Clone + PartialEq, F: Fn(T) -> T>(f: F, x: T) -> T that applies f repeatedly until f(x) == x (convergence). Use this to compute square roots by Newton's method.
  • Compose from twice: Express compose(f, g) in terms of twice — or explain why it cannot be expressed that way without additional combinators.
  • Apply N times: Implement apply_n<T>(f: impl Fn(T) -> T, n: usize, x: T) -> T that applies f exactly n times. For n=0, return x. For n=1, return f(x). Connect this to Church numeral representation.
  • Fixed point: Implement fixed_point<T: PartialEq + Clone>(f: impl Fn(T) -> T, x: T, max_iter: usize) -> Option<T> that keeps applying f until the output equals the input (or exceeds max_iter). Useful for iterative numerical methods.
  • Open Source Repos