Jonathan Sterling ⇒ Expressing Church Pairs with Types.

Summary. It’s easy to express pairs in the untyped lambda calculus; adding types makes it somewhat more complex to get a correct encoding. In this article, I explain how we can build a pair with typesafe accessors in a polymorphically typed lambda calculus with type operators (System F$_\omega$), and demonstrate it in Haskell. This document is Literate Haskell: simply copy and paste it into an .lhs file, and you can feed it into GHCI.

In the untyped lambda calculus, a pair (x, y) is encoded as a function which takes two values x, y, yielding a new function that takes an accessor function f as its argument (that is, f selects either the left or the right argument):

$\begin{aligned} \textbf{pair} &:= \lambda xyf. f\,xy\\ \textbf{fst} &:= \lambda p. p\, (\lambda xy. x)\\ \textbf{snd} &:= \lambda p. p\, (\lambda xy. y) \end{aligned}$

So, the following reductions can occur:

$\begin{aligned} \textbf{pair}\; ab &\equiv (\lambda xyf. f\,xy)\; ab\\ &\to \lambda f. f\,ab\\ \textbf{fst}\; (\textbf{pair}\; ab) &\equiv \textbf{fst}\; (\lambda f. f\,ab)\\ &\to (\lambda p. p\, (\lambda xy. x))\; (\lambda f. f\,ab)\\ &\to (\lambda f. f\,ab)\; (\lambda xy. x)\\ &\to (\lambda xy. x)\; ab\\ &\to a\\ \textbf{snd}\; (\textbf{pair}\; ab) &\equiv \textbf{snd}\; (\lambda f. f\,ab)\\ &\to (\lambda p. p\, (\lambda xy. y))\; (\lambda f. f\,ab)\\ &\to (\lambda f. f\,ab)\; (\lambda xy. y)\\ &\to (\lambda xy. y)\; ab\\ &\to b\\ \end{aligned}$

Translating our untyped example into the typed lambda calculus naïvely doesn’t change much of anything. The main features of this new calculus are polymorphic types (α, β, γ…), and type-level functions corresponding to universal quantification. Types are abstracted over using a capital lambda.

$\begin{aligned} \textbf{pair} &:= \Lambda\alpha\beta\gamma. \lambda x^\alpha y^\beta f^{\alpha\to\beta\to\gamma}. f\,xy\\ \textbf{fst} &:= \Lambda\alpha\beta\gamma. \lambda p^{(\alpha\to\beta\to\alpha)\to\gamma}. p (\lambda x^\alpha y^\beta. x)\\ \textbf{snd} &:= \Lambda\alpha\beta\gamma. \lambda p^{(\alpha\to\beta\to\beta)\to\gamma}. p (\lambda x^\alpha y^\beta. y) \end{aligned}$

However, this is problematic. What this definition of pair ends up saying is that for all types α, β, γ, there is a function that will retrieve a value in γ from a pair in (α, β). This, however, is only true inasmuch as γ is either α or β. Thus, our current definition will allow more incorrect programs than is acceptable in a typed (but not dependently-typed) language. What we really need is to constrain γ:

$\begin{aligned} \textbf{pair} &:= \Lambda\alpha\beta. \lambda x^\alpha y^\beta f^{\alpha\to\beta\to(\alpha\lor\beta)}. f\,xy\\ \textbf{fst} &:= \Lambda\alpha\beta. \lambda p^{(\alpha\to\beta\to\alpha)\to\alpha}. p (\lambda x^\alpha y^\beta. x)\\ \textbf{snd} &:= \Lambda\alpha\beta. \lambda p^{(\alpha\to\beta\to\beta)\to\beta}. p (\lambda x^\alpha y^\beta. y) \end{aligned}$

So, we have created a typed encoding of the Church Pair which only allows accessors which return a value of the correct type.

Encoding our typed Church Pair in Haskell

> {-# LANGUAGE MultiParamTypeClasses #-}
> {-# LANGUAGE Rank2Types #-}
> {-# LANGUAGE FlexibleInstances #-}
> {-# LANGUAGE IncoherentInstances #-}
> {-# LANGUAGE UnicodeSyntax #-}
> module ChurchPair where
> import Prelude hiding (fst, snd)

The first order of business will be to implement the disjunction constraint (Λ αβ. α ∨ β) in Haskell. To do this, we can use a type class:

> class TypeOr α β γ

and inhabit it with the proper combinations:

> instance TypeOr α β α
> instance TypeOr α β β

Now, we can create a type synonym (for the sake of convenience) that encapsulates the structure of pairs:

> type Pair α β =  γ. TypeOr α β γ  β  γ)  γ

A Pair over (α, β) is function that takes a function that returns one of its elements. Note that we cannot specify within this non-dependent type system that the result of this accessor actually be one of the elements, but the (TypeOr α β γ) constraint allows us to at least ensure that the result is of the type of either element in the pair.

And now, given two values, we can construct a Pair:

> pair :: α  β  Pair α β
> pair x y f = f x y

The accessors are easily written, and their type annotations make clear their function:

> fst :: Pair α β  α
> fst p = p $ \x y x
> snd :: Pair α β  β
> snd p = p $ \x y y

Incidentally, fst and snd can also be defined in terms of library functions:

> fst' p = p const
> snd' p = p $ flip const

To demonstrate the benefit our typing constraints, try the following:

badAccessor :: Pair α β  String
badAccessor p = p $ \x y "Hello" -- type error

It is impossible to create an accessor of the wrong type (unless the accessor itself is the bottom, in which case you will have a runtime error).

Want to comment?

I’m on Twitter. @jonsterling. By the way, you should try Githood for iOS, a minimalist GitHub client. If you use a Mac, take a look at Desklet , the easiest way to change your desktop background!