key: cord-0047284-51orl5u0 authors: Chen, Chao-Hong; Choudhury, Vikraman; Carette, Jacques; Sabry, Amr title: Fractional Types: Expressive and Safe Space Management for Ancilla Bits date: 2020-06-17 journal: Reversible Computation DOI: 10.1007/978-3-030-52482-1_10 sha: 6deed48d2ac70d2f72ee42aedc1e934cf56b5d78 doc_id: 47284 cord_uid: 51orl5u0 In reversible computing, the management of space is subject to two broad classes of constraints. First, as with general-purpose computation, every allocation must be paired with a matching de-allocation. Second, space can only be safely de-allocated if its contents are restored to their initial value from allocation time. Generally speaking, the state of the art provides limited partial solutions, either leaving both constraints to programmers’ assertions or imposing a stack discipline to address the first constraint and leaving the second constraint to programmers’ assertions. We propose a novel approach based on the idea of fractional types. As a simple intuitive example, allocation of a new boolean value initialized to [Image: see text] also creates a value [Image: see text] that can be thought of as a garbage collection (GC) process specialized to reclaim, and only reclaim, storage containing the value [Image: see text]. This GC process is a first-class entity that can be manipulated, decomposed into smaller processes and combined with other GC processes. We formalize this idea in the context of a reversible language founded on type isomorphisms, prove its fundamental correctness properties, and illustrate its expressiveness using a wide variety of examples. The development is backed by a fully-formalized Agda implementation (https://github.com/DreamLinuxer/FracAncilla). We solve the ancilla problem in reversible computation using a novel concept: fractional types. In the next section, we introduce the problem of ancilla management, motivate its importance, and explain the limitations of current approaches. Although the concept of fractional types could potentially be integrated with general-purpose languages, its natural technical definition exploits symmetries present in the categorical model of type isomorphisms. To that end, we first review in Sect. 3 our previous work [7, 8, 13, 14] on a reversible programming language built using type isomorphisms. In Sect. 4, we introduce a simple version of fractional types that allows allocation and de-allocation of ancilla bits in patterns beyond the scoped model but, like existing stack-based solutions, still requires a runtime check to verify the safety of de-allocation. In Sect. 5 we show how to remove this runtime check, by lifting programs to a richer type system with pointed types, expressing the proofs of safety in that setting, and then, from the proofs, extracting programs with guaranteed safe de-allocations and no runtime checks. The last section concludes with a summary of our results. Restricting a reversible circuit to use no ancilla bits is like restricting a Turing machine to use no memory other than the n bits used to represent the input [1] . Since such a restriction disallows countless computations for trivial reasons, reversible models of computation have, since their inception, included management for scratch storage in the form of ancilla bits [25] with the fundamental restriction that such bits must be returned to their initial states before being safely reused or de-allocated. Reversible programming languages adopt different approaches to the management of ancilla bits, which we review below. Quipper [12] . The language provides a scoped mechanism to manage ancilla bits via: with_ancilla :: (Qubit -> Circ a) -> Circ a The operator takes a block of gates parameterized by an ancilla value, allocates a new ancilla value of type Qubit initialized to |0 , and runs the given block of gates. At the end of its execution, the block is expected to return the ancilla value to the state |0 at which point it is de-allocated. The expectation that the ancilla value is in the state |0 is enforced via a runtime check. Quipper also provides primitives qinit and qterm, which allow programmers to manage ancilla bits manually without scoping constraints. This management is not supported by the type system, however. For example, the following staticallyvalid expression allocates an ancilla bit using qinit but neither it nor its caller are required by the type system to de-allocate it: ex :: Qubit -> Circ (Qubit,Qubit) ex x = do y <-qinit 0 y <-qnot y 'controlled' x x <-qnot x 'controlled' y return (x,y) rFun [23, 26] . This language similarly allows expressions to freely allocate constant values: data Bool = True | False ex :: Bool <-> (Bool,Bool) ex b = (b , False) Using such expressions, it is possible to define expressions that behave like qinit and qterm in Quipper: initF :: () <-> Bool initF () = False termF :: Bool <-> () termF False = () At run-time termF might fail with an incomplete pattern-matching exception but, statically, the type system neither enforces that termF is called nor that it is called with only the value False. Ricercar [24] . This language uses a scoped way to manage ancilla bits. The expression αx.A allocates an ancilla wire x for the gate A requiring that x is set to 0 after the evaluation of A. Janus [27] . This is a reversible imperative programming language that is not based on the circuit model but as Rose [20] explains, its treatment is essentially similar to above: All variables in original Janus are global, but in the University of Copenhagen interpreter you can allocate local variables with the local statement. The inverse of the local statement is the delocal statement, which performs deallocation. When inverted, the deallocation becomes the allocation and vice versa. In order to invert deallocation, the value of the variable at deallocation time must be known, so the syntax is delocal = . Again the onus is on the programmer to ensure that the equality actually holds. The approaches above are pragmatic but limited in two ways: non-scoped approaches do not enforce de-allocation and scoped ones do not enforce that the de-allocated bit has the correct value. To understand these points more vividly, consider the following analogy: allocating an ancilla bit by creating a new wire in the circuit is like borrowing some money from a global external entity (the memory manager); the computation has access to a new resource temporarily. De-allocating the ancilla bit is like returning the borrowed money to the global entity; the computation no longer has access to that resource. It would however be unreasonably restrictive to insist that the person (function) borrowing the money must be the same person (function) returning it. Indeed, as far as reversible computation is concerned, the only important invariant is that information is conserved, i.e., that money is conserved. The identities of bits are not observable as they are all interchangeable in the same way that particular bills with different serial numbers are interchangeable in financial transactions. Thus the only invariant is that the net flow of money between the computation and the global entity is zero. This observation allows us to go even further than just switching the identities of borrowers. It is even possible for one person to borrow $10, and have three different persons collectively collaborate to pay back the debt with one person paying $5, another $2, and a third $3, or the opposite situation of gradually borrowing $10 and returning it all at once. Computationally, this extra generality is not a gratuitous concern: since scope is a static property of programs, it does not allow the flexibility of heap allocation in which the lifetime of resources is dynamically determined. Furthermore, limiting ancilla bits to static scope does not help in solving the fundamental problem of ensuring that their value is properly restored to their initial value before de-allocation. We demonstrate that both problems can be solved with a typing discipline. The main idea is simple: we introduce a type representing "processes specialized to garbage-collect specific values." The infrastructure of reversible computing will ensure that the information inherent in this process will never be duplicated or erased, enforcing that proper, safe, de-allocation must happen in a complete program. Furthermore, since reversible computation focuses on conservation of information rather than syntactic entities, this approach will permit fascinating mechanisms in which allocations and de-allocations can be sliced and diced, decomposed and recomposed, run forwards and backwards, in arbitrary ways as long as the net balance is 0. The syntax of the language Π [8] consists of several sorts: τ : Focusing on finite types, the building blocks of the type theory are: the empty type (0), the unit type (1, where tt : 1 is the only inhabitant), the sum type (+), and the product (×) type. One may view each type τ as a collection of physical wires that can transmit |τ | distinct values where |τ | is a natural number that indicates the size of a type, computed as: |0| = 0; |1| = 1; |τ 1 + τ 2 | = |τ 1 | + |τ 2 |; and |τ 1 × τ 2 | = |τ 1 | * |τ 2 |. Thus the type B = 1 + 1 corresponds to a wire that can transmit one of two values, i.e., bits, with the convention that inj 1 (tt) represents F and inj 2 (tt) represents T. The type B × B × B corresponds to a collection of wires that can transmit three bits. From that perspective, a type isomorphism between types τ 1 and τ 2 (such that |τ 1 | = |τ 2 | = n) models a reversible combinational circuit that permutes the n different values. These type isomorphisms are collected in Fig. 1 . It is known that these type isomorphisms are sound and complete for all permutations on finite types [9, 10] and hence that they are complete for expressing combinational circuits [11, 13, 25] . Algebraically, these types and combinators form a commutative semiring (up to type isomorphism). Logically they form a superstructural logic capturing space-time tradeoffs [21] . Categorically, they form a distributive bimonoidal category [17] . Below, we show code, in our Agda formalization, that defines types corresponding to bits (booleans), two-bits, and three-bits. We then define an operator ctrl that builds a controlled version of a given combinator c. This controlled version takes an additional "control" bit and only applies c if the control bit is true. The code then iterates the control operation several times starting from boolean negation building up to Toffoli. Although austere, this combinator-based language has the advantage of being more amenable to formal analysis for at least two reasons: (i) it is conceptually simple and small, and (ii) it has direct and evident connections to type theory and category theory. Indeed our solution for managing ancillae is inspired by the construction of compact closed categories [2, 3, 15] . These categories extend the monoidal categories [4, 5, 18] which are used to model many resource-aware (e.g., based on linear types) programming languages [6, 16] (including Π) with a new type constructor that creates duals or inverses to existing types. This dual will be our fractional type. The main idea is to extend the Π terms with two combinators η and witnessing the isomorphism A * 1/A = 1. The names and types of these operations are inspired by compact closed categories which are extensions of the monoidal categories that model Π. Intuitively, η allows one, from "no information," to create a pair of a value of type A and a value of type 1/A. We interpret the latter value as a GC process specialized to collect the created value. Dually, applies the GC process to the appropriate value annihilating both. 1 To make this idea work, several technical issues need to be dealt with. Most notably, we must exclude the empty type from this creation and annihilation process. Otherwise, we would be able to prove that: The second important issue is to ensure that the GC process is specialized to collect a particular value. We therefore exploit ideas from dependent type theory to treat individual values as singleton types. More precisely, we extend the syntax of core Π in Sect. 3 as follows: Value types τ ::= · · · | 1/v Values v ::= · · · | Program types τ ↔ τ Programs c ::= · · · | η v:τ : 1 For now, the core Π language is simply extended with a new type 1/v which represents a GC process specialized to collect the value v. Since all relevant information is present in the type, at runtime, this GC process is represented using a trivial value denoted by . The combinators η and are parameterized by the value v (and its type τ ) which serves two purposes. First it guarantees that the combinators operate on non-empty types, and second it fixes the type of the GC process. At this point, however, although the language guarantees that the GC process can only collect a particular value, the type system does not track the value created by η, nor does it predict the value that reaches . In other words, it is possible to write programs in which expects one value but is instead applied to another value. In this section, we will deal with such situations by including a runtime check in the formal semantics, and show how to remove it, via a safety proof, in the next section. Our Agda formalization clarifies our semantics, with the new type as: The new combinators are defined as follows: The most relevant excerpt of the formal semantics is given below: The interpreter either returns a proper value (just . . .) or throws an exception nothing. The semantics of the core Π combinators performs the appropriate isomorphism and returns a proper value. At η, the v that parameterizes the combinator is used to create a new value v and a GC process specialized to collect it. By the time evaluation reaches , the value created by η may have undergone arbitrary transformations and is not guaranteed to be the value expected by the GC process. A runtime check is performed: if the value is the expected one, it is annihilated together with the GC process; otherwise an exception is thrown which is demonstrated in the following example which returns normally if given F and otherwise throws an exception: Changing the value used to instantiate η will force a corresponding change for : For future reference, we will call this language Π/D for the fractional extension of Π with a dynamic check. We illustrate the expressiveness of the language with two small examples. The Agda code for the examples is written in a style that reveals the intermediate steps for expository purposes. The first circuit has one input and one output. Immediately after receiving the input, the circuit generates an ancilla wire and its corresponding GC process (first two steps in the Agda definition). The original input and the ancilla wire interact using two CNOT gates, after which the ancilla wire is redirected to the output (next three steps in the Agda code). Finally the original input is GC'ed (last two steps in the Agda code). The entire circuit is extensionally equivalent to the identity function but it does highlight an important functionality beyond scoped ancilla management: the allocated ancilla bit is redirected to the output and a completely different bit (with the proper default value) is collected instead. The second example illustrates the manipulation of GC processes. A process for collecting a pair of values can be decomposed into two processes each collecting one of the values (and vice-versa): By lifting the scoping restriction, the development in the previous sections is already more general than the state of the art in ancilla management. It still shares the same limitation of needing a runtime check to ensure ancilla values are properly restored to their allocation value [12, 24] . We now address this limitation using a combination of pointed types, singleton types, monads, and comonads. Before giving all the (rather involved) technical details, we highlight the main idea using the toy language below: The toy language has two types (natural numbers and booleans) and two functions square and isZero and their compositions. Say we wanted to prove that compose isZero square always returns false when applied to a non-zero natural number. We can certainly do this proof in Agda (i.e., in the meta-language of our formalization) but we would like to do the proof within the toy language itself. The most important reason is that it can then be used within the language to optimize programs (or, for the case of Π/D, to remove a runtime check). The strategy we adopt is to create a lifted version of the toy language with pointed types [22] , i.e., types paired with a value of the type. In the lifted language, the evaluation function has an interesting type: it keeps track of the result of evaluation within the type: This allows various properties of compose isZero square to be derived within the extended type system. For example: The first two tests show that the type system can track exact concrete values. More interestingly, test3 shows a property that holds for all natural numbers n; its proof uses "symbolic" evaluation within the type system. In more detail, from the definition of eval, we see that eval square (suc n) produces (suc n) * (suc n); by definition of multiplication, this is an expression with a leading suc constructor which is enough to determine that evaluating isZero on it yields false. This form of partial evaluation is quite expressive, and sufficient to allow to keep track of ancilla values throughout complex programs. After proving properties about a program in T•, we can extract a T program that still satisfies those properties. Note that, since the property of test3 holds for all N, it does not matter which value we use to instantiate it. And it indeed satisfies the property: However, there are no such guarantees in the case not covered by the property: We now use the above idea to create a version of the Π language, which we call Π/•, in which all types are pointed, i.e., for each type t some value v of type t is "in focus" t#v. As the goal of the language is to keep track of fractional types, it is sufficient to inherit the multiplicative structure of Π. We also need a special kind of pointed type that includes just one value, a singleton type. The singleton types will allow the type system to track the flow of one particular value (the ancilla value), which is exactly what is needed to prove the safety of deallocation. We present the relevant definitions from our formalization and explain each: Given a set A with an element v, the singleton set containing v is the subset of A whose elements are equal to v. In Agda's type theory, this is encoded using the Singleton type. For a given type A, and a value v of type A, the type Singleton A v is inhabited by a choice of point • in A, along with a proof that v is equal to •. In other words, it is possible to refer to a singleton value v using several distinct syntactic expressions that all evaluate to v. Put differently, any claim that a value belongs to the singleton type must come with a proof that this value is equal to v. The reciprocal type Recip A v consumes exactly this singleton value. The universe of pointed types •U contains plain Π types together with a selection of a value in focus; products of pointed types; singleton types; and reciprocal types. Note that the actual value in focus for reciprocals, i.e., the runtime value of a GC process, is a function that disregards its argument returning the constant value of the unit type. As we show, this is safe, as the type system prevents the GC process being applied to anything but the particular singleton value in question. The combinators in the lifted language Π/• consist of all the combinators in the core Π language together with their multiplicative structure. The types for η and are now specialized to guarantee safety of de-allocation as follows. When applying η at a pointed type, the current witness value is put in focus in a singleton type and a GC process for that particular singleton type is created. To apply this process using the very same singleton value must be the current one. The mediation between general pointed types and singleton types is done via return and extract, which form a dual monad/comonad pair, from which many structural properties can be derived: specifically a pair of singleton types is a singleton of the pair of underlying types, and a singleton of a singleton is the same singleton. Proof. The main insight needed is to define the functor •Sing u , the tensor/ cotensor, and the join/cojoin (duplicate): Like for the toy language, evaluation is reflected in the type system, and in this case we have the additional property that evaluation is reversible: The type of evaluation now states that given a combinator mapping pointed type T 1 to pointed type T 2 where T i consists of an underlying type t i and value v i , evaluation succeeds if applying the combinator to v 1 produces v 2 . In other words, the result of evaluation is completely determined by the type system: To summarize, if a combinator expects a singleton type, then it would only typecheck in the lifted language, if it is given the unique value it expects. A particularly intriguing instance of that situation is the following program: The program takes a value of type •1/ (•1/ A). This would be a GC process specialized to collect another GC process! By collecting this process, the corresponding singleton value is "rematerialized." At runtime, there would be no information other than the functions that ignore their argument but the type system provides enough guarantees to ensure that this process is well-defined and safe. By lifting programs and their evaluation to the type level, we can naturally leverage the typechecking process to verify properties of interest, including the safe de-allocation of ancillae. One "could" just forget about Π/D and instead use Π/• as the programming language for ancilla management. Indeed the dual nature of proofs and programs is more and more exploited in languages like the one used to formalize this paper (Agda). However, it is also often the case that constructive proofs are further processed to extract native efficient programs that eschew the overhead of maintaining information needed just for proof invariants. In our case, the question is whether we can extract from a Π/• program, a program in Π/D that uses a simpler type system, a simpler runtime representation, and yet is guaranteed to be safe and hence can run without the runtime checks associated with de-allocation sites. In this section, we show that this indeed the case. We demonstrate this by constructing an extraction map from the syntax of Π/• to Π/D. This is fully implemented in the underlying Agda formalization, but we present the most significant highlights. There are three important functions whose signatures are given below: The function ExtU maps a Π/• type to a Π/D type and a value in the type. The function Ext•−• maps a Π/• combinator to a Π/D combinator, whose types are fixed by ExtU. And finally, the function Ext≡ asserts that the extracted code cannot throw an exception (it must return a just value). Each of these functions has one or two enlightening cases which we explain below. In Π/D the fractional type expresses that it expects a particular value but lacks any mechanisms to enforce this requirement. Thus we have no choice when mapping a fractional type from Π/• to Π/D but to use the 1/ v type with the trivial value: When mapping Π/• combinators to Π/D combinators, the main interesting cases are for η and . In each of those, we use the values from the pointed type as choices for the ancilla value, and the expectation for the GC process respectively: Finally we can prove the correctness of extraction. The punchline is in the following case: Here, the singleton type in Π/• guarantees that the runtime check cannot fail! This new language not only allows us to verify circuits but also allows us to merge verification with programming. To clarify this idea, we show how to implement a 4-bit Toffoli gate using proper ancilla management while at the same time proving its correctness. We start with verification of the Toffoli gate implementation we have in Sect. 3 in Π/• using pattern matching: Since we use the same implementation in all the cases, it does not matter which value we use to instantiate extraction: Using this as the building block, we can use Toffoli's construction [25] to construct a 4-bit Toffoli gate using an additional ancilla bit: The code is written in a conventional Π/D style except for the pervasive lifting to pointed types: With this construction however, we can verify that the circuit satisfies the specification of 4-bit Toffoli gate and the ancilla bit is correctly garbage collected without pattern matching. And using the extraction mechanism, we obtain a fully verified 4-bit Toffoli gate in Π/D: Note that, the type above has shown that our implementation is independent of any input, so it does not matter which value we use to instantiate the extraction: We have introduced, in the context of reversible languages, the concept of fractional types as descriptions of specialized GC processes. Although the basic idea is rather simple and intuitive, the technical details needed to reason about individual values are somewhat intricate. The use of fractional types, however, enables a complete elegant type-based solution to the management of ancilla values in reversible programming languages. The classification of reversible bit operations A structural approach to reversible computation Geometry of interaction and linear combinatory algebras Catégories avec multiplication. C. R. de l'Académie des Algèbreélémentaire dans les catégories avec multiplication A mixed linear and non-linear logic: proofs, terms and models Dagger traced symmetric monoidal categories and reversible programming Computing with semirings and weak rig groupoids Remarks on isomorphisms in typed calculi with empty and sum types Isomorphisms of generic recursive polynomial types Conservative logic Quipper: a scalable quantum programming language Information effects Isomorphic interpreters from logically reversible abstract machines Many-variable functorial calculus. I Integrating dependent and linear types Coherence for distributivity Natural associativity and commutativity A categorical presentation of quantum computation with anyons Arrow: A Modern Reversible Programming Language Superstructural reversible logic The Univalent Foundations Program: Homotopy Type Theory: Univalent Foundations of Mathematics. Institute for Advanced Study Interpretation and programming of the reversible functional language RFUN Ricercar: a language for describing and rewriting reversible circuits with ancillae and its permutation semantics Reversible computing Towards a reversible functional language A reversible programming language and its invertible self-interpreter We thank the reviewers for their extensive comments and corrections. This material is based upon work supported by the National Science Foundation under Grant No. OMA-1936353 and by an EAR grant from the Office of the Vice Provost for Research at Indiana University.