Haskell doesn’t come with a library for reversible computation, but because of the pure nature of functions written in Haskell, it lends itself quite nicely to defining reversible computations. It is the job of the programmer to ensure that the func- tions we define are reversible, although we could define a universal set of reversible gates and combinators from which we can define reversible computations. This approach would be very similar to the classical subset that can be used in defining computations in the QIO Monad.
A simple example of reversible computation written in Haskell is to implement a toy language that represents reversible circuits, along with an interpreter for running the circuits over lists of Boolean values.
The first thing we define for our toy language is the data-type that represents the possible gates in a circuit.
dataGate = Empty | X Gate
| Control Gate Gate | DWire Gate Gate
The names of these constructors represent the operations each gate performs. We shall look shortly at what these operations actually are. The last Gate argument to each constructor (except the Empty constructor) gives us a recursive structure that can be used to build up circuits of gates. As such, it is useful to think of the Gate data-type in terms of a monoid.
instanceMonoid Gate where mempty = Empty
mappend (X g) g′ = X (mappend g g′)
mappend (Control c g) g′ = Control c (mappend g g′) mappend (DWire d g) g′ = DWire d (mappend g g′)
The monoidal identity (mempty) is simply the Empty constructor, and the se- quencing of gates (using mappend ) can be thought of as pushing the append operation down through the recursive structure until the end of the circuit is reached.
The syntax of the language is given by functions that represent the behaviour of each of the constructors, along with the monoidal constructs to sequence them.
x :: Gate x = X Empty
X
The x operation is used to logically negate the first bit in the circuit (or sub- circuit) it represents.
control :: Gate → Gate control g = Control g Empty
• Gate
The control operation is used to conditionally apply the gate given as its argument to the corresponding number of bits below it in the circuit (or sub-circuit) it represents.
dwire :: Gate → Gate
dwire g = DWire g Empty Gate
Finally, the dwire operation can be thought of as moving the action of its argument gate down by one wire in the circuit (or sub-circuit) it represents.
As our circuits are designed to be reversible, we can also provide a function that returns the reverse of a given circuit. This reverse function works at the level of the Gate data-type, simply reversing the order of any gates and sub-gates.
reverse :: Gate → Gate reverse Empty = Empty
reverse (X g) = reverse g ‘mappend ‘ x
reverse (DWire d g) = reverse g ‘mappend ‘ dwire (reverse d )
Now we’ve defined the syntax of our language, we are able to define some examples of circuits written in it. For example, we can define the Toffoli gate,
toffoli :: Gate
toffoli = control (control x )
or the control′ gate that only runs its argument when the control wire is in the
False state.
control′:: Gate → Gate
control′ g = x ‘mappend ‘ control g ‘mappend ‘ x
Finally, to show the use of the dwire operation, we can define a circuit that conditionally applies the x operation to the two wires below the control wire.
controlXX :: Gate
controlXX = control x ‘mappend ‘ control (dwire x )
If we want to be able to run these circuits, then we must define an evaluation function for our toy language of reversible gates. We can think of a reversible circuit as a function that takes a list of Boolean values, to a list of Boolean values. As such, we define the data-type Circuit to represent these functions.
dataCircuit = Circuit {c :: [Bool ] → [Bool ]}
In order to make the design of the evaluation function easier, it is useful to lift the underlying monoidal behaviour of our Gate data-type into an equivalent monoidal behaviour of the Circuit data-type.
instanceMonoid Circuit where mempty = Circuit id
(Circuit f ) ‘mappend ‘ (Circuit g) = Circuit (g ◦ f )
The empty circuit is simply the id function, and sequencing of circuits is achieved by functional composition.
We can now go on to define the actual eval function that formalises the be- haviour of circuits (or members of the Gate data-type), lifting them to the func- tional representation of a Circuit.
eval :: Gate → Circuit eval Empty = mempty
eval (X g) = Circuit (λ(x : xs)→ ((¬ x ) : xs)) ‘mappend ‘ eval g
eval (Control c g) = Circuit (λ(x : xs)→ (x : (if x then (run c xs) elsexs))) ‘mappend ‘ eval g eval (DWire d g) = Circuit (λ(x : xs)→ (x : (run d xs)))
‘mappend ‘ eval g
It’s quite easy to see how these functions relate to the behaviour we described for each Gate above. Running a computation can now be thought of as applying an evaluated circuit, to a list a Booleans.
run :: Gate → [Bool ] → [Bool ] run g bs = c (eval g) bs
With such an evaluator, we could define reversible computations in terms of our reversible circuits, or give irreversible computations by embedding them into such a reversible circuit. An example of an irreversible computation embedded in a reversible circuit would be to define the logical and function (&) by extracting the third Boolean value from running the Toffoli gate with the two input values, and a third value set to False.
(&) :: Bool → Bool → Bool
a & b = (run toffoli [a, b, False ]) !! 2
Such a toy language shows how the functional style of programming in Haskell lends itself very nicely to defining reversible computations.
Chapter 5
QIO - The Quantum IO Monad
This chapter introduces the Quantum IO Monad (QIO), written in Haskell. Much of the work in this chapter, and the following two chapters, shall also appear in the forthcoming book “Semantic Techniques in Quantum Computation” ([AG10]).
5.1
QIO in Haskell
As we have already seen (in section 4.3), Haskell uses the categorical notion of a monad to enable effectful programs to be designed. Any effects that a program has are described explicitly by the monad in which they take place, allowing Haskell programmers to create monads for describing any arbitrary effects they wish to model. This lends itself very nicely to modelling quantum computations in Haskell, by using a quantum monad that explicitly defines the side effects that occur in a quantum computation. Namely, the side effects that can occur from measurements in a quantum computation (see section 2.3). The Quantum IO Monad (QIO), is a monad defined in Haskell which acts as an interface to quantum programming. It is only a first approximation to a functional interface, and we shall discuss some of its pitfalls at the end of chapter 7. First, this chapter shall introduce the Quantum IO Monad as a Haskell library, which allows quantum computations to be defined using Haskell syntax, in a functional manner. Then, chapter 7 will go over the implementation of the quantum simulator functions that can be used to simulate
the actual running of the quantum computations defined in the QIO monad. This implementation of the Quantum IO monad provides a constructive se- mantics for quantum programming, in the sense that the functional programs can also be understood as a mathematical model of quantum computation. The monadic structure of QIO means that the side-effects from measurements are built explicitly into the language, and enables us to keep the definition of unitary oper- ators separate from the monadic structure until we wish to define actual quantum computations over specific qubits. Once quantum computations are defined, the quantum simulator functions enable us to simulate the running of the computa- tion, giving a probabilistic result, or provide a probability distribution over all the possible results of the computation. The approach taken here, is that when a sufficiently sized quantum register becomes available (and affordable), it can be swapped in for these simulator functions, or more specifically the run simulator function that returns a single probabilistic result.
We shall introduce the Quantum IO Monad in a similar manner as introduc- tions to the IO Monad, using examples to show the available constructs and how they are used in defining quantum computations. Section 5.6 will then give a recap of all the constructs, and a brief discussion on their relation to the cate- gorical model introduced in chapter 3. In the following chapter, we shall go on to give fully developed versions of a few of the most famous quantum algorithms written in QIO, such as Deutsch’s algorithm, Quantum teleportation, and even an implementation of Shor’s algorithm. The implementation of Shor’s algorithm also introduces a library of reversible arithmetic operators written in QIO that are used to define the necessary modular exponentiation function. Shor’s algorithm also makes use of the quantum Fourier transform, again defined as a quantum computation in QIO.