Try implementing the n-ary tuple constructor. I will also strive for simple implementation, which means that I try to avoid natural numbers and tuples and try to avoid using other (Church-related) data structures.
My strategy is this:
- Write a well-typed version of a function in a dependent language.
- Translate it to the untyped calculus of lambda.
The reason for this is that I quickly get lost in the untyped calculus of lambda, and I have to make a lot of mistakes along the way, while the dependent typed environment puts me on the rails. In addition, troubleshooting assistants are just a great help for writing any code.
Step one
I am using Agda. I'm a little cheating on type-in-type . This makes Agda inconsistent, but suitable types of universes will be a huge pain for this problem, and it is very unlikely that we will encounter inconsistency anyway.
{-# OPTIONS --type-in-type #-} open import Data.Nat open import Data.Vec
We need the concept of n-ary polymorphic functions. We store argument types in a vector of length n :
NFun : ∀ {n} → Vec Set n → Set → Set NFun [] r = r NFun (x ∷ ts) r = x → NFun ts r -- for example, NFun (Nat ∷ Nat ∷ []) = λ r → Nat → Nat → r
We have the usual church encoding for tuples. Constructors for n-ary sets are n-ary functions that return a set.
NTup : ∀ {n} → Vec Set n → Set NTup ts = ∀ {r} → NFun ts r → r NTupCons : ℕ → Set NTupCons n = ∀ ts → NFun {n} ts (NTup ts)
We would like to have a function of type ∀ {n} → NTupCons n . We return the Vec Set n parameter for the tuple constructor. The empty case is simple enough, but the case with the ends is a bit more complicated:
nTupCons : ∀ {n} → NTupCons n nTupCons [] x = x nTupCons (t ∷ ts) x = ?
We need a NFun ts (NTup (t ∷ ts)) instead of a question mark. We know that nTupCons ts is of type NFun ts (NTup ts) , so we need to somehow get the first from the last. We notice that we only need the n-ary composition of the function, or, in other words, the functorial mapping over the returned type of NFun :
compN : ∀ {n AB} (ts : Vec Set n) → (A → B) → NFun ts A → NFun ts B compN [] f = f compN (t ∷ ts) fgx = compN ts f (gx)
Now we need to get NTup (t ∷ ts) from NTup ts , and since we already have x with type t in scope, it's pretty simple:
nTupCons : ∀ {n} → NTupCons n nTupCons [] x = x nTupCons (t ∷ ts) x = compN ts consTup (nTupCons ts) where consTup : NTup ts → NTup (t ∷ ts) consTup tup con = tup (con x)
Second step
We will get rid of Vec Set n -s and rewrite the functions so that they iterate over the parameters n . However, a simple iteration is not suitable for nTupCons , since it gives us a recursive result ( nTupCons ts ), but we also need the current index n for compN (since we implement compN by iterating over n ). Therefore, we are writing an assistant that is a bit like paramorphism . We also need pairs encoded in churches to pass Nat -s through iteration:
zero = λ z s. z suc = λ nz s. s (nzs) fst = λ p. p (λ a b. a) snd = λ p. p (λ a b. b)
The rest can be easily translated:
compN = λ n. n (λ f. f) (λ hyp fg x. hyp f (gx)) nTupCon = rec-with-n (λ x. x) (λ hyp n. λ x. compN n (λ f g. f (gx)) hyp)
Test it for simple cases:
nTupCon zero = (λ t. t) nTupCon (suc zero) = (λ hyp n. λ x. compN n (λ f g. f (gx)) hyp) (nTupCon zero) zero = λ x. compN zero (λ f g. f (gx)) (λ t. t) = λ x. (λ f g. f (gx)) (λ t. t) = λ x. λ g. (λ t. t) (gx) = λ x . λ g. gx = λ xg . gx nTupCon (suc (suc zero)) = (λ hyp n. λ x. compN n (λ f g. f (gx)) hyp) (nTupCon (suc zero)) (suc zero) = λ x. compN (suc zero) (λ f g. f (gx)) (λ a t. ta) = λ x a. (λ f g. f (gx)) ((λ y t. ty) a) = λ x a. (λ f g. f (gx)) (λ t. ta) = λ xa g. (λ t. ta) (gx) = λ xa g. gxa
This seems to work.