Natural transformation

Polymorphism, generic algorithms

Publish at:
 _   _    _  _____ _   _ ____      _    _
| \ | |  / \|_   _| | | |  _ \    / \  | |
|  \| | / _ \ | | | | | | |_) |  / _ \ | |
| |\  |/ ___ \| | | |_| |  _ <  / ___ \| |___
|_|_\_/_/__ \_\_|_ \___/|_| \_\/_/___\_\_____|___  __  __    _  _____ ___ ___  _   _ ____
|_   _|  _ \    / \  | \ | / ___||  ___/ _ \|  _ \|  \/  |  / \|_   _|_ _/ _ \| \ | / ___|
  | | | |_) |  / _ \ |  \| \___ \| |_ | | | | |_) | |\/| | / _ \ | |  | | | | |  \| \___ \
  | | |  _ <  / ___ \| |\  |___) |  _|| |_| |  _ <| |  | |/ ___ \| |  | | |_| | |\  |___) |
  |_| |_| \_\/_/   \_\_| \_|____/|_|   \___/|_| \_\_|  |_/_/   \_\_| |___\___/|_| \_|____/

You might have guessed this one already. As soon as we have some sort of concept, we tend to generalize it by understanding its relation to itself and other objects of the same nature. We categorized morphisms and objects. There is no reason to leave functors behind. Once we have functors mapping between categories, a natural question arises: How do functors relate to each other? If we have two functors F and G both going from category C to category D, what does it mean for them to be "connected" in a meaningful way?

This is where natural transformations come in. They are morphisms between functors—systematic ways to transform one functor into another while respecting the categorical structure. The word "natural" captures something profound about transformations that work "the same way" regardless of the specific objects involved.

Consider a simple programming example: converting a list to its length. This operation works for lists of any type—List<Int>, List<String>, List<Bool>—and the transformation behaves consistently across all types. This consistency is what makes it "natural." In contrast, an operation that behaves differently for different types (like choosing the "first" element based on some arbitrary ordering) would not be natural.

Natural transformations formalize this intuitive notion of "canonical" or "systematic" transformations:

  • Converting between different data representations
  • Extracting information from data structures
  • Composing operations that work across multiple contexts
  • Defining interfaces that work generically across types

Formal definition #

A natural transformation is a way of transforming one functor into another while preserving the structure of the categories involved.

Definition #

Given two functors F and G both mapping from category C to category D:

  • F: C -> D
  • G: C -> D

A natural transformation η: F ⟹ G (read as "eta from F to G") consists of:

  1. Component morphisms: For each object A in category C, there is a morphism η_A: F(A) -> G(A) in category D.

  2. Naturality condition: For every morphism f: A -> B in category C, the following diagram commutes:

F(A) ---F(f)---> F(B)
 |                |
 |η_A             |η_B
 |                |
 ▼                ▼
G(A) ---G(f)---> G(B)

This means: η_B ∘ F(f) = G(f) ∘ η_A

  • Component morphisms (η_A): Think of these as a "recipe" that works for every object. For each object A in the source category, we have a specific morphism η_A that transforms F(A) into G(A).

  • Naturality condition: This is the crucial constraint that makes the transformation "natural." It says that no matter which path you take through the diagram—whether you:

    • Apply F(f) first, then η_B, OR
    • Apply η_A first, then G(f)

...you get the same result.

This consistency across all morphisms is what distinguishes natural transformations from arbitrary collections of morphisms.

Properties #

  1. Identity Natural Transformation: For any functor F: C → D, there exists an identity natural transformation id_F: F ⟹ F where each component is (id_F)_A = id_{F(A)}.

    Just like every object has an identity morphism, every functor has an identity natural transformation that "does nothing" to the functor. Each component simply maps F(A) to itself using the identity morphism. This is like having a function that returns its input unchanged—it satisfies all the requirements of a natural transformation trivially because identity morphisms preserve all structure.

  2. Vertical composition: Given natural transformations η: F ⟹ G and μ: G ⟹ H, their (vertical) composition μ ∘ η: F ⟹ H is defined component-wise: (μ ∘ η)_A = μ_A ∘ η_A.

    You can chain natural transformations together, just like composing functions. If you have a way to transform functor F into G, and another way to transform G into H, you can combine them to get a direct transformation from F to H. At each object A, you simply compose the component morphisms in sequence. The naturality condition ensures this composition remains natural.

  3. Horizontal composition (whiskering): Natural transformations can be composed "horizontally" with functors.

    If you have a natural transformation η: F ⟹ G between functors from C to D, and another functor H: D → E, you can "extend" the natural transformation by applying H to get H ∘ η: H ∘ F ⟹ H ∘ G. Similarly, you can compose with functors on the left. This is called "horizontal" composition because you're composing along the direction of functor composition, allowing natural transformations to interact with the categorical structure in a systematic way.

The naturality condition captures what programmers intuitively mean when they say an operation works "generically" or "polymorphically." It formalizes the idea that the transformation doesn't depend on the specific structure of individual objects, but works uniformly across the entire category.

