Dec Code Story

Chapter #4 – Functions as Values

pure functions, first-class functions, composition, currying and partial application

4.0  Prologue

In declarative languages, functions are values like any other: they can be stored in variables, passed as arguments, returned from other functions, and placed in data structures. This property, called first-class functions, enables a style of programming where behavior is composed from small, named, reusable pieces rather than hard-coded into control-flow structures.

4.1  Pure Functions

A pure function depends only on its arguments and returns the same result for the same inputs with no observable side effects. It is the building block of declarative composition. -- Haskell: all functions are pure unless they return IO double :: Int -> Int double x = x * 2 clamp :: Ord a => a -> a -> a -> a clamp lo hi x = max lo (min hi x) -- Rust: pure functions (no mut, no I/O, no unsafe) fn double(x: i32) -> i32 { x * 2 } fn clamp(lo: f64, hi: f64, x: f64) -> f64 { lo.max(hi.min(x)) } Pure functions are trivially testable (no setup or teardown), safely memoizable, and trivially parallelizable (no shared state to synchronize).

4.2  First-Class and Higher-Order Functions

First-class functions can appear anywhere a value can appear. Higher-order functions take functions as arguments or return them as results. -- Haskell: applyTwice takes a function and a value applyTwice :: (a -> a) -> a -> a applyTwice f x = f (f x) applyTwice double 3 -- 12 applyTwice (+3) 10 -- 16 -- F#: function stored in a list let transforms = [ (*2); (+10); negate ] transforms |> List.map (fun f -> f 5) // [10; 15; -5] -- Rust: function pointers and closures as arguments fn apply_twice(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(f(x)) } apply_twice(|x| x * 2, 3) // 12

4.3  Function Composition

Composition builds a new function from two existing ones by piping the output of the first into the input of the second. It is the declarative substitute for a sequence of variable assignments. -- Haskell: (.) is the composition operator -- (f . g) x = f (g x) normalize :: String -> String normalize = trim . toLower . removeAccents -- Applied: normalize " Café " = "cafe" -- F#: (|>) pipes left-to-right; (>>) composes left-to-right let normalize = removeAccents >> toLower >> trim " Café " |> normalize // "cafe" -- Rust: iterator chaining is functional composition let result: Vec<String> = words .iter() .map(|w| w.to_lowercase()) .filter(|w| !w.is_empty()) .collect(); -- Python: functools.reduce for composition from functools import reduce normalize = reduce(lambda f, g: lambda x: g(f(x)), [remove_accents, str.lower, str.strip]) Composition makes the pipeline explicit and linear: each stage’s name describes exactly what it does to the data. There are no intermediate variable names obscuring the transformation sequence.

4.4  Currying and Partial Application

Currying transforms a multi-argument function into a chain of single-argument functions. Applying a curried function to fewer arguments than it requires returns a new function that expects the rest - this is partial application. -- Haskell: all functions are curried by default add :: Int -> Int -> Int add x y = x + y add5 :: Int -> Int add5 = add 5 -- partial application; add5 y = add 5 y map (add 10) [1,2,3] -- [11,12,13] -- F#: curried by default let add x y = x + y let add5 = add 5 [1;2;3] |> List.map add5 // [6;7;8] -- Rust: partial application via closures (no built-in currying) let add = |x: i32| move |y: i32| x + y; let add5 = add(5); vec![1,2,3].iter().map(|&y| add5(y)).collect::<Vec<_>>() -- Python: functools.partial from functools import partial add5 = partial(lambda x, y: x + y, 5) list(map(add5, [1, 2, 3])) # [6, 7, 8] Currying and partial application eliminate the need for many lambda wrappers. Instead of map (fun x -> add 5 x), you write map (add 5). This is the foundation of point-free style, covered in Chapter 6.

4.5  Epilogue

Treating functions as values transforms a program from a collection of procedures into an algebra of transformations. Composition replaces sequencing; partial application replaces boilerplate wrappers; pure functions replace stateful methods. The next chapter shows how recursion replaces loops as the mechanism for repetition.

4.6  References

Currying - Wikipedia
Haskell Functions - Tutorial
F# Functions - Microsoft
Python functools.partial