A function type a β b is not just a mapping. It is an object in the category, called the exponential ba. Currying is the adjunction between products and exponentials: (a Γ b) β c β a β (b β c). This isomorphism is why every multi-argument function is secretly a chain of single-argument functions.
Universal construction of function objects
In Set, the set of all functions from a to b is itself a set (the hom-set). In an arbitrary category, there is no guarantee that morphisms between two objects form an object in the same category. The exponential objectba is defined by a universal construction: it comes equipped with an eval morphism from ba Γ a to b, and any other candidate factors through it uniquely.
Scheme
; The universal property of function objects:; For any g : z Γ a β b, there exists a unique; h : z β (a => b) such that eval β (h Γ id) = g;; eval : (a => b) Γ a β b; eval "applies" a function object to an argument.
(define (my-eval func-and-arg)
((car func-and-arg) (cdr func-and-arg)))
; A function object: the set of all functions from Bool to Int; Bool = {#t, #f}, so a function BoolβInt is a pair of Ints.
(define (bool->int-1 b) (if b 1020))
(define (bool->int-2 b) (if b 990))
; eval applies the function to the argument
(display "eval(f1, #t): ") (display (my-eval (cons bool->int-1 #t))) (newline)
(display "eval(f1, #f): ") (display (my-eval (cons bool->int-1 #f))) (newline)
(display "eval(f2, #t): ") (display (my-eval (cons bool->int-2 #t))) (newline)
; The exponential Int^Bool has |Int|^2 inhabitants.; Each function is determined by two choices: what #t maps to, what #f maps to.
Python
# eval: apply a function object to an argumentdef my_eval(pair):
func, arg = pair
return func(arg)
f1 = lambda b: 10if b else20
f2 = lambda b: 99if b else0print(f"eval(f1, True): {my_eval((f1, True))}")
print(f"eval(f1, False): {my_eval((f1, False))}")
print(f"eval(f2, True): {my_eval((f2, True))}")
Currying: the adjunction
Currying is the heart of this chapter. A function of two arguments (a Γ b) β c can always be rewritten as a function that takes one argument and returns a function: a β (b β c). This is a natural isomorphism: the two forms carry the same information.
Scheme
; curry : ((a Γ b) β c) β (a β (b β c)); uncurry : (a β (b β c)) β ((a Γ b) β c);; These are inverses: uncurry(curry(f)) = f, curry(uncurry(g)) = g
(define (my-curry f)
(lambda (a)
(lambda (b)
(f (cons a b)))))
(define (my-uncurry g)
(lambda (pair)
((g (car pair)) (cdr pair))))
; A function on pairs: add two numbers
(define (add-pair p) (+ (car p) (cdr p)))
; Curry it: now takes one arg, returns a function
(define add-curried (my-curry add-pair))
(display "uncurried: ") (display (add-pair (cons 34))) (newline)
(display "curried: ") (display ((add-curried 3) 4)) (newline)
; Round-trip: uncurry(curry(f)) = f
(define add-back (my-uncurry add-curried))
(display "round-trip: ") (display (add-back (cons 34))) (newline)
; Partial application falls out for free
(define add10 (add-curried 10))
(display "add10(5): ") (display (add10 5))
The isomorphism: (a Γ b) β c β a β (b β c)
Curry and uncurry are inverses. This means the set of functions from a product is isomorphic to the set of higher-order functions. In exponential notation: c(aΓb) β (cb)a. This is the familiar law of exponents: xyz = (xy)z. The algebraic identity is not an analogy. It is the same identity, living in the category.
The notation is not metaphorical. For finite sets, the number of functions from a to b is literally |b||a|. A function from a 3-element set to a 2-element set must make 3 independent binary choices, giving 23 = 8 possible functions. The algebraic laws of exponents hold exactly because they are the algebraic laws of function types.
Scheme
; Counting functions between finite types.; |b^a| = |b|^|a|; Bool = {#t, #f} |Bool| = 2; Three = {0, 1, 2} |Three| = 3; All functions Three β Bool: 2^3 = 8; Each function picks a bool for each of 0, 1, 2.
(define (enumerate-three->bool)
(let loop ((i 0) (count 0) (results '()))
(if (>= i 8) (list count results)
(let* ((b0 (if (zero? (modulo i 2)) #f#t))
(b1 (if (zero? (modulo (quotient i 2) 2)) #f#t))
(b2 (if (zero? (modulo (quotient i 4) 2)) #f#t)))
(loop (+ i 1) (+ count 1)
(cons (list b0 b1 b2) results))))))
(define result (enumerate-three->bool))
(display "Number of functions {0,1,2} β Bool: ")
(display (car result)) (newline)
; 8 = 2^3; Algebraic laws:; a^0 = 1 (one function from Void: absurd); 1^a = 1 (one function to Unit: always ()); a^1 = a (functions from Unit β elements of a)
(display "a^0 = 1: unique function from empty set") (newline)
(display "1^a = 1: every input maps to ()") (newline)
(display "a^1 = a: picking a function Unitβa = picking an element of a")
A category where you can always form products and exponentials is called Cartesian closed. It must have three things: a terminal object (the empty product), a product for every pair of objects, and an exponential for every pair of objects. Set is the canonical example. So is every typed lambda calculus. A Cartesian closed category is exactly the categorical semantics of a language with functions and tuples.
Scheme
; Cartesian closed: terminal object + products + exponentials.;; In Set:; Terminal object = {()} (unit / singleton set); Product a Γ b = {(x,y) | x β a, y β b}; Exponential b^a = {f | f : a β b};; The lambda calculus is the internal language of a CCC.; Every lambda term is a morphism in a CCC.; Terminal object: unit
(define unit '())
; Product: pairs
(define (make-pair a b) (cons a b))
; Exponential: lambda; (lambda (x) body) is an element of the exponential b^a; The CCC structure gives us:; 1. Pairing: a β b β a Γ b; 2. Projecting: a Γ b β a, a Γ b β b; 3. Currying: (a Γ b β c) β (a β b β c); 4. Eval: (b^a) Γ a β b
(define (fst p) (car p))
(define (snd p) (cdr p))
(display "fst(3, 7): ") (display (fst (make-pair 37))) (newline)
(display "snd(3, 7): ") (display (snd (make-pair 37))) (newline)
; Eval in a CCC = function application
(display "eval(add1, 5): ") (display (my-eval (cons (lambda (x) (+ x 1)) 5)))
; Every well-typed program in a functional language; is a proof that the corresponding CCC diagram commutes.
Algebraic laws of exponentials
In a bicartesian closed category (one that also has coproducts), the laws of high-school algebra hold for types. Sums are addition, products are multiplication, exponentials are exponentiation, and the distributive law connects them. These are not analogies. They are isomorphisms between types.
Scheme
; The algebra of types:; 0 = Void (initial object / empty type); 1 = Unit (terminal object); a + b = Either a b (coproduct); a Γ b = (a, b) (product); b^a = a β b (exponential);; Laws (all are type isomorphisms):; a^0 = 1 Void β a has exactly one function (absurd); 1^a = 1 a β () has exactly one function (const ()); a^1 = a () β a β picking an element of a; a^(b+c) = a^b Γ a^c Either β a β pair of functions; (a^b)^c = a^(bΓc) currying!; (aΓb)^c = a^c Γ b^c c β (a,b) β pair of functions from c; Let's verify: a^(b+c) = a^b Γ a^c; A function from Either takes a Left or Right,; so it's equivalent to two separate functions.
(define (either-fn e)
(if (eq? (car e) 'left)
(* (cdr e) 2) ; handle Left (numbers)
(string-length (cdr e)))) ; handle Right (strings); Split into two functions:
(define (left-fn n) (* n 2))
(define (right-fn s) (string-length s))
(display "either-fn(Left 5): ") (display (either-fn (cons 'left 5))) (newline)
(display "left-fn(5): ") (display (left-fn 5)) (newline)
(display "either-fn(Right "hi"): ") (display (either-fn (cons 'right "hi"))) (newline)
(display "right-fn("hi"): ") (display (right-fn "hi")) (newline)
; Same results. One function from a sum = two functions.; a^(b+c) = a^b Γ a^c
Python
# a^(b+c) = a^b Γ a^c# A function from a union = a pair of handlers# One function that handles both casesdef either_fn(tag, val):
if tag == "left":
return val * 2returnlen(val)
# Equivalent pair of functions
left_fn = lambda n: n * 2
right_fn = lambda s: len(s)
print(f"either_fn('left', 5): {either_fn('left', 5)}")
print(f"left_fn(5): {left_fn(5)}")
print(f"either_fn('right', 'hi'): {either_fn('right', 'hi')}")
print(f"right_fn('hi'): {right_fn('hi')}")
# (a^b)^c = a^(bΓc) β this is currying!
f_curried = lambda c: lambda b: c + b # (a^b)^c
f_uncurried = lambda pair: pair[0] + pair[1] # a^(bΓc)print(f"curried: {f_curried(10)(3)}")
print(f"uncurried: {f_uncurried((10, 3))}")
Curry-Howard: functions as proofs
Under the Curry-Howard correspondence, the function type a β b is logical implication: "if a then b." The eval morphism proves modus ponens: given (a β b) and a, produce b. Currying proves the deduction theorem: to prove a β (b β c), assume a and b, then prove c. Cartesian closed categories constrain what compositions are physically realizable β type forcing shows how physical constraints collapse Spivak's operad of possible wirings down to the ones that actually typecheck.
Scheme
; Curry-Howard correspondence:; Types β Propositions; Functions β Proofs; a β b β a β b (implication); (a, b) β a β§ b (conjunction); Either a b β a β¨ b (disjunction); Void β β₯ (false); Unit β β€ (true); eval proves modus ponens:; ((a β b) β§ a) β b; "If you have a proof of aβb and a proof of a, you get a proof of b."
(define (modus-ponens proof-and-evidence)
((car proof-and-evidence) (cdr proof-and-evidence)))
; Proof that "even? β not-odd?" (a specific implication)
(define proof-even-implies-not-odd
(lambda (n) (not (odd? n))))
; Apply modus ponens: given the proof and evidence (4 is even)
(display "modus ponens: ")
(display (modus-ponens (cons proof-even-implies-not-odd 4)))
(newline)
; Currying proves the deduction theorem:; To prove a β (b β c), assume a and b, then prove c.; curry : ((a β§ b) β c) β (a β (b β c))
(display "Deduction theorem = currying")
The blog post uses Haskell's native currying (all functions are curried by default) and C++ templates. This page makes currying explicit with my-curry and my-uncurry, since Scheme's lambda can take multiple arguments. The universal construction of exponentials is presented informally here. In the blog post, Milewski gives the full pattern: a candidate is a pair (z, g : z Γ a β b), the ranking says z is better than z' when there exists h : z' β z such that g' = g β (h Γ id), and the exponential ba is the universal (best) such candidate. The eval morphism is not just function application dressed up in categorical language. It is the counit of the product-exponential adjunction, which Chapter 16 covers in full.
Ready for the real thing? Read the blog post. Start with the universal construction, then trace how the exponential laws recover high-school algebra.