Examples #

Let's explore natural transformations through concrete examples that demonstrate how they work in practice across different programming languages.

List Length - A Simple Natural Transformation #

One of the most intuitive natural transformations is converting any list to its length. This transformation works uniformly across all list types.

Maybe/Option Head #

(Extracting First Element)

This natural transformation converts any non-empty list to its first element wrapped in a Maybe/Option type.

Flatten #

(From Nested Structure to Flat Structure)

This natural transformation flattens nested structures, demonstrating how natural transformations can work with more complex functors.

Triangle Example #

(Perimeter to √Area Natural Transformation)

Let's illustrate natural transformations with a geometric example involving triangles, similar to how we explored functors.

Consider a restricted category of Triangles whose morphisms are only positive similarity transformations, such as uniform scalings and rigid motions. On this source category, both perimeter and √(area) scale linearly.

Let the target category be the category whose objects are positive real numbers and whose morphisms are scaling maps x ↦ kx for k > 0.

  • Perimeter Functor (Perim): Maps each triangle to its perimeter, and each similarity of ratio k to multiplication by k.
  • Root-Area Functor (RootArea): Maps each triangle to √(Area(T)). Under a similarity of ratio k, area scales by , so √(Area) scales by k as well.

Now, there's a natural transformation η: Perim ⟹ RootArea that relates perimeter to √(area) systematically across all triangles in this restricted category.

For any triangle T, define the component η_T to be "multiply by the constant" √(Area(T)) / Perimeter(T).

where is a square root.

This transformation has the naturality property: when you apply a similarity of ratio k, it doesn't matter whether you:

  1. Apply the perimeter-to-√(area) conversion first, then scale, OR
  2. Scale first, then apply the conversion

Let's verify this with a concrete example:

Original triangle: Vertices at (0,0), (3,0), (0,4) - a right triangle

  • Area: 6 square units
  • Perimeter: 12 units
  • η_T = √6 / 12 ≈ 0.204

After scaling by factor 2: Vertices at (0,0), (6,0), (0,8)

  • Area: 24 square units (scaled by 2² = 4)
  • Perimeter: 24 units (scaled by 2)
  • η_T' = √24 / 24 ≈ 0.204

The ratio remains constant. This demonstrates naturality for similarities: the transformation works consistently because both functors send a similarity of ratio k to the same scaling map.

     Category of Triangles        →    Category of Positive Reals
       (similarities)                        (scalings)

Triangle T ----scale k----> T'        Perim(T) ----×k----> Perim(T')
    |                       |           |                    |
    |η_T                    |η_T'       |η                  |η
    |                       |           |                    |
    ▼                       ▼           ▼                    ▼
Triangle T ----scale k----> T'       √Area(T) ----×k----> √Area(T')

The commutative diagram shows: η_T' ∘ (×k) = (×k) ∘ η_T

This naturality condition captures what we intuitively expect: the relationship between perimeter and √(area) is similarity-invariant. It works uniformly across all triangles as long as the morphisms are restricted to transformations that preserve the common linear scaling behavior of both quantities.

Visualizing natural transformations #

1. Systematic "bridge" between two functors

     Category C                    Category D

    A ----f---> B           F(A) ---F(f)---> F(B)
    │           │             │               │
    │     η     │             │ η_A           │ η_B
    │  bridge   │             │               │
    ▼           ▼             ▼               ▼
    A ----f---> B           G(A) ---G(f)---> G(B)

The η bridge connects every F(A) to G(A) in a way that
"commutes" with all the traffic patterns f in category C.

It's a bridge that respects the traffic patterns (morphisms) of the underlying categories.

2. The Recipe

Raw Ingredients (F)          Prepared Dishes (G)
        ↓          η recipe         ↓
   Tomatoes    --------------->    Tomato Sauce
        │                           │
        │     "dice & simmer"       │ same process
        │                           │
    Onions     --------------->    Onion Sauce

The η recipe (dice & simmer) works for any ingredient,
and combining ingredients works the same way before or
after applying the recipe (naturality condition)

3. The Commutative Square

    F(A) ----F(f)----> F(B)
     │                 │
  η_A│              η_B│
     │                 │
     ▼                 ▼
    G(A) ----G(f)----> G(B)

This means: G(f) ∘ η_A = η_B ∘ F(f)

"map then transform" equals "transform then map"

Conclusion #

naturality = systematic consistency

A natural transformation works the same way everywhere, respecting the categorical structure without making arbitrary choices that depend on specific objects or morphisms.

Think of it as the difference between:

  • Natural: "Take the length of any list" (works uniformly)
  • Unnatural: "Take the 'best' element from any list" (requires arbitrary choices)

Natural transformations formalize what programmers mean when they say an operation works "generically" or "polymorphically."

Source code #

Reference implementation (opens in a new tab)

References

  1. Natural transformation (Wikipedia) (opens in a new tab)