jcreed blog > Depending on Category Variables, Dependently
Depending on Category Variables, Dependently
I'm still grinding through a lot of details, but I wanted to
talk a little bit about a small idea that popped out at me,
which seems to be important to making things work nicely with both
variables whose 'type' is a category, and types depending on ordinary term variables.
Starting from the Non-dependent Case
How I arrived at the
idea was first saying to myself, suppose you write down
something syntactic like
\[ \alpha + \C ; \cdot \prov B : \rtype \]
where $\alpha + \C$ means $\alpha$ is a variable 'varying over' the category $\C$,
and the $+$ in place of the $:$ emphasizes that you're depending on it covariantly.
Something this simple, where $B$ doesn't depend on any other variables, surely it should
carry the interpretation that $B$ is a functor from $\C$ to $\rset$. Maybe we write $B_c$ for the
type that you get from applying that functor to an object $c\in \C$, and $B_f : B_c \to B_d$
for the functorial action on a morphism $f : c \to d$.
Depending on One Term Variable
What next? Let's try to add a term variable that the type depends on.
\[ \alpha + \C ; x : A \prov B(x) : \rtype \]
Now presumably there's some condition on the context that $x : A$
lives in that ensures $A$ is a well-formed type given $\alpha + \C$.
So the semantics of $A$ is again a functor $\C \to \rset$,
and we have types $A_c$ and functions $A_f : A_c \to A_d$.
But what's the interpretation of $B$? For every object $c \in \C$, it should have an object
part. Let's say...
\[B_c : A_c \to \rtype\]
Sure, that seems right. For every $c \in \C$, and every choice
of an indexing element from $A_c$, there's a type $B_c$. What's the morphism part?
It should roughly feel like
\[B_f : B_c \to B_d \]
but $B_c$ and $B_d$ aren't types any more, they're types indexed over $A$. We have to fill in something in
\[B_f : B_c(?) \to B_d(?) \]
Hm, ok... so maybe we want to quantify over all $a \in A$, like
\[B_f : (a : A_?) \to B_c(a) \to B_d(a) \]
Oh, but we have to pick an object to interpret $A$ at. Maybe the 'earlier' one, $c$, is more canonical?
\[B_f : (a : A_c) \to B_c(a) \to B_d(\textcolor{red}{a}) \]
But that red $a$ doesn't typecheck; it needs to be $A_d$ instead of $A_c$. But hey, we have a way
of transporting from $A_c$ to $A_d$! That's exactly what $A_f$ does! So
\[B_f : (a : A_c) \to B_c(a) \to B_d(A_f(a)) \]
is a well-typed proposal for what the 'morphism part' of the meaning of $B$ should be.
Trying to Interpret $\Pi$ Types
Ok, if we can have types depending on variables in a context, we
should be able to bind them. A sensible guess for the dependent
function space type formation rule is
\[ \Delta ; \Gamma, x : A \prov B : \rtype
\over
\Delta ; \Gamma \prov (x : A) \to B : \rtype\]
Let's just think about the special case where $\Delta$ is a singleton and $\Gamma$ is empty, though:
\[ \alpha + \C ; x : A \prov B : \rtype
\over
\alpha + \C ; \cdot \prov (x : A) \to B : \rtype\]
Given everything we just said above, knowing that the premise of this rule would let us infer that semantically all of
\[ A_c : \rtype \qquad A_f : A_c \to A_d \]
\[B_c : A_c \to \rtype \qquad B_f : (a : A_c) \to B_c(a) \to B_d(A_f(a)) \]
hold for any $c \in \C$ or $f : c \to d \in \C$. What we want to do is give a meaning to the type $(x : A) \to B$, which would
be a functor $\C \to \rset$.
What's the object part? There doesn't seem to be any obstacle to merely passing the object $c$ down to the component types:
\[ ((x : A) \to B)_c = (x : A_c) \to B_c(x) \]
How about the morphism part? We're given a $f : c \to d$, and we need to define something of type
\[ ((x : A) \to B)_f : ((x : A_c) \to B_c(x)) \to ((x : A_d) \to B_d(x))\]
...and here we run into a big problem: the transport we have for the type $A$ only works covariantly, but we need to get from an $A_d$
that we have, to the $A_c$ that we need to supply to our argument of type $((x : A_c) \to B_c(x))$!
Trying to Interpret $\Pi$ Types, Take 2
Ok, hmmm, let's think about what's going on here. It's an utterly
ubiquitous repeating theme throughout category theory and
programming language theory and so on that the argument position
of a function types are Where Contravariance Canonically Happens.
So perhaps although we were correct to say that
\[ \alpha + \C ; \cdot \prov B : \rtype \]
should lead to $B$ being interpreted as
a covariant functor $\C \to \rset$, maybe for types appearing to
the left of the turnstile (instead of on the right, where
$B$ sits) we should interpret them as contravariant functors in $\C$?
Let's go through again what happens with the meaning of a type with one term variable in it. If we have
\[ \alpha + \C ; x : A \prov B(x) : \rtype \]
then we can still have $B_c : A_c \to \rtype$, no problem. But what becomes of
\[ B_f : (a : A_c) \to B_c(a) \to B_d(\textcolor{red}{A_f(a)}) \]
? We're now assuming that $A_f : A_d \to A_c$, so the call to transport in red is no longer well-typed.
Both its input $a : A_c$ and the environment it's in (which wants an $A_d$) are wrong.
But wait — if we quantify over $A_d$ instead of $A_c$ in the first place,
\[ B_f : (a : A_d) \to B_c(\ldots) \to B_d(a) \]
we don't need to do transport at all on the output side$\ldots$ and moreover we can
now do the appropriate transport on the input side.
\[ B_f : (a : A_d) \to B_c(A_f(a)) \to B_d(a) \]
Ok, great, can we do the morphism part of $\Pi$ types now? Yes we can! Now the premise of the rule
\[ \alpha + \C ; x : A \prov B : \rtype
\over
\alpha + \C ; \cdot \prov (x : A) \to B : \rtype\]
tells us that
\[ A_c : \rtype \qquad A_f : A_d \to A_c \]
\[B_c : A_c \to \rtype \qquad B_f : (a : A_d) \to B_c(A_f(a)) \to B_d(a) \]
and we can define
\[((x : A) \to B)_c = (x : A_c) \to B_c(x)\]
as we did before, and moreover
\[((x : A) \to B)_f : ((x : A_c) \to B_c(x)) \to (x : A_d) \to B_d(x)\]
\[((x : A) \to B)_f(k )(a) = B_f(A_f(a))(k(A_f(a))) \]
Check for yourself that this is really well-typed! The
substitutions arising from the dependent function types in our ambient
metalogic work out just the way they need to.
Trying to Interpret $\Sigma$ Types
Wonderful, $\Pi$ types work fine, let's do $\Sigma$ types. They should be even easier, right?
Let's look at the special-case rule
\[ \alpha + \C ; x : A \prov B : \rtype
\over
\alpha + \C ; \cdot \prov (x : A) \x B : \rtype\]
whose premise as before gives us all of
\[ A_c : \rtype \qquad A_f : A_d \to A_c \]
\[B_c : A_c \to \rtype \qquad B_f : (a : A_d) \to B_c(A_f(a)) \to B_d(a) \]
For the object part, surely we again want to just behave homomorphically and say
\[ ((x : A) \x B)_c = (x : A_c) \x B_c(x) \]
And for the morphism part, we can probably just do things componentwise, right?
We'll need to build something of type
\[ ((x : A) \x B)_f : ((x : A_c) \x B_c(x)) \to ((x : A_d) \x B_d(x)) \]
And then we... oh no. For this we do need to build an $A_d$ out of
an $A_c$. That's covariant transport, not contravariant.
What's the deal with this? How are we supposed to interpret
variables $x : A$ in the context $\Gamma$ if $\Pi$ types want them
to do one thing and $\Sigma$ types want them to do something else?
The Solution: Variance-Polarized Term Variables
I think the right thing to do is just have two types of variables in $\Gamma$, one $x + A$ that undergoes transport
covariantly, and one $x - A$ that does it contravariantly. Context formation could reflect the fact that $x - A$ variables
actually need to access $\Delta$ differently:
\[ \Delta^\op \prov A : \rtype \over \Delta \prov x - A \rctx \]
or more generally I think
\[ \over \Delta \prov \cdot \rctx\]
\[ \Delta \prov \Gamma \rctx \qquad \Delta^\op ; \Gamma^\op \prov A : \rtype \over \Delta \prov x - A \rctx \]
\[ \Delta \prov \Gamma \rctx \qquad \Delta ; \Gamma \prov A : \rtype \over \Delta \prov x + A \rctx \]
and then the $\Pi$ and $\Sigma$ rules would each bind a certain kind of variable:
\[ \Delta ; \Gamma, x - A \prov B : \rtype
\over
\Delta ; \Gamma \prov (x : A) \to B : \rtype\]
\[ \Delta ; \Gamma, x + A \prov B : \rtype
\over
\Delta ; \Gamma \prov (x : A) \x B : \rtype\]
where $\Gamma^\op$ is defined by
\[ (\Gamma, x + A)^\op = \Gamma^\op, x - A \]
\[ (\Gamma, x - A)^\op = \Gamma^\op, x + A \]
There's a lot more to this story that I need to make sure is coherent,
but it seems interesting so far.