Type Isomorphism

Type isomorphisms are a general notion of conversion between types. We say that type A and B are isomorphic, if we have conversion functions f :: A -> B and g :: B -> A such that

f . g = idB
g . f = idA

Type isomorphisms imply that we can safely convert without loss of information between types.

Motivation

There always exist multiple types that can represent the same values we want to represent. The problem is that they are not compatible because the type system does not automatically recognize them as equal. So we might not be able to reuse the existing libraries as the types we use in our program are not compatible with the types these libraries use. Type isomorphisms provide wrapping/unwrapping functions that can safely convert between these types.

Type isomorphisms also help us understand various transformations used in equational reasoning and API design.

Basics

Haskell programmers already use the type isomorphism to reason about programs. For example, we know that pair (a,b) is isomorphic to (b,a) because swap is the conversion function in both directions.

swap :: (a,b) -> (b,a)
swap (a, b) = (b, a)

Another example of type isomorphism is a and () -> a. We can define conversion functions as follows:

f :: a -> () -> a
f = \x _ -> x
g :: (() -> a) -> a
g k = k ()

What about a -> ()? This type is unsurprisingly isomorphic to () because a -> () type has only one inhabitant which discards the argument and returns (). Conversion functions are:

f :: (a -> ()) -> ()
f _ = ()
g :: () -> a -> ()
g = \_ _ -> ()

Currying and uncurrying

In functional programming, currying transforms a function that takes multiple arguments via a pair, into a function that accepts the first argument of the pair, and returns a function that accepts the second argument, before returning the result. uncurrying performs transformation in the opposite direction.

Algebraic data types

Every algebraic data type can be represented with combinations of product and sum types. This is why these types are called algebraic data types.

In Haskell, products are encoded by (a, b) and sums are encoded by Either a b. Thus an algebraic data type of Haskell is isomorphic to some combinations of (a, b) and Either a b. Let’s see a few examples.

Bool is isomorphic to Either () () because we can define conversion functions f and g: