My name is Peter Selinger
and with my coauthors Frank Fu and Kohei Kishida,
I would like to tell you about
Linear Dependent Type Theory for Quantum Programming Languages.
The goal of this research is to develop techniques
for formal reasoning about quantum software.
And we do this via the theory of quantum programming languages.
In this talk, we want to focus on high-level languages.
And by a high-level language, I mean a language in which an
algorithm can be described at a similar level of abstraction
at which the algorithm would normally be described
in a research paper.
Formal methods are even more important
in the quantum setting than in the classical setting,
because basically you can't debug a quantum program. 
It is not possible to just stop a quantum program 
in the middle of the computation and examine its state.
So formal verification is perhaps the only way
to ensure the correctness of quantum software.
The programming language that we've developed
over the last 8 years or so is called "Quipper".
Quipper is a functional programming language.
It's embedded in Haskell, and it's quite practical.
We've implemented several different algorithms in Quipper,
including some quite complicated ones from the literature.
And some of these algorithms produce trillions of gates.
Quipper is embedded in Haskell.
And while this makes it quite practical,
unfortunately, Quipper is not type-safe,
because there are two features that Haskell doesn't have.
One of these is linear types, and the other one is dependent types.
And both of these would be necessary
if we wanted to make a type-safe version of Quipper.
And so, for the past few years, we've been working on
a family of programming languages that we call "Proto-Quipper".
Proto-Quipper is basically our attempt
to formalize a meaningful fragment of Quipper.
It's a family of languages,
because in the beginning, we started with a small core,
and we're using this as a platform
to experiment with adding different features to the language.
And the goal is that Proto-Quipper will eventually be
as useful as the "real" Quipper.
So, the first thing to understand about Quipper
is that Quipper is a circuit description language.
So it's a language for describing families of quantum circuits,
where the families might be parameterized by some kind of data.
And you can describe circuits at different levels of abstraction.
The lowest level of abstraction is
when you describe the circuits one gate at a time.
In Quipper, there is a type "Qubit",
and a value of this type is essentially a pointer
to a place in a circuit where a gate could be appended.
And as the term M evaluates, every once in a while it says:
"please apply the gate G to qubits k and m."
The pointers k and m will be used up,
and there will be new pointers, say k' and m',
corresponding to the outputs of the new gate.
But as it turns out, describing circuits
at this very low level of "one gate at a time"
is not at all how people describe quantum algorithms in the literature.
Rarely do you find a paper in the quantum computing literature
that says "first apply a Hadamard gate,
then apply a controlled-not gate".
Rather, these algorithms are typically described at a fairly high level.
For example, someone might be telling you:
"Such-and-such problem can be solved in polynomial time.
Therefore, there exists a polynomial family
of classical circuits for this problem.
Using a standard trick,
we can turn these into reversible quantum circuits.
And then we apply another method to this circuit,
for example, phase estimation."
And what it means is that
most of the description of a quantum algorithm
is usually by describing transformations on entire quantum circuits
rather than talking about individual gates.
And in Quipper, we therefore want to be able to express
these ideas of operating on whole circuits at a time.
So an example of a circuit level operation is:
take a circuit, and then reverse it.
Okay? For example,
many quantum algorithms take the form A, then B, then A⁻¹.
So you don't want to write a program to compute the circuit A
and another program to compute the circuit A⁻¹.
Rather, you want to generate A and then invert it.
And the way that these high-level operations are provided in Quipper
is via these constructions called "boxing" and "unboxing" of circuits.
So if A and B are quantum data types,
then A → B would be the type of functions from A to B.
And when you run such a function,
it might issue some gates and compute a quantum circuit.
But when you want to treat that circuit as data,
rather than a function, then you need to "box" the circuit.
So "box" is an operation that takes a circuit-generating
function and turns it into a circuit that is data.
And the way that this is handled in the operational semantics is this:
if you're evaluating a term, which maybe holds several references to
qubits in the current quantum circuit,
then when you come to a "box" operation,
then the current circuit is put aside,
and the term inside the box operation is evaluated
in the context of a new empty circuit.
And when that term finishes,
then the whole circuit is put into a data structure
and becomes the return value of the box operation.
And once you have this ability to box circuits,
then you also have all kinds of things
you can do with the boxed circuits.
For example reversing it, simplifying it,
applying a gate transformer that maybe replaces
certain gates by other gates, and so on.
And some of these will be predefined
as primitive operations in the programming language,
and other ones will hopefully be definable by the user.
Now when you want to have such a language,
where circuits can be described either at a low level or a high level,
and you want that language to be type-safe,
then that's not an entirely trivial thing to do.
Okay? And it turns out that to make a type-safe language,
it's very important to make a distinction between
values that are parameters and values that are states.
So let me explain what I mean by that.
Because Quipper is a circuit description language,
it shares this feature that can also be found
in hardware description languages,
where there are basically two runtimes.
When you first run the program, it's going to generate a circuit.
And later, you're going to give this circuit to the quantum computer,
and then the circuit would be running on the quantum computer.
So the first runtime is circuit generation time,
and then the second runtime is circuit execution time.
And any kind of data that is known at circuit generation time
is called a parameter.
And any kind of data that is only known at circuit execution time
is called state.
Okay? And the design choice that we've made in Quipper
is that both parameters and state should live
in the same syntactic category.
They should both be first-class citizens of the programming language
on an equal footing.
This is not the only possible solution.
For example, QWire, developed at the University of Pennsylvania,
is an embedded quantum programming language
where basically a parameter is any value of the host language,
and then state only lives inside a boxing operation.
Basically there are two different languages then:
an inner language for circuits, and an outer language for parameters.
But in Quipper, we decided not to go this route.
In Quipper, parameters and states belong to the same syntactic class,
and the type of a variable determines how the variable can be used.
So this creates certain complications,
because state in a quantum world cannot be duplicated
and needs to be treated linearly.
So we need a type system that is based on linear logic.
But one of the advantages of keeping parameters and state
in the same name space of the programming language
is that this then enables us to create data structures
that are part parameter and part state.
So an example of this would be a pair of a qubit and an integer.
Or, for example, a list of qubits,
where the length of the list is known at circuit generation time,
so that's a parameter,
but then the actual state of the qubits in the list
is only known at circuit execution time, so that's state.
Now, in addition to this distinction between parameters and state,
there is another distinction we also have to make.
And it is between simple and non-simple types.
A type is called "simple" if a value of that type only has state
and there is no parameter component.
So for example, Qubit would be a simple type,
Qubit ⊗ Qubit would be a simple type,
but a list of qubits would not be a simple type.
And the reason simple types are important is
that you can really only box individual circuits.
You cannot box families of circuits.
So that means, when you actually want to generate a circuit
and put it in a box,
you need to specify which member of the family you're going to box.
Okay? And that means that circuits can only be boxed at simple types.
So for example, an example of a family of circuits
is the Quantum Fourier Transform.
There is a family of circuits, one for every n,
okay, where n is the number of qubits.
And maybe a natural way to code that would be
as a function from lists of qubits to lists of qubits.
So if you apply it to a list of 10 qubits, then you'll get
the circuit for the Quantum Fourier Transform of size 10.
But the problem is that boxing only makes sense for simple types,
and lists of qubits is not a simple type.
So we need some way of picking out a member of that family of circuits
and then boxing that member.
And that is exactly where dependent types come into the picture.
So now my coauthor Frank Fu is going to tell you about
how we use dependent types in Proto-Quipper.
Frank?
Thank you Peter!
So we cannot box linear functions from list of qubits to list of qubits.
Which is a shame,
because a lot of useful quantum algorithms can be given this type.
With dependent data types such as Vector,
we can do the following instead.
So, first we take a given natural number n,
then we box a member,
which is a function from Vectors of qubits of length n
to Vectors of qubits of length n.
Now let me show you how this looks like in the implementation.
As we can see over here,
we define the dependent vector data type as a simple type.
Then we define the Quantum Fourier Transformation
as a family of functions going from vectors of qubits
to vectors of qubits.
Now we can use the box construct to box the family of QFT functions
into a family of circuits.
So this qftBox function takes a number as input
and returns the circuit of the corresponding size.
Here is a circuit that is generated from qftBox with the input 5.
Now that we have linear types on the one hand,
and dependent types on the other,
we will have to be careful about how to combine these two.
And in fact, the most challenging problem
of combining linear and dependent types
is the following situation.
So here we have a linear resource term a.
When we give it as an argument to the linear function,
the resource term a appears both in the term and in the type.
So how are we going to explain the linear resource
getting used twice in this typing judgement?
In fact, Cervasato and Pfenning argue that
the term a is used twice, so it cannot be a linear resource.
So this leads to two different kinds of function spaces,
namely, the linear function types and the dependent function types.
So recently, Connor McBride proposes
that we are going to allow the term a to be a resource,
but when the resource gets mentioned in a type,
it's considered we are "contemplating" the value of the resource.
But this does not quite make sense in our setting,
because, for example, the resource a can be a qubit,
and it does not make sense to contemplate
the value of a qubit during type checking.
So our approach to handle this problem
is that we are going to allow the term a to be a resource,
but when the resource gets mentioned in the type level,
we say only the "shape" of the resource gets used.
And the shape of a resource is considered reusable data.
Let me give you an intuition about the shape of data.
So if we think of data as a tree,
then the shape of this data is the same tree structure,
except that we replace all the leaves with units.
So for example, a list of qubits, its shape is a list of units,
which is the same as its length.
Now my coauthor Kohei Kishida will explain
the categorical underpinning of our approach.
Kohei?
Thank you, Frank.
The goal in this part of the talk is
to unify the two semantic frameworks
for linear type theory and dependent type theory.
On the one hand, linear type theory
is modelled by certain monoidal categories,
in which monoidal products are not cartesian
and can therefore accommodate a notion of linear resources
that cannot be duplicated.
On the other hand, dependent type theory amounts to
locally cartesian closed categories,
in which every slice over a context Γ
is a cartesian closed category of types and terms in Γ,
and the interaction among different contexts
is modelled by pullbacks and their adjunctions.
Unification of these two frameworks is not an easy task, however,
because, on the one hand, LCCCs are — well — cartesian,
and do not accommodate the linear notion of resources.
On the other hand, monoidal closed categories do not have nice slices,
so they do not admit dependent types.
Our solution to this problem
is to introduce what we call a "state-parameter fibration",
a certain fibration # of a monoidal category E over an LCCC B.
The guiding idea behind this setting
is to take an object A of E as a parameterized family of state spaces,
and its image #A, or underline-A, as the underlying
set of those parameters, or the "shape" of A.
In fact, with a small assumption, # has a right adjoint
that embeds B as a full subcategory into E, so that,
(1) the role of # can also be fulfilled by the unit of adjunction,
and (2), we can consider slices of E over objects of B.
We then axiomatize the fibration so that each such slice is monoidal closed.
Indeed, # being a fibration means that 
any map of parameters can be canonically lifted,
or equivalently, that any parameter map can pull things back.
This slice structure models dependent types and terms in contexts
and the interaction between contexts.
In other words, although not all slices of E are nice
and although E does not have all pullbacks,
it is okay because slices over objects of B
and pullbacks along arrows of B
are enough for our linear dependent type theory.
This is actually the semantic counterpart of our principle
that dependent types are dependent upon shapes.
Let's expand on how the state-parameter setting works.
Simple types, for instance, boil down to types whose shape is 1.
An important idea is that we can think of the lifting of a parameter map,
or the pullback along the parameter map,
as re-parameterizing the components of the parameterized family A.
So pullbacks define linear structures as if componentwise.
Using this idea, we can define a functor p
that gives to each parameter set the monoidal unit of its shape.
When we further assume that p has a right adjoint,
we end up with a linear-nonlinear adjunction in the usual sense,
and we can use it to interpret the "!" operator,
as well as boxing and unboxing.
But maybe more crucially,
images of p interpret parameter types within the category E,
thereby placing parameters and state within the same name space.
As another example of componentwise definition,
we have a parameterized family of monoidal products here,
but then, by getting the subfamily
in which the parameters of B depend on those of A,
we can define what we call a "dependent monoidal product",
which interprets both the extension of a context
and the linear Σ-type.
At the parameter level, this is exactly the LCCC interpretation.
But at the state level, we are combining resources
from the state spaces in both A and B.
Using such linear structures over nonlinear parameters,
the semantics of dependent types and terms goes as follows.
First, a context is interpreted by an object of E,
then a dependent type in that context is an
object of the slice category over the shape of that context.
A term in the context is then an arrow in that slice category.
So this triangle summarizes our interpretation.
Observe that, if we hit the triangle with #,
at the parameter level we have the usual triangle
for nonlinear dependent type theory in LCCCs.
Our linear interpretation, however, is different
in that we generally have different objects
in the top-left vertex and the base.
The base needs to be a shape,
because dependent types depend on parameters,
but the top-left vertex should not generally be a shape,
since it may need to embody resources that terms consume.
This means that, even though dependent types depend on shapes,
our semantics still covers the two cases of contexts:
involving and not involving resources,
in a uniform fashion, and accordingly,
even in linear Π-types, the antecedent can involve resources.
To sum up, we can say that, by taking the fibration #,
our semantics takes a fibration of the linear type theory
of state and resources in the category E
over the dependent type theory of parameters in B.
So this is how our semantics works,
and now Frank will give you more detail on the syntactic side.
Frank?
Thank you Kohei.
Now I am going to explain the syntactic shape operation.
This operation is directly derived from our categorical semantics.
As you can see here, we are defining the shape of a qubit as a unit.
For all the other type constructors, we define their shapes inductively.
For example, the shape of list of type A is a list of Sh(A).
And the shape of a linear function type
is defined as a parameter function type from Sh(A) to Sh(B).
Now let's see what our type system looks like
once we incorporate the shape operation.
If we look at this kinding rule for the linear function type,
we first take the shape of A, and add that to the context.
Then we use the extended context to kind-check B.
And for the application rule,
when we are applying a linear function M to an argument N,
we first combine the contexts Γ₁ and Γ₂ in a linear fashion,
then we substitute the shape of N into the type B.
For this typing rule to be consistent,
we have to make sure that the shape of N has the type Sh(A).
And you can find the rest of the typing rules in our paper.
Now that we have dependent types, we can do all kinds of new things.
For example, here we are defining the WithGarbage data type.
It uses the existential vector data type.
The WithGarbage data type is a monad,
and it comes with a runGarbage function,
so it's just like we have a run function for the state monad.
So to see how this WithGarbage monad is useful,
let's take a look at this circuit
that is performing the one bit addition.
So this circuit takes 3 qubits as inputs,
and outputs 3 qubits together with a lot of excessive qubits.
We will call them "garbage qubits".
So, to get rid of these garbage qubits,
we can give this circuit to the runGarbage function
that comes with the WithGarbage data type.
As we can see here, all the garbage qubits are uncomputed —
are collected or uncomputed, in a type-safe manner.
So here is the circuit that we obtain.
And it is the garbage free version of this one bit adder.
So to summarize, we make the following contributions.
First, we develop a notion of state-parameter fibration.
Then we work out the operational semantics,
type soundness and safety properties.
And last but not least,
we have a prototype available on GitLab.
So if you are interested,
please check out our paper and our implementation. 
