Formalization of Reynolds's parametricity theorem in Coq

Posted on April 3, 2019

(Actually, this was posted on May 3. I’m leaving this here to not kill existing links.)

I wrote a little formalization of parametricity in Coq.

One of my motivations was to experiment with dependent types in the formalization of a programming language. I was pretty convinced that it is a nice way of doing such things, but I had actually never tried it. System F is particularly interesting in this regard, because terms can abstract over both types and terms, which makes contexts rather richly structured (and a system with kinds or dependent types would go even further).

Well-typed by construction

Whereas a traditional formalization of programming languages would define separate grammars for types and terms, and relate them via logical predicates…

Inductive ty : Type := (* ... *) . Inductive tm : Type := (* ... *) . Definition well_typed : ty -> tm -> Prop .

… we can instead parameterize each syntactic category (types and terms) by its contexts, thus obviating the need for separate predicates for typing judgements. Terms are well-typed by construction.

Inductive ty ( n : nat ) : Type := (* ... *) . Inductive tm ( n : nat ) ( vs : list ( ty n )) : ty n -> Type := (* ... *) .

ty n contains types with n free type variables, given as indices from 0 to n-1 .

tm n vs contains terms with n free type variables, and with free term variables whose types are given by the list vs .

In other words, a term encodes not only a term, but actually the full derivation of a typing judgement α1 ... αn ; x1:t1 ... xm:tm ⊢ u : t , with type variables αi , and term variables xi each of some type ti .

Thanks to that, denotational semantics can be defined as actual functions in Coq, with pretty straightforward definitions. No proof terms to decompose or absurd cases to prove impossible, because they simply don’t exist.

(** Semantics of terms of type [t] as Coq values *) Definition eval_tm0 { t : ty 0 } : tm0 t -> eval_ty0 t .

I really like this approach. Everything is tightly coupled, which means the type checker will be in the way until every piece is right. This has its pros and cons. In order to not get stuck on every small detail, it is quite useful to postpone them into small unimplemented auxiliary functions. An axiom TODO : forall a, a. is especially useful to type check some branches of match without having to implement all of them.

In the end I think it works well for self-contained projects like this one, where I already had a precise mental picture of what I wanted, but such heavily dependently-typed programming definitely has a steep learning curve. Things might be easier in other systems than Coq (Agda, Idris?), with better environments for type-driven programming (I haven’t tried).

Actually, I believe one can be quite proficient at formalizing things in Coq without knowing anything about dependent types. A lot of work can be done by writing implementations (“stuff that computes”) only in a fragment of Coq without dependent types (as a variant of ML), and while typical specifications (“stuff in Prop ”) are technically dependent types, the details are quite well hidden behind the tactic system. So I think it’s worth pointing out that, by those standards, the heavy use of dependent types in Coq makes this project quite an outlier.

Dependently-typed structures

The whole project relies on native dependent pattern-matching, without help from Program or Equations, and for that to work well, indexed types are to be used very sparely.

For example, the type of length-indexed lists I use is a function nat -> Type which unfolds to a sequence of nested pairs:

(** Length-indexed lists (aka "vectors") *) Fixpoint lilist ( A : Type ) ( n : nat ) : Type := match n with | O => unit | S n => A * lilist A n end .

In contrast, the standard library defines it naively as an indexed type (named Vector ):

Inductive lilist ( A : Type ) : nat -> Type := | nil : lilist A O | cons { n : nat } : A -> lilist A n -> lilist A ( S n ) .

Basically, the issue is that with indexed types, the information you get by pattern-matching “flows” from the indexed type to its indices, but in the case of structures like lists you often want things to flow in the opposite way. This makes pattern-matching awkward, for example, when multiple types depend on the same index (e.g., lists of the same length), or when the index is meant to reflect partial information about the indexed type (e.g., non-empty lists, with length S n ).

This experience with length-indexed lists suggests a more general pattern, as an alternative to indexed types in various situations, which is strongly reminiscent of ornaments.

The only indexed type in this project is the type of terms, which is indexed by their type. Intuitively, this might work because the type system is syntax-directed.

Parametricity

Of course, another motivation for this project was to get a better grasp on the parametricity theorem and the idea of Theorems for free! by Philip Wadler, to apply parametricity to prove things about specific programs.

A noteworthy detail here is that the interpretation of types as relations cannot be done in naive set theory . But in Coq (a type theory, I guess?), a naive formulation is happily accepted. Having mechanized the result serves to confirm my high-level intuition of it, even if I’m not entirely familiar with those more foundational details.

The next thing I want to look at now is the question of parametricity for functors. There is a folklore result (maybe to be found in one of Janis Voigtländer’s papers) that parametricity implies in particular that polymorphic functions forall a. f a -> g a , with functors f and g , are natural transformations. But if we define “functor” as a synonym for “lawful definition of fmap :: forall a b. (a -> b) -> (f a -> f b) ”, I can’t see a straightforward connection to parametricity, as opposed to a different definition in terms of polarity, “ a occurs only positively in f a ”. I can imagine that the latter implies the former, but the converse might not be provable.