Complexity and Implementation of Nominal Algorithms Christophe François Olivier Calvès Department of Computer Science King's College London A thesis submitted for the degree of Doctor of Philosophy 1 I would like to dedicate this thesis to my loving grandparents and the teachers who believed in me ... Acknowledgements I want to express the deepest gratitude to my supervisor, Maribel Fernández, for all the support, the constant help and the dedication she put into teaching me how to be a good researcher. I wish to be one day, for my students, as nice as she has been for me. I wish to thank particularly Olivier Danvy for having invited me at BRICS in Aarhus. It was a wonderful and very rewarding experience, as much on the research level as the personal level. Without him the part of this thesis about continuations would have been much lighter. I thank Mathieu Boespug and Zoé Drey for the great time it was searching and living with them in Aarhus. I wish the stay could have continued more, but reaching the end of the PhD, time was delimited. I thank James Cheney, Murdoch J. Gabbay, Andrzej Filinski, Aad Mathijssen, Dale Miller, Andrew Pitts and Christian Urban for the precious discussions we had. The experience their shared with me helped directing my eorts in the right direction. I thank Agi Kurucz for having been my second supervisor and the nice time it was to be her teaching assistant. I thank the Engineering and Physical Sciences Research Council for the grant that enabled me to live three years studying a subject I like. Last but not least, I thank particularly Clémentine for her p atience, lo ve and the u nlimite d motivat ion she gives to me. And many thanks to Maribel, Zoé and 3 Clémentine for the proofreading. Abstract Nominal terms generalise rst-order terms by introducing abstraction and name swapping constructs. It aims to be a natural and simple way to represent systems that involve binders. -equivalence, unication, matching and rewriting can be generalized to nominal terms. This thesis shows that rst-order and nominal theories are strongly related. In particular, we extend rst-order unication algorithms, such as Patterson and Wegman's linear one and Martelli and Montanari's almost linear one, to nominal unication. We then present a modular algorithm to solve nominal -equivalence and matching problems. The approach relies on name management abstraction and stream manipulation. We show how choosing an appropriate name management strategy leads, in some cases, to linearity in time. The matching algorithm is then adapted to nominal rewriting, improving in some cases the time complexity. Handling name management, freshness constraints and specic reduction strategies simultaneously makes nominal algorithms intricate. This thesis presents a high level and modular approach to the implementation of nominal algorithms. Each nominal aspect is implemented as a monadic layer. We show how the CPS hierarchy can be used to separate the signature of each layer from its actual implementation thus enabling to use dierent interpretation functions. We also present a rewriting framework based on the previous nominal rewriting algorithm and a term navigation monadic layer based on a zipper. For each of the algorithms, an implementation is available either in the Haskell language or in the Objective Caml. Contents Acknowledgements 3 Abstract 4 1 Introduction 10 I Generalities 14 2 Presentation of Haskell and Continuations 15 2.1 Haskell Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.2 Monads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3 Continuations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3 First-Class Monadic Signatures 21 3.1 A simple example . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2 A specialized continuation monad . . . . . . . . . . . . . . . . . . 24 3.3 A more complex example . . . . . . . . . . . . . . . . . . . . . . . 25 3.4 General morphism . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.5 Monadic Tower and the CPS Hierarchy . . . . . . . . . . . . . . . 30 3.5.1 Lifting operations . . . . . . . . . . . . . . . . . . . . . . . 32 3.5.2 Well behaved Error Monad . . . . . . . . . . . . . . . . . . 33 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.6 4 Nominal Theory 36 4.1 Graphs and Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.2 Nominal Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5 CONTENTS 4.3 4.4 4.2.1 Standard Nominal Term . . . . . . . . . . . . . . . . . . . 40 4.2.2 Compact Syntax . . . . . . . . . . . . . . . . . . . . . . . 41 4.2.3 Reduced Compact Syntax . . . . . . . . . . . . . . . . . . 43 4.2.4 From Standard Terms To Compact Terms . . . . . . . . . 45 4.2.5 From Nominal to First-Order . . . . . . . . . . . . . . . . 48 Nominal Problems . . . . . . . . . . . . . . . . . . . . . . . . . . 49 4.3.1 Morphisms . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 Complexity and Implementation of Permutations and Sets . . . . 57 4.4.1 Implementation . . . . . . . . . . . . . . . . . . . . . . . . 58 4.4.2 Optimisation . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.4.3 Abstraction of sets and permutations . . . . . . . . . . . . 61 4.4.4 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . 62 II Unication 63 5 Nominal Unication Terms and Problems as Directed Acyclic Graphs 65 6 Quadratic Unication Algorithm 69 6.1 Nominal Unication as a Relation on Term Nodes . . . . . . . . . 69 6.2 A Quadratic Nominal Unication Algorithm . . . . . . . . . . . . 73 7 A Simple and Ecient Nominal Unication Algorithm 80 7.1 A Nominal Union-Find Algorithm . . . . . . . . . . . . . . . . . . 81 7.2 An Almost Quadratic Nominal Unication Algorithm . . 86 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . 91 7.2.1 (AQNU) 8 Nominal Unication via Graph Rewriting 8.1 93 A polynomial Algorithm via graph rewriting . . . . . . . . . . . . 96 8.1.1 . . . 96 8.1.1.1 Normalisation of the graph . . . . . . . . . . . . 96 8.1.1.2 Properties . . . . . . . . . . . . . . . . . . . . . . 99 A graph rewriting algorithm to solve constraints . 8.1.1.3 -rules . 8.1.1.4 Computing permutations: Neutralisation . . . . . 111 . . . . . . . . . . . . . . . . . . . . . . 106 6 CONTENTS 8.1.1.5 Putting all together . . . . . . . . . . . . . . . . 113 8.1.1.6 An upper bound on the number of iterations . . . 114 8.1.1.7 An upper bound on the number of normalisation steps . . . . . . . . . . . . . . . . . . . . . . . . . 116 8.1.1.8 Cost of the rules . . . . . . . . . . . . . . . . . . 118 8.1.2 Freshness constraints . . . . . . . . . . . . . . . . . . . . . 121 8.1.3 Cost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 8.1.4 Total cost in time for the unication algorithm . . . . . . . 124 III -equivalence, Matching and Rewriting 127 9 A modular algorithm 129 9.1 Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 9.2 Core algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 9.3 Checking the validity of -equivalence constraints . . . . . . . . . 138 9.4 Solving Matching Problems . . . . . . . . . . . . . . . . . . . . . 139 10 Implementation 141 10.1 The Freshness Layer . . . . . . . . . . . . . . . . . . . . . . . . . 142 10.2 Only one environment: The environment layer . . . . . . . . . . . 143 10.3 Combining the phases: Streams . . . . . . . . . . . . . . . . . . . 146 10.4 -equivalence and Matching . . . . . . . . . . . . . . . . . . . . . 149 10.5 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 10.6 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 11 A Simple Nominal Rewriting Framework 156 11.1 Nominal rewriting . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 11.2 A nominal rewriting algorithm: two improvements on the matching algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 11.3 Complexity of nominal rewriting . . . . . . . . . . . . . . . . . . . 161 11.3.0.1 Special cases . . . . . . . . . . . . . . . . . . . . 162 11.4 The Zipper Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 7 CONTENTS IV Conclusions 164 Related Works 165 Future Works 168 Conclusion 170 Bibliography 176 A Haskell Code 177 A.1 Base denitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 A.2 Fist-Class Monadic Signatures . . . . . . . . . . . . . . . . . . . . 180 A.3 Error Handling Layer . . . . . . . . . . . . . . . . . . . . . . . . . 185 A.4 Store Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 A.5 Nominal Terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 A.6 Sets and Permutation Layer . . . . . . . . . . . . . . . . . . . . . 198 A.7 Nominal Union-Find Algorithm . . . . . . . . . . . . . . . . . . . 210 A.8 Unication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 A.9 Environment Layer . . . . . . . . . . . . . . . . . . . . . . . . . . 228 A.10 -equivalence and Matching Algorithms . . . . . . . . . . . . . . 235 A.11 Zipper Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 B Objective CAML Code 250 B.1 Quadratic Nominal Unication 8 . . . . . . . . . . . . . . . . . . . 250 List of Algorithms 1 First-Order Linear Unication (FLU): Part 1 . . . . . . . . . . . . 74 2 First-Order Linear Unication (FLU): Part 2 . . . . . . . . . . . . 75 3 Quadratic Nominal Unication (QNU): Part 1 . . . . . . . . . . . 76 4 Quadratic Nominal Unication (QNU): Part 2 . . . . . . . . . . . 77 5 Quadratic Nominal Unication (QNU): Part 3 . . . . . . . . . . . 78 6 Tarjan's union-nd algorithm . . . . . . . . . . . . . . . . 82 7 Nominal Union-Find Algorithm (NUF): Part 1 . . . . . . . . . . . 84 8 Nominal Union-Find Algorithm (NUF): Part 2 . . . . . . . . . . . 85 9 Unication and normalizing functions . . . . . . . . . . . . . . . . 88 10 Equation storing function . . . . . . . . . . . . . . . . . . . . . . . 90 11 Interpretation of Freshness when Solving an -equivalence or matching problem (TUF) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 12 Phase 2 and 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 13 Streamed version of Phase 1 . . . . . . . . . . . . . . . . . . . . . 147 14 Streamed version of Phase 3 . . . . . . . . . . . . . . . . . . . . . 148 15 The core algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . 149 17 -equivalence Interpretation of core Matching Interpretation of core . . 18 Interpretation of Freshness for matching terms in context 16 9 . . . . . . . . . . . . . . . . . 150 . . . . . . . . . . . . . . . . . 150 . . . . . 160 Chapter 1 Introduction This thesis is the result of my three years of research as a PhD student at the Department of Computer Science at King's College London. The subject was to study nominal algorithms, both from a theoretical point of view by studying their complexity and from a practical one by studying their implementations. The notion of a binder is ubiquitous in computer science. Programs, logic formulas, and process calculi are some examples of systems that involve binding. Program transformations and optimisations, for instance, are dened as operations on programs, and therefore work uniformly on -equivalence classes. To formally dene a transformation rule acting on programs, we need to be able to distinguish between free and bound variables, and between meta-variables of the transformation rule and variables of the object language. We also need to be able to test for -equivalence, and we need a notion of matching that takes into account -equivalence. In Jones The Implementation of Functional Programming Languages shows how manipulating terms up to -equivalence [41] Peyton- is a big issue in the implementation of functional programming language. Nominal techniques were introduced to represent in a simple and natural way systems that include binders [24, 42, 48]. The nominal approach to the representation of systems with binders is characterised by the distinction, at the syntactical level, between abstracted (we use the notation variables atoms (or object-level variables), which can be [a]t, where a is an atom and t is a term), and meta- (or just variables), which behave like rst-order variables but may be 10 decorated with atom permutations. Permutations are generated using swappings (a b) t means swap a and b everywhere in t). For instance, (a b)[a]a = [b]b, and (a b)[a]X = [b](a b)X (we will introduce the notation formally in (e.g., chapter 4.2). As shown in this example, permutations suspend on variables. The idea is that when a substitution is applied to be applied to the term that instantiates X. X in As atoms are just names, we want to identify (a b)X , the permutation will [a]a and [b]b and even [a]X and [b]X with some conditions. Permutations of atoms are one of the main ingredients in the denition of -equivalence for nominal terms, they are the translation tables from one term to another. Obviously such translations are subject to conditions, [a]X and [b]X can only be identied if neither a nor b are unabstracted in X . The constant management of such translations and conditions is what makes nding and implementing good nominal algorithms not an easy task. As we will see in this thesis, we managed to show that unication of nominal terms can be done in quadratic time and that -equivalence and matching problems can even sometimes be done in linear time and space. Implementing these algorithms was not an easy task because of the amount of subtle atom management details. We found an elegant and easy way of programming with nominal terms, handling as many details as possible automatically and eciently. Such an implementation was possible thanks to the visit the author made at the BRICS [1] PhD school with Olivier Danvy. This is where the second big aspect of this thesis was developed: continuations. Chapter 3 is a result of the thought developed during the visit. The whole Haskell implementation is based on it. It enabled the implementation to be clearer, more exible and manageable. The main contributions of this thesis are: A study of relations between nominal theory and rst-order theory. We present a morphism between nominal theory and rst-order theory. This morphism enabled us to extend algorithms and properties in rst-order theory to nominal theory. A quadratic nominal unication algorithm, the most ecient unication algorithm yet. 11 An almost quadratic nominal unication algorithm: while being almost as ecient as the one above, it is also very easy to use, to maintain and to extend, which makes it often the best choice in practice. A polynomial nominal algorithm based on graph reduction: it gives inter- An ecient, very modular algorithm for nominal -equivalence and match- esting results and techniques for rewriting up to -equivalence. ing: its complexity is linear on ground problems, log-linear on linear problems, which is very frequent in practice. An implementation of monadic classes which makes them rst-class citizen of the language and makes the instantiation explicit: it makes monadic towers much simple by giving them a concrete representation. It can also bring monadic classes to some languages which do not natively support them. A nominal framework made of monadic layers handling dierent nominal aspects (permutations, sets of atoms, freshness contexts, environments, . . . ) to ease the development of program manipulating nominal terms. With is programming with nominal terms is almost as easy as programming with rst-order terms. The thesis is structured as follows: The rst part presents general results. Chapter 2 presents briey the Haskell programming, language, its syntax, monads in computer science and continuations. Chapter 3 introduces nominal theory: nominal terms with several syntaxes and how they are related, nominal problems and implementation of permutations and sets. The second part is about solving nominal unication problems. Chapter 5 shows how to represent nominal terms as directed acyclic graphs. This representation is used to present, in chapter 6 a quadratic algorithm for nominal unication based on a linear rst-order algorithm. Chapter 7 introduces a more practical and still ecient algorithm. Finally chapter 8 presents a polynomial algorithm based on graph rewriting. 12 The third part is about solving -equivalence and matching problems. Chap- ter 9 presents, in an abstract way, a very modular algorithm to solve -equivalence and matching problems. Chapter 10 describes its implementation and proves its complexity. Finally, chapter 11 presents a simple nominal rewriting algorithm based on this algorithm and a zipper. The last part of the thesis concludes and shows the actual implementation of the algorithms written in Haskell and Objective Caml. The code has been put in the appendices for two reasons: because it is the most precise description of the algorithms presented in the thesis, and as a real world example of the implementation techniques developed in the thesis. The code can also be found at [5]. 13 Part I Generalities 14 Chapter 2 Presentation of Haskell and Continuations Most of the implementations presented in this thesis are written in the Haskell programming language. This section presents briey the main aspects and the syntax of the language to enable the reader to understand the examples. For a detailed description, please refer to the Haskell 98 Revised Report [3]. The implementations use many extensions not present in the report but described in the GHC documentation [2]. To learn about the language, the Haskell web site [4] is a good entry point. Haskell is dened in the Haskell 98 Revised Report Haskell is a general purpose, as purely functional programming lan- guage incorporating many recent innovations in programming language design. Haskell provides higher-order functions, non-strict semantics, static polymorphic typing, user-dened algebraic datatypes, pattern-matching, list comprehensions, a module system, a monadic I/O system, and a rich set of primitive datatypes, including lists, arrays, arbitrary and xed precision integers, and oating-point numbers. Haskell is both the culmination and solidication of many years of research on non-strict functional languages. Purely functional means that the value of any expression in Haskell only depends on the expression itself and not the context. As a consequence, values are 15 2.1 Haskell Syntax persistent and when applied to the same arguments, a function always returns the same value. Non-strict semantics means that it is possible that an expression returns a value (not ?) even if some of its subexpressions evaluate to ?. example, let us consider the following 1 2 3 let in Haskell For code: loop () = loop () f x = 0 f ( loop ( ) ) where loop is a recursive function taking () as argument and looping on itself 0 and f is a function ignoring its argument and returning . loop () never terminates 0 but f (loop ()) still returns . 2.1 Haskell Syntax Denitions 1 Variables are dened by: var = e x p r e s s i o n where var is the variable name and expression an expression. Variables are immutable in Haskell, so variables can be seen as identiers for their expressions. In Haskell, denitions are recursive, which means that var can appear in expression. 0 For example, the following code denes an innite list of : 1 list = 0 : list Functions 1 In \ arg1 . . . argn where n > Haskell, a function is an expression of the form: > expression 0 and arg1 to argn are the n arguments of the function. The character \ represents in -calculus. Because functions are expressions, they can be assigned to variables as any expression. For example the function f dened above could have been dened by: 1 f = \x > 0 For the sake of simplicity, a simpler way to dene functions is: 1 f arg1 . . . argn = e x p r e s s i o n 16 2.1 Haskell Syntax where f is the name of the function. If an argument is not used in the body of the function, it can be replaced by _. Type declarations type is written variable by: 1 2 5 :: type . For example, list and f could have been dened Integer list :: [ ] list = 1 : list 3 4 In Haskell, declaring that the variable variable has the type f :: a > f x = 0 Integer which means list is a list of integers and f is a function whose argument can be of any type and returns an integer. Pattern-Matching Haskell supports pattern-matching. It can be used in sev- eral ways: 1 2 3 4 by a case expression: case e x p r e s s i o n where pattern1 ... patternn > expression1 > expressionn where expression, expression1, :::, expressionn are expressions and pattern1 to patternn are patterns. The value of the case expression is the value of the expression of the rst pattern (from pattern1 to patternn) to match the value of expression. 1 2 3 in a function denition: f pattern1 ... f patternn = expression1 = expressionn which corresponds to 17 2.1 Haskell Syntax 1 f x = 2 3 4 case of x pattern1 ... patternn > expression1 > expressionn User-dened algebraic datatypes Users can dened their own algebraic datatypes. The syntax is: 1 2 3 4 data DataTypeName a r g s = Constructor1 args1 | ... | Constructorp argsn where DataTypeName is the name of the data type, args a list of type variables, Constructor1 to Constructorn n constructors where args1 to argsn are the lists of the types of the arguments of their corresponding constructor. For example, the following code denes a parametrized binary tree data type: 1 2 3 4 data Tree l e a f t y p e = Empty | Leaf l e a f t y p e | Node ( Tree l e a f t y p e ) ( Tree l e a f t y p e ) Type classes Type classes are the Haskell way to implement ad-hoc polymor- phism. Let us consider the equality function (==), which takes two arguments and returns whether or not they are equal. In 1 2 3 this function has the type Bool. (Eq a) => is a type constraint, it means that the type a has to be an instance of the class Eq which is dened as: class Eq a where (==) : : a > a > Bool (/=) : : a > a > Bool which means that to be an instance of Eq, a has to provide the two functions (==) and (/=), both of type a > a > Bool. (==) :: ( Eq a) =>a Haskell >a > 18 2.2 Monads 2.2 Monads Monads are a general way to represent hidden computation [36]. In Haskell, among other things, they provide a simple way to implement side eects. A monad in Haskell is a datatype which is an instance of the Monad class (simplied version): 1 2 3 class Monad m where return : : a (>>=) :: m a > (a > m b) >m a >m b The idea is that you can inject a value into a monad (return) and bind a monad with a function ((>>=)) but you cannot, in general, retrieve a value from a monad. For example, the datatype of lists is a monad: 1 2 3 instance Monad [ ] where return x = [ x ] m >>= f = concat (map f m) return x returns the list containing only the element x and (>>=) maps every element of the list m by the function f of type a > [b] and returns the list of all the generated elements. IO. Input/Output operations are implemented in Haskell via an abstract monad On the contrary to most programming languages, input/output operations have to be executed only in the IO monad and nowhere else. 2.3 Continuations Continuations were introduced by Landin [32] to be the functional counterpart of jumps in programming languages (GOTO). They have known since a big development. In this thesis we will not use any of the many control operators that have been introduced since Landin's J operator but plain and simple code in con- tinuation passing style in the form of a slight variation of the usual continuation monad (simplied version): 1 data Cont r a = Cont ( a > r) > r 19 Haskell 2.3 Continuations which means that a code in continuation passing style which takes a function as argument called the continuation and returns value. The continuation rep- resent the rest of the computation. For example, the following code just gives the value 1 5 to the rest of the computation: g i v e 5 = Cont (\ k > k 5) The following code, if the second argument is 0 does not execute the rest of the computation, instead it returns directly a string saying there was an error: 1 2 3 div x y = Cont $ \k > if y == 0 " d i v i s i o n by z e r o " k (x / y) then else Code in continuation passing style can decide whether or not they want to continue the computation and even how many times to do it. For a more detailed description of continuation passing style (CPS) and how to how to transform any code into CPS, please refer to [15, 16, 46]. 20 Chapter 3 First-Class Monadic Signatures A monadic signature is an abstract monad along with a set of operations on it. The usual way of doing it is, in Haskell, to dene a class. In Objective would declare a module signature rst-class citizens in either CAML, we even if it is not the same thing. Classes are not Haskell or Objective CAML. We present a way to do so in Haskell. The implementation relies on the use of existential quantication in datatypes, rank n polymorphism and higher kinded type constructors. be possible to do the same in That is why we present it in Objective Caml, but not as easy as with It might Haskell. Haskell. The idea is similar to defunctionalization [17, 46]. The abstract monad op- erations are represented by a datatype, the abstract monad being implemented as a concrete continuation monad. As in defunctionalization, concrete instantiations of the monad are implemented by an interpretation function. 3.1 A simple example In this example we consider a class with logging capabilities. It contains a single operator write taking the string to log and returning the number of char written. The usual way of doing it in Haskell is the following: 1 2 class (Monad m) => Log m where w r i t e : : String > m Int The constraint (Monad m) => ensures that m is also a monad. Let us consider a piece of code using write 21 3.1 A simple example 1 2 3 4 => Int t e s t : : ( Log m) m test = x < w r i t e " F i r s t w r i t e \n" y < w r i t e " Second w r i t e \n" (x + y) do return Indeed the constraint (Log m) => ensure that m is an instance of Log. Two instances of Log could be: the state monad with a string state (State write s would append s to the state and the IO monad ( String), IO), write s would print s on the standard output: 1 2 3 4 5 instance String ) where Log ( S t a t e w r i t e s = modify (\ l instance Log write s = > l ++ s ) >> return ( length IO where putStr s >> return ( length s) s) and the monad m in test can be instantiated as any of them: 1 2 testState : : State testState = test 3 4 5 String Int IO Int testIO : : testIO = t e s t Let us dene writeState and writeIO as 1 2 3 4 5 String > State String > writeState : : writeState s = write writeIO writeIO :: s = write String Int IO Int We present now another way to implement and instantiate the class Log without class constraints, based on continuations. The Log class is implemented as the datatype : 1 data DLog r = DWrite String ( Int > r) and the abstract previous monad m satisfying the class Log is implemented as the concrete continuation monad. The idea is, when an operation of the class is called, to stop the computation and return the constructor corresponding to the invoked call, with its arguments and the captured continuation. DWrite would be something like 22 3.1 A simple example 1 2 d w r i t e s t r i n g = Cont $ \ c o n t i n u a t i o n > DWrite s t r i n g c o n t i n u a t i o n Unfortunately this is not that simple. Returning DWrite string continuation requires that r is of type DLog r. Furthermore, the rest of the computation may contains other calls, so other DWrite string' continuation' and nally, if it exists, the nal value of the computation. To make all this work nely we need to introduce a data type for such a sequence of calls possibly ended by a nal value: 1 2 3 data SCall datatype f i n a l v a l u e = End f i n a l v a l u e | C a l l ( datatype ( SCall datatype f i n a l v a l u e ) ) when datatype is DLog, it gives (in pseudo Haskell) : 1 2 3 data SCall DLog f i n a l v a l u e = End f i n a l v a l u e | C a l l ( DWrite ( String Int > SCall DLog f i n a l v a l u e ) ) what is what we need. dwrite is then implemented as 1 2 String Int dwrite : : > Cont ( SCall Log f i n a l v a l u e ) d w r i t e s t r i n g = Cont (\ cont > C a l l ( DWrite s t r i n g cont ) ) Instantiation is made by an interpretation function for DWrite string continuation. This function actually writes string and resume the continuation with the returning value. For example, the interpretation functions for the two previous instances (State 1 2 String and IO) are: : : DLog a > S t a t e ( DWrite s k ) = r < interpretIO interpretIO : : DLog a > ( DWrite s k ) = do 3 4 5 6 String interpretState interpretState IO a do 7 a writeState s (k r ) return r < writeIO s (k r ) return the nal building block is an instantiation function. It takes two arguments: an interpretation function (such as interpretState and interpretIO) and an abstract computation and interprets the stream of calls with the interpretation function, thus instantiating the computation: 23 3.2 A specialized continuation monad 1 2 3 4 Monad => instantiate : : ( m) ( t h e c l a s s ( SCa ll t h e c l a s s a ) > Cont ( SCall t h e c l a s s a ) a >m a > m ( SCall t h e c l a s s a ) ) 5 6 7 8 9 i n s t a n t i a t e i n t e r p r e t t = aux ( runCont t End) aux (End r) = r aux ( C a l l c ) = n < interpret c aux n where return do For example test is simply 1 2 3 4 do return and instantiated with 1 2 5 String dtestState : : State a dtestState = instantiate interpretState dtest 3 4 Int d t e s t : : Cont ( SCall DLog e ) dtest = x < d w r i t e " F i r s t w r i t e \n" y < d w r i t e " Second w r i t e \n" (x + y) IO dtestI O : : a dtestI O = instantiate interpretIO dtest 3.2 A specialized continuation monad We introduce a variation of the continuation monad, specialized to our needs. We have seen in the previous section that the continuation was of type a > SCall datatype nal nal can also be universally quantied. 8final (a ! SCall datatype final) ! SCall datatype final which gives in 1 2 Haskell newtype SCont datatype a = SCont { unSCont : : f o r a l l r . ( a > SCall datatype r ) > SCall datatype r ) } SCont theclass is a monad: 24 3.3 A more complex example 1 2 3 instance Monad return a ( SCont t h e c l a s s ) = SCont $ \k ( SCont m) >>= f = SCont $ \k where > k a > m (\ x > unSCont ( f x ) k ) and return a >>= f m >>= return (m >>= f ) >>= g , fa , m , m >>= (x ! fx >>= g) This a particular case of the general continuation monad: 1 2 newtype LContT m a = LContT { unLContT : : forall r . (a > m r ) > m r } which is just the usual ContT continuation monad transformer (see section 3.5) with universally quantied return type. Jaskelio calls in [28] this monad the codensity monad. Jaskelio's coden- sity monad seems to come from considerations in category theory. For the scope of this thesis, this is just a continuation monad. 3.3 A more complex example Let us consider a monadic class for exception handling: 1 2 3 class Monad m => MonadError m error where throwError : : error > m a c a t c h E r r o r : : m a > ( error > m a ) > m a This is not the real library Haskell MonadError dened in the Monad transformer but a simplied version of it without error messages. The semantic of throwError and catchError is not important here as our only concern is correctly implementing the signature and instantiation. As before we want to implement the class MonadError as a data type DMonadError and throwError and catchError as 1 2 error dthrowError SCont $ \k = > C a l l ( DThrowError dcatchError v h SCont $ \k = > C a l l ( DCatchError v h error 3 4 5 25 k) k) 3.3 A more complex example error is a parameter of MonadError, it has to be also a parameter of DMonadError. a is a universally quantied type variable in throwError and catchError, it has to be also universally quantied in DThrowError and DCatchError. Furthermore, because the abstract monad m satisfying MonadError will be implemented as the concrete error) monad. The arguments of dcatchError have to be of type SCont (DMonadError error) a and error > SCont (DMonadError error) a instead of m a and error > m a. The datatype implementing MonadError is then: data DMonadError error r = SCont (DMonadError 1 2 3 4 5 f o r a l l a . DThrowError ( a > r ) | f o r a l l a . DCatchError ( SCont ( DMonadError ) a) ( > SCont ( DMonadError (a > r ) error error error ) a) In practice it is easier to dene DMonadError as 1 2 3 4 5 data error DMonadError mc r = f o r a l l a . DThrowError ( a > r ) | f o r a l l a . DCatchError (mc a ) ( > mc a ) (a > r ) error and modify SCall to instantiate mc to SCont theclass: 1 2 3 4 5 data SCall datatype f i n a l v a l u e = End f i n a l v a l u e | C a l l ( t h e c l a s s ( SCont datatype ) ( SCall datatype f i n a l v a l u e ) ) As expected dthrowError and dcatchError are 1 2 3 error error = dthrowError : : dthrowError SCont $ \k > SCont ( DMonadError > C a l l ( DThrowError error ) error a k) 4 5 6 7 8 9 error error d c a t c h E r r o r : : SCont ( DMonadError ) a > ( > SCont ( DMonadError ) a) > SCont ( DMonadError ) a d c a t c h E r r o r v h = SCont $ \k > C a l l ( DCatchError v h error error k) The interpretation function follows the same principles as before but we can not directly give the arguments stored by dcatchError to catchError because 26 3.4 General morphism dcatchError arguments are of types SCont (DMonadError ( error > SCont (DMonadError m a and error error) a and error) a whereas catchError accepts arguments of types > m a where m is an abstract monad satisfying the MonadError class. So we have to map arguments from SCont (DMonadErorr error) to m what is exactly what instantiation is. The interpretation function is then: 1 2 3 4 5 error i n t e r p r e t E r r o r : : ( MonadError m) DMonadError ( SCont ( DMonadError ( SCall ( DMonadError > m ( SCall ( DMonadError ) a) error error 6 7 8 9 i n t e r p r e t E r r o r ( DThrowError v < throwError $ k v do error return error => error ) ) error ) a ) k) = 10 11 12 13 14 15 i n t e r p r e t E r r o r ( DCatchError m h k) = m' = instantiate interpretError h' e = instantiate interpretError $ v < c a t c h E r r o r m' h ' $ k v let in do m h e return Obviously the instantiation function from SCont (DMonadError error) to m is 1 2 error m) => error ) a > m a i n s t a n t i a t e E r r o r : : ( MonadError SCont ( DMonadError 3 4 instantiateError = instantiate interpretError 3.4 General morphism On a general manner we can see all previous monadic operations as 1 op :: 8: F m!my where F is a functor from the category of monads to a category In the case of write, C C. is the category of strings (String). For throwError, C is the categories of values of type error. For catchError, C is the category of values of type 27 3.4 General morphism (m a , error > m a) . write (resp. throwError) takes the same arguments of dwrite (resp. dthrowError) because in this case =>F m = F (SCont DLog) (resp. (MonadError error m) =>F m = F (SCont (DMonadErorr error)) (Log m) . But in the case of catchError, (MonadError error m) ) F m 6= F (SCont (DMonadErorr error)) so to interpret DCatchError we had to map the arguments from SCont (DMonadError error) = instantiate interpretError = instantiate interpretError $ m h e to m. The piece of code 1 2 3 let in m' h' e ... is actually equivalent to 1 let (m' , h ' ) = F ( i n s t a n t i a t e i n t e r p r e t E r r o r ) (m , h ) in ... because instantiate interpretError is a function of type (MonadError error m) =>SCont (DMonadError error m) a =>m a so F ( instantiate interpretError) maps arguments of dcatchError to arguments of catchError. Let us formalize the transformation and consider the following class: 1 2 3 4 class Monad m ) TheClass m where; op1 :: 8 1 : F1 m ! m y1 ; : : :; opp :: 8 p : Fp m ! m yp where (Monad m) => indicates that m has to be a monad. opi are the p operations that m has to implement. and i are lists of distinct type variables. 28 3.4 General morphism opi 1 :: 8(m :: ! ) i : (TheClass m overalpha) ) Fi m ! m yi are class parameters common to every opi whereas i are not parameters of the class but specic to every opi . Such a class is called a monadic class signature. The complete type of each opi is The class TheClass is implemented as the algebraic data type with existential quantication TheDataType, called a monadic signature datatype: data DataType n r =; 8 1 : Op1 (F1 n) (y1 ! r); j ::: ; j 8 p : Opp (Fp n) (yp ! r) 1 2 3 4 Every Opi is the constructor corresponding to opi . It has type Opi 1 :: 8 n r i : Fi n ! (yi ! r) ! DataType n r The general form of SCall and SCont are 1 2 3 4 5 6 7 data SCall datatype e = End e | C a l l ( t h e c l a s s ( SCont datatype ) ( SCall datatype e ) ) newtype 8 9 } SCont datatype a = SCont { unSCont : : f o r a l l e . ( a > SCall datatype e ) > SCall datatype e For the correctness of the transformation, using directly the constructors End and Call will be forbidden. The following function call will be used to implement monadic calls: 1 2 3 c a l l : : ( f o r a l l r . ( a > r ) > datatype ( SCont datatype ) r ) > SCont datatype a c a l l a = SCont (\ k > C a l l ( a k ) ) every op_i is then implemented as: 29 3.5 Monadic Tower and the CPS Hierarchy :: 8 i : Fi (SCont datatype) ! SCont datatype yi; t = call (Opi t) 2 dopi dopi 1 interpret (Opi 1 2 t) = do r opi (Fi (instantiate interpret) t); return (k r) and interpretation functions as: The instantiation function becomes: 1 2 3 4 5 6 7 8 9 Monad => instantiate : : ( m) ( datatype ( SCont datatype ) ( SCall datatype a ) > m ( S Call datatype a ) ) > SCont t h e c a l l a > m a i n s t a n t i a t e i n t e r p r e t t = aux ( unSCont t End) aux (End r) = r aux ( C a l l c ) = n < interpret c aux n where return do 3.5 Monadic Tower and the CPS Hierarchy Monad transformers [29] are a way to build a new monad on top of another one. It enables us to combine monads and thus their eects instead of having to dene a monolithic monad. For example, let m be a monad. To add a global state to m, we can simply use the StateT monad transformer dened in the 1 newtype StateT s m a = StateT { runStateT : : s GHC library as: > m (a , s ) } StateT s m is the monad with as a global state of type s and two operations get and put to fetch the state or put one. Because it has been made from m, it has the behavior or m. We can use another monad transformer on it: for example, to give StateT s m the capabilities of the list monad, we can use the ListT transformer: 1 newtype ListT m a = ListT { runListT : : m [ a ] } ListT (StateT s m) is then a monad with the behavior of m, a global state and multiple computation branch capabilities. Monad transformers provide a function 30 3.5 Monadic Tower and the CPS Hierarchy to lift any computation in a monad m (m a) to a computation in the transformed monad (t m a) where t is the transformer. For example, for StateT, the lifting function is: 1 2 3 4 Monad => lift :: ( m) m a > StateT s m a l i f t m = StateT $ \ s > a < m (a , s do return Stacking transformers this way gives what is called a transformer in the tower is called a layer. monadic tower. Each The lowest layer is the initial monad and the uppermost layer is the transformer at the top of the stack. The transformation to rst-class monadic signatures can be extended very easily to monad transformers. The implementation can be found in appendix A.2 and practical examples of monadic towers made of rst-class monadic signatures are all around the thesis and the implementation. Let us consider an example of tower made of monadic classes and its transformed version using rst-class monadic signatures. Let us take Log as the lowest layer, then add a monad transformer implementing a state monad and nally one implementing exception handling: 1 exampleoftower :: ( 2 3 4 => 5 6 7 MonadState MonadTrans MonadError MonadTrans Log m) t ( t1 m) [ s < get s lift $ lift $ exampleoftower = do 8 9 10 if null then else Char ] ( t ( t1 m) ) , Char ] ( t1 m) , [ t, [ t1 , Char ] throwError "empty s t a t e " l i f t $ write s t and t1 are monad transformers, t1 implements a MonadError, t implements a MonadState and m has to satisfy Log. The same example can be expressed with out approach: 1 2 3 4 dexampleoftower : : SContT ( DState ( SContT ( DMonadError ( SCont DLog dexampleoftower = s < dgetT do 31 [ [ Char ] ) Char ] ) )) [ Char ] 3.5 Monadic Tower and the CPS Hierarchy if null s then l i f t $ else l i f t $ return "Ok" 5 6 7 8 dthrowErrorT "empty s t a t e " l i f t $ dwrite s We believe it to be much easier to read and in practice and because rst-class monadic signatures use explicit instantiation, it also makes the life of the typing inference algorithm better. Remark 1 First-class monadic signatures being implemented via continuations. Towers made of First-class monadic signatures are actually in the chy [9, 18] CPS hierar- 3.5.1 Lifting operations To catch the error thrown in the previous example, we would naively write: 1 c a t c h E r r o r exampleoftower (\ _ > return "Not OK" ) Unfortunately it does not work because exampleoftower is not of type (t1 m) [Char] but t (t1 m) [Char]. It is even clearer with dexampleoftower: dcatchError expects a SContT (DMonadError error) DOTS but dexampleoftower is a SContT (DState [Char]) DOTS. Fortunately there exists a technique to lift monadic operators. Let us consider a monadic operation op over a monad m: op :: 8 : m a ! m a and a monad transformer t. We want to lift op to t m. op does not accept arguments of type t m a but it does accept arguments of type m (t m a). The return value is then also of type m (t m a). By lifting it we get t m (t m a). We only have to join it to get a value of type t m a. The lifting operation is then for op: 1 liftedOp t = join $ l i f t $ op ( return t) On a general manner, for a monadic operation op: op :: 8 : F m ! m y the lifted operation is 32 3.5 Monadic Tower and the CPS Hierarchy 1 liftedOp t = join $ l i f t $ op (F return t) This technique has been used by Filinski [23] to implement its monadic layers. The composition join . lift corresponds to glue function. For catchError we get: 1 2 3 Monad liftedCatchError : : ( ( t m) , MonadTrans t , MonadError e m) t m a > (e > t m a) > t m a liftedCatchError t h = $ l i f t $ catchError ( t) ( join return => return . h) 4 5 6 7 8 9 10 11 12 13 14 Monad => liftedDCatchError : : ( m) SContT theclass ( SContT ( DMonadError ) m) a > ( > SContT t h e c l a s s ( SContT ( DMonadError > SContT t h e c l a s s ( SContT ( DMonadError ) m) a liftedDCatchError t h = $ liftSContT $ dcatchErrorT ( error error error ) join m) a ) error return t) ( return . h) what is exactly what we want. Unfortunately, even it the types are correct, this transformation is not correct for every monad. It is not for the Maybe, Either error and ErrorT error m monads provides in the Monad Transformer Library. Jaskelio presents in [27, 28] a general lifting way. We present in the next section a way to get the monad right right using backtracking. It seems that Jaskelio's approach is the categorical general counterpart of the backtracking ad-hoc solution. 3.5.2 Well behaved Error Monad The usual error monad is as follows 1 2 data Either e r r r o r value = | Left error Right v a l u e and its implementation is 1 2 3 instance Monad ( Either error ) where return v = Right v m >>= f = case m of 33 3.5 Monadic Tower and the CPS Hierarchy Left error Right v a l u e 4 5 Left error > > f value Let us introduce a very simple monad transformer, the identity transformer: 1 2 newtype I d e n t i t y T deriving (Monad) m a = IdentityT { runIdentityT : : m a } 3 4 5 6 7 8 l i f t I d e n t i t y T : : m a > IdentityT m a l i f t I d e n t i t y T = IdentityT instance MonadTrans I d e n t i t y T l i f t = liftIdentityT where Now let us try to lift catchError to IdentityT (Either error) 1 2 3 4 5 6 7 liftedCatchError t h = $ l i f t $ catchError ( = $ l i f t $ catchError ( = $ lift $ t = $ IdentityT ( t) = IdentityT ( t ) >>= (\ x = t join join join join return t ) ( return . h ) Right t ) ( return . h ) Right Right Right > x) so whatever t is, liftedCatchError will never catch the exception. Let us assume that t is an exception. right returning Right t. Right t is not one, so catchError (Right t) (return . h) is The problem is the computation should backtrack when t is reached so that h can be called. Because continuations are very well suited for backtracking, we add a continuation layer to the monad: the well behaved Error error monad is thus dened as LContT (Either error). throwError is straightforward: 1 2 error throwError : : > LContT ( throwError e = l i f t $ e Left Either error ) a catchError m h runs m and if and exception is raised, backtracks on h. The backtracking is done by running m and h with the current continuation where catchError is called: 1 catchError : : 2 3 4 tryErrorT Either error ) a error Either error ) Either error ) a LContT ( > ( > LContT ( > LContT ( m h = LContT $ \k > 34 a) 3.6 Conclusion 5 6 7 case unLContT m k of Left e > unLContT Right v > Right v (h e ) k Now the operations of the exception handling layer can be lifted to any layer. 3.6 Conclusion We have seen in this chapter how to implement monadic signatures via the CPS Hierarchy and and how a continuation monad can transform a problematic monad into a well behaved one. The Haskell code provided in appendices relies a lot on rst-class monadic signatures. One of the goals of doing so was to prove that, using it, even towers with lots of layers can still be readable and manageable. Another positive aspect of the approach is eciency: using the continuation monad to implement a given monad can be faster if there are few calls to the operations of the monad. 35 Chapter 4 Nominal Theory This chapter introduces nominal terms and problems. We present several syntaxes for nominal terms. Each of them has its own benets. We show how theses syntaxes are related and that they are all dierent facets of the same objects: terms with names. 4.1 Graphs and Trees Denition 1 A directed graph is a pair (Sn ; Se ) where Sn is a set of elements called nodes and Se Sn Sn a set of edges. A edge e is a pair of nodes (o; d) written o !e d. o is the origin and d the destination of the edge. e is said to be an incoming edge for d and an outcoming edge for o. An undirected graph is a graph whose edges (o; d) and (d; o) are identied. Its edges have neither origin nor destinations but two extremities (without order). A root of a directed graph is a node without incoming edge. A leaf is a node without outcoming edge. Examples of graphs are given in section 5 Denition 2 A path in the graph is a sequence (n1 !e n2); (n2 !e n3); : : : ; (nl 1 !e nl ) such that the origin (resp. destination) of an edge is the destination (resp. origin) of the previous (resp. following) one in the sequence. For example (n1 !e n2); (n2 !e n3); (n3 !e n4) 36 4.2 Nominal Terms is a path but is not. n1 (n1 !e n2); (n3 !e n4); (n2 !e n3) is called the origin of the path and nl its destination. Denition 3 A directed acyclic graph is a directed graph without cyclic path. A tree is a directed acyclic graph with exactly one root and every other node has exactly one incoming edge. Size Denition 4 (Size) The size of an object e is written j e j. Denition 5 The size of a tuple (e1 ; : : : ; en ), a sequence e1 ; : : : ; en , a list [e1 ; : : : ; en ] or a set S , when considered as mathematical objects, is dened as j (e1; : : : ; en) j=j e1 j + + j en j j e1; : : : ; en j=j e1 j + + j en j j [e1; : : : ; en] j=j e1 j + + j en j j S j= Px2S j x j When considered as concrete objects (in the implementation), their size is the size of their representation. Denition 6 The size of a graph d = (Sn ; Se ) is dened as j d j=j Sn j + j Se j 4.2 Nominal Terms In this section, we present several syntaxes of nominal terms. Let be a denu- function symbols f , g, . . . ; X a denumerable set of variables X; Y; : : :; and A a denumerable set of atoms a; b; : : : We assume that , X , and A are pairwise disjoint. , X , and A are dened for the whole thesis: unless merable set of stated otherwise, all atoms (resp. variables or functions) will be considered as elements of A (resp. X or ). 37 4.2 Nominal Terms Denition 7 (Size) f is dened as The size of an atom a, a variable X or a function symbol j a j=j X j=j f j= 1 In the intended applications, variables will be used to denote meta-level variables (unknowns), and atoms will be used to represent object level variables. swapping is a pair of (not necessarily distinct) atoms, written (a b). Permutations are lists of swappings, generated by the grammar: A ::= Id j (a b) where Id is the identity permutation. We write 1 for the permutation ob- tained by reversing the list of swappings in . We denote by 0 the permutation containing all the swappings in followed by those in 0 . Set set of permutations is written . Denition 8 (Size) The size of a swapping (a b) is dened as j (a b) j= 2 Because permutations are sequences of swappings, their size is the size of sequences. Denition 9 (Terms) In all the syntaxes presented here, nominal terms, denoted s; t; u; : : : , are trees made of atoms, variables, function symbols, abstractions [a]t, tuples and permutations. A(t) (resp V(t)) denotes the set of elements of A (resp. X ) that occur in t. A term t is ground if V(t) = ;. For example, [a]a is a ground term, and V([a](X; X )) = fX g. and a variable X is called a suspended variable, written X ; we say that is suspended on X . The application of a permutation on a term t is denoted t. A pair of a permutation Denition 10 (Size) The size of an abstraction ned as j [a]t j= 3+ j a j + j t j 38 [a]t or a suspension t is de- 4.2 Nominal Terms j t j= 1+ j j + j t j The denition of the size will depend on the syntax but will always be the size of the graph representing them. A substitution over the set of terms T is generated by the grammar: ::= Id j [X 7! t] where t 2 T . ST denotes the set of substitutions over permutation, written 0 , is dened as: T. The composition of = 0 = [X 7! t]( 0) Denition 11 (Size) The size of [X 7! t] is dened as Id 0 ([X 7! t]) 0 j [X 7! t] j= 3+ j X j + j t j Because substitutions are sequences, their size is the size of sequences. Substitutions can be applied to terms: Denition 12 over T Let T be a term set. is a function from ST T to A T substitution application function such that 8t 2 T t Id = t 8t 2 T ; ; 0 2ST t ( 0) = (t ) 0 where t denotes the application of will be written t. on t. When there is no ambiguity, t = [X1 7! t1] : : : [Xn 7! tn]. is called a standard (resp. n compact, reduced, encoded, . . . ) substitution if (ti )i=1 are terms of the standard Denition 13 Let (resp. compact, reduced, encoded, . . . ) syntax. Denition 14 For any function f on T , f can be extended to ST by f ([X1 7! t1 ] : : : [Xn 7! tn ]) = [X1 7! f (t1 )] : : : [Xn 7! f (tn )] 39 4.2 Nominal Terms Permutations will act top-down and accumulate on variables whereas substitutions act on variables. nominal structure the triple (T ; ; Denition 15 We call a set of terms, a function from ). T denotes the T to T representing the application of a permutation on a term, written t. And a substitution application function over T . Denition 16 A morphism from the nominal is a function ' from T1 to T2 such that structure (T1; 1; 1) to (T2; 2; 2) 8t 2 T1; 2 '( 1 t) = 2 '(t) and 8t 2 T1; 2ST1 '(t 1 ) = '(t) 2 '() where '([X1 7! t1 ] : : : [Xn 7! tn ]) = [X1 7! '(t1 )] : : : [Xn 7! '(tn )] 4.2.1 Standard Nominal Term Standard Nominal terms, or just standard terms for short, are trees built from atoms, suspended variables, tuples, abstractions, and function applications. More precisely, standard terms are generated by the grammar: s; t ::= a j X j (s1 ; : : : ; sn ) j [a]s j f t We follow standard notational conventions, omitting brackets and abbreviating Id X as X when there is no ambiguity. The set of standard nominal terms is written Ts . For example: (a; X; f c), [a]f ((a b)X; Y; [c](c; d)), and [a](c d)(d e)X standard nominal terms but [X ]a is not. A() and V() are dened on standard terms inductively by: A(a) = fa g A((a1 a2 ) : : : (an 1 an )X ) = fa1 ; : : : ; an g A(f t) = AS(nt) A((t1 ; : : : ; tn )) = i=1 A(ti) A([a]t) = fag [ A(t) 40 are 4.2 Nominal Terms and (a) ((a1 a2) : : : (an (f t) ((t1; : : : ; tn)) ([a]t) V V V V V 1 an )X ) = ; = fX g = VS(nt) = i=1 V(ti) = V(t) on standard term t is dened by induction: Id s t = t and ((a b) ) s t = (a b) s ( s t), Application of permutation where (a b) s a (a b) s b (a b) s c (a b) s (X ) (a b) s (f t) (a b) s [n]t (a b) s (t1; : : : ; tn) = = = = = = = We dene the instantiation of a duction: where b a c c 62 fa; bg ((a b) )X f (a b) s t [(a b) s n](a b) s t ((a b) s t1; : : : ; (a b) s tn) standard term t by a substitution if by in- t Id = t and t[X 7! s] = (t[X 7! s]) a[X 7! s] ([a]t)[X 7! s] (X )[X 7! s] (Y )[X 7! s] (t1; : : : ; tn)[X 7! s] (ft)[X 7! s] Denition 17 = = = = = = a [a](t[X 7! s]) s s Y (t1[X 7! s]; : : : ; tn[X 7! s]) f (t[X 7! s]) Ts with s and the above substitution application function forms a nominal structure called the standard nominal structure 4.2.2 Compact Syntax We present here another way to dene nominal terms. terms are terms generated by the following grammar: s; t ::= a j X j f Compact nominal j (t1; t2) j [a]s j t This grammar is called the compact syntax. Variables, atoms and constants are called leaves. t is called a suspended term. 41 Leaves, abstraction and pairs 4.2 Nominal Terms are called Tc. non suspended terms. The set of compact nominal terms is written (a; X ), [a](e f )(f; ((a b)X; (Y; [c](c; d)))), and [a](c d)(d e)(X; c) are compact nominal terms but f a and (a; b; X ) are not. Denition 18 Let t be a compact nominal term then n (t) denotes the number of nodes in t the number of subterms of the form u) A() and V() are dened on compact terms inductively by: A(a) = fa g A(X ) = ; A(f ) = ; A((a1 a2 ) : : : (an 1 an )t) = fa1 ; : : : ; an g [ A(t) A((t1 ; t2 )) = A(t1) [ A(t2) A([a]t) = fag [ A(t) For example: and (a) = ; (f ) = ; (X ) = fX g ((a1 a2) : : : (an 1 an)t) = V(X ) ((t1; t2)) = V(t1) [ V(t2) ([a]t) = V(t) In the standard syntax, s t is not a term but denotes the application of on t as dened above but in the compact syntax t is the compact term made V V V V V V of the pair of the permutation a permutation c t = t. and the compact term t. The application of on a term t, written c t is simply the syntactic operation Substitution application is dened inductively by: t c Id = t and t c [X 7! s] = (t c [X 7! s]) where a (t1; t2) ([a]t) f (t) X c c c c c c [X 7! s] [X 7! s] [X 7! s] [X 7! s] [X 7! s] [X 7! s] c = a = (t1 c [X 7! s]; t2 c [X 7! s]) = [a](t c [X 7! s]) = f = c (t c [X 7! s]) = s 42 4.2 Nominal Terms Tc Denition 19 with permutation application and substitution application as above forms a nominal structure called the compact nominal structure. 4.2.3 Reduced Compact Syntax Let RedudeCompactT erms be the following reduction system on compact nom- inal terms: t a f (t1 ; t2 ) [a]t (0 t) Proposition 1 Proof Let t ) ) ) ) ) ) Idt (applied only once, before any other rule!) (a) f (t1; t2) [(a)](t) ( 0)t This system always terminates and is conuent be a compact term. Let subterm position in the tree of w be the function associating to each t a natural integer. w is dened by the following equations: w(a) = w (f ) = w (X ) = 0 w((u1 ; u2 )) = 1 + w(u1 ) + w(u2 ) w([a]u) = 1 + w(u) w(u) = w(u) P Let us consider the measure m(t) = ( u2P w(u); j P j) where P is the set of subterms of t of the form :. with the usual lexical ordering on pairs of integers. All the rules make the measure to decrease: w(a) = w((a)) = 0 and the number of suspended subterm decreases w(f ) = w(f ) = 0 and the number of suspended subterm decreases w((t1 ; t2)) = 1 + w((t1 ; t2 )) w([a]t) = 1 + w([(a)](t)) So the system terminates. 43 4.2 Nominal Terms The system is also locally conuent. The critical pairs are joinable: (0 a) (0 f ) (0 (t1 ; t2 )) (0 [a]t) (0 (00 t)) =) =) =) =) =) ( 0)(a) f (( 0)t1; ( 0)t2)) [(( 0)(a)](( 0)t) ( 0 00)t So the system is conuent. Normal forms are terms generated by the grammar s; t ::= a j X j f reduced compact terms. and called written Tr . For example: (a; IdX ), [a](a; b) (a b)a, [e](c d)(X; a) is not. j (r1; r2) j [a]s The set of reduced compact terms is are reduced compact nominal terms but When reduction rules are only applied on the head of the term, head normal forms are called head reduced compact nominal terms the grammar: where t, t1 and s ::= a j X j f t2 and correspond to j (t1; t2) j [a]t are not head reduced compact terms but ordinary nominal compact terms. The set of head reduced compact nominal terms is written Th . For example: a a b a; X and f; a b a are nominal terms but a b f; X [ ]( )( is not. th ). ) ( ( ) ) ( )( Let t be a term, its normal form (resp. head normal form) is written ) tr (resp. Reduced terms and head reduced terms being compact terms, the denitions of A () and V() for compact term hold for reduced and head reduced terms. Remark 2 Reduced compact nominal terms are standard nominal terms Denition 20 The application of a permutation on reduced compact nominal terms is dened as =t c r . r t = tr . t r reduced nominal Substitution application is dened as (Tr ; r ; r ) forms a nominal structure called the 44 4.2 Nominal Terms structure. '(t) = tr is a morphism from the compact nominal structure to the reduced nominal structure. t be a compact nominal structure and a permutation: '( c t) = c tr = tr = r '(t). Let t; s 2 Tc ; X 2 X : Proof Let '(t r [X 7! s]) = t r [X 7! s]rr = t c [X 7! s]r r = tr c [X 7! s]r r = '(t)'([X 7! s]) = '(t) r '([X 7! s]) 4.2.4 From Standard Terms To Compact Terms t be a standard nominal term. t can be encoded as a compact term with 3 new function symbols O, C and F not in : O means Open Tuple, C Close Tuple and F Function. Let The encoding is dened by: (a) (X ) ([a]u) (f u) ((t1 ; : : : ; tn )) = = = = = a X [a](u) (F; (f; (u))) (O; ((t1); (: : : ; ((tn); C )))) (O; (t1; (: : : ; (tn; C )))) is the usual encoding of list by nested pairs. For example (()) = (O; C ), ((t)) = (O; (t; C )), ((t1 ; t2 )) = (O; (t1 ; (t2 ; C ))) and so where on. are called compact encoded standard nominal encoded terms for short. They are a particular case of reduced Terms in the image of terms, or terms (and so of compact terms) generated by the following grammar called the compact encoded standard nominal syntax, or encoded syntax for short: s; t ::= a j X j [a]s j (F; (f; t)) j (O; (t1 ; (: : : ; (tn ; C )))) 45 4.2 Nominal Terms 62 fF; O; C g. The set of encoded terms is written Te. For example ((f X; a; Y )) = (O; ((F; (f; (IdX ))); (a; (IdY; C )))) Encoded terms being compact terms, the denition of A() and V() for compact where f term hold for encoded terms. The inverse encoding is dened on encoded terms by: ! (a ) !(X ) !([a]t) !((F; (f; t)) !((O; (t1 ; (: : : ; (tn ; C ))))) Proposition 2 = a = X = [a]!(t) = f !(t) = (!(t1); : : : ; !(tn)) 8t 2 Ts !((t)) = t 8t 2 Te (!(t)) = t Proof !((a)) !((X )) = = = !(([a]t)) = = !((f t)) = = = !(((t1 ; : : : ; tn ))) = = ! (a ) = a !(X ) X !([a](t)) [a]!((t)) !((F; (f; (t)))) !((F; (f; (t)))) f !((t)) !((O; ((t1 ); (: : : ; ((tn ); C ))))) (!((t1)); : : : ; !((tn))) (!(a)) (!(X )) = = = (!([a]t)) = = (!((F; (f; t)))) = = = (!((O; (t1 ; (: : : ; (tn ; C )))))) = = 46 (a) = a (X ) X ([a]!(t)) [a]!((t)) (f !(t)) (F; (f; (!(t)))) f !((t)) !((O; ((t1 ); (: : : ; ((tn ); C ))))) (!((t1)); : : : ; !((tn))) 4.2 Nominal Terms Proposition 3 is a morphism from the standard nominal structure to the reduced nominal structure. t be a standard nominal term, a permutation: ( s t) = r (t) Proof Indeed: Let (a) = (a) r = (a) 0 (( X )) = (( 0)X ) = ( 0)Xr = (0X ) r = ((0X )) ([a]t) = ([(a)]t) = [(a)](t)r = [(a)](t)r = [(a)]r(t) = [a](t)r = ([a]t) = ([a]t) ((f t))) = (F; (f; (t)))r = (F; (f; (t) ))r = (F; (f;r(t))) = (f t) ((t1 ; : : : ; tn ))) = (O; ((t1 ); (: : : ; ((tn ); C )))) r = (O; ((t1); (: : : ; ((tn); C )))) = (O; ((t1); (: : :r; ((tn); C ))))r = ((t1; : : : ; tn)) Let be a substitution over Ts : (t) = (t) r () by induction on the structure of t and . Proposition 4 ! is a morphism from the reduced nominal structure to the standard nominal structure. Proof The proof is the same as for . Proposition 5 =! and (Te ; r ; r ) is an isomorphism between the standard nominal structure 47 4.2 Nominal Terms 4.2.5 From Nominal to First-Order Nominal terms (either standard or compact) not containing atoms (A (t) = ;) are actually rst-order terms. In the rest of this section we use compact terms but the denition and properties can easily be transposed any of the above syntax. Denition 21 The set of rst-order terms, written Tf , is dened as ft j t 2 Tc ^ A(t) = ;g . When applying a permutation on a standard or reduced nominal term, permutations accumulate on variables but in rst-order syntax, variables represent rst-order terms so rst-order terms as Proposition 6 X = X. Thus we dene the permutation application on 8t 2 Tf ; 2 f t = t Tf with f as permutation application function and the standard substitution application function forms a nominal structure called the structure. We present here a morphism F rst-order from compact terms to rst-order terms. Let , X and A. F associate to t a rst-order term with two new function symbols and [] of arity respectively 0 and 1. will represent atoms and [] abstraction. F forgets the nominal details of t: F (a) = F (X ) = X F (f ) = f F (t) = F (t) F ((t1 ; t2 )) = (F (t1 ); F (t2 )) F ([a]t) = [] F (t) For example: F (([a](a; ((a b)X; Y )); f )) = ([](; (X; Y )); f ) t be a compact nominal term over Proposition 7 F is a morphism from the compact nominal structure to the rst- order structure. 48 4.3 Nominal Problems Proof F (t). Let t be a compact term and a permutation, F ( c t) = F (t) = f We check inductively on the structure of F (s)] t 7! s]) = F (t)[X 7! j t j max2t j j (j F (t) j that F (t[X Proposition 8 Let t be a compact nominal term: +n (t)) where 2 t ranges over the permutations appearing in t. Proof Because the only dierence in size between rst-order terms and nom- inal terms are permutation sizes. 4.3 Nominal Problems # species a freshness relation between atoms and terms, and denotes alpha-equivalence. Constraints have the form a#t or s t. The predicate Denition 22 (Size) The size of constraints is dened as follows: j a#t j= 3+ j a j + j t j j s t j= 3+ j s j + j t j A set P r of constraints is called an alpha-equivalence problem. Intuitively, a#t means that if a occurs in t then it must do so under an abstractor [a]-. For example, a#b, and a#[a]a but not a#a. We sometimes write a; b#s instead of a#s; b#s, or write A#s, where A is a set of atoms, to mean that all atoms in A are fresh for s. In the absence of variables, a#s and s t are structural properties (to check a#s we just check that every a in s occurs under an abstractor), but in the presence of variables both predicates may depend on assumptions a#X about what will get substituted for the variables. A set of assumptions A # X is called a freshness context and written inductively, by a system of axioms and rules, using # in the denition of (see below). We write ds(; 0 )#X as an abbreviation for fn#X j n 2 ds(; 0 )g, where ds(; 0 ) = fn j n 6= 0 ng is the set of atoms where and 0 dier (i.e., their Formally, we dene dierence set). Below # and . under the freshness context a; b are any pair of distinct atoms. 49 4.3 Nominal Problems With the standard syntax: ` a#s1 ` a#sn ` a #s (#tup) (# ) ` a #b ` a#(s1; : : : ; sn) ` a#fs f 1 (#absa) ` a#s (#absb) ` a#X (#X ) ` a#[a]s ` a#[b]s ` a#X a#X2 (# ) ` a #X 0 (a) ` ds(; )#X (X ) ` a a ` X 0X ` s1 t1 ` sn tn ` s t (tup) ( ) ` (s1; : : : ; sn) (t1; : : : ; tn) ` fs ft f ` s t ` s (a b)t a#t (absa) (absb) ` [a]s [a]t ` [a]s [b]t (#ab) - With the compact syntax: ` a#s1 ` a#s2 (#pair ) ` a #b ` a #f ` a#(s1; s2) 1 (#absa) ` a#s (#absb) ` a#t (# ) ` a#[a]s ` a#[b]s ` a#t a#X2 (# ) ` a #X ( ) ( ) ( ) ` a a a ` f f f ` X X X ` s1 t1 ` s2 t2 ` ds(; 0 )#t ( ) ( ) ` (s1; s2) (t1; t2) tup ` t 0t ` s t ` s (a b)t a#t (absa) (absb) ` [a]s [a]t ` [a]s [b]t We remark that is indeed an equivalence relation (see [51] for more details). We write a # t and t s) when there is no ambiguity on (usually when is empty or when ). (#ab) (#f ) - 50 4.3 Nominal Problems Below we dene reduction relations (also called rewriting relations) on standard terms or problems. If =) is a reduction relation, we will denote by =) its transitive and reexive closure. Irreducible terms are called normal forms. The following set of simplication rules from [51], acting on problems in the standard syntax, where check a; b denote any pair of distinct atoms, can be used to the validity of -equality constraints. a#b; P r a#fs; P r a#(s1 ; : : : ; sn ); P r a#[b]s; P r a#[a]s; P r a#X; P r =) P r =) a#s; P r =) a#s1; : : : ; a#sn; P r =) a#s; P r =) P r =) 1a#X; P r 6= a a; P r =) P r (l1; : : : ; ln) (s1; : : : ; sn); P r =) l1 s1; : : : ; ln sn; P r fl fs; P r =) l s; P r [a]l [a]s; P r =) l s; P r [a]l [b]s; P r =) l (a b)s; a#s; P r X 0 X; P r =) ds(; 0 )#X; P r - Id This reduction relation can be adapted to problems in the compact syntax by adding the rules a#t; P r =) 1 a#t; P r 6= Id - =) th u P r =) t uh P r Denition 23 Let P r be a problem and a freshness context. P r is said valid in the context , written ` P r if and only if for any constraint a # t and s u in P r, ` a # t and ` s u. Otherwise it is not valid. t u; P r t u; P r P r0 The above rules generate a reduction relation on problems: is obtained from Pr Pr =) P r0 if by applying a simplication rule. To check constraints we proceed as follows: Given a problem P r, we apply the rules until we get an of constraints of the form a#X are left, then the original problem is valid in the context (i.e., ` P r). irreducible problem, i.e., a normal form. If only a set 51 4.3 Nominal Problems Denition 24 Let be a freshness context and problem dened as a substitution. is the = fa # X j a # X 2 g . Denition 25 A nominal -equivalence problem, or -equivalence problem for short, is, given a problem P r, to know whether there exists a freshness context such that ` P r . Such a is called the solution of the nominal -equivalence problem. [a]X [b]X reduces to the set of constraints a#X; b#X ; therefore a#X; b#X ` [a]X [b]X , as mentioned in the introduction. A problem such as X a is not valid since it is irreducible; however, in For example, the problem this case X can be made equal to a by instantiation (i.e., applying a substitution) and we say that this constraint can be solved. nominal unication problem, or unication problem for short, is, given a problem P r to know whether it exists a freshness context and a substitution such that for any constraint a # t and s u in P r , ` a # t and ` s u .(; ) is called a solution of the nominal unication problem. Denition 26 A A most general solution to a nominal unication problem P r is a pair (; ) of a freshness context and a substitution, obtained from the simplication rules above enriched with two instantiating rule labelled with substitutions: X 7!-1 u (X 62 V(()u)) (X 62 V(()u)) If we impose the restriction that in a constraint s t the variables in t X u; P r u X; P r =) X 7! u =) -1 P r[X 7! 1 u] P r[X 7! 1 u] - cannot be instantiated and the variables in left-hand sides are disjoint from the variables in right-hand sides, then we obtain a nominal we also require obtain a s matching problem. If to be linear (i.e., each variable occurs at most once in s), we linear nominal matching problem. 52 4.3 Nominal Problems Denition 27 A short, is, given a of nominal matching problem, or matching problem for problem P r where variables appearing on the left-hand side are distinct from variables appearing on the right-hand side of : fX j X 2 V(si) si ti 2 P rg \ fY j Y 2 V(ti) si ti 2 P rg = ; and a substitution such that for any constraint a # t and s u in P r , ` a # t and ` s u .(; ) is to know whether it exists a freshness context called a solution of the nominal matching problem. A most general solution to a nominal matching problem P r is a pair (; ) of a freshness context and a substitution, obtained from the simplication rules above enriched with an instantiating rule labelled with substitutions: X u; P r X 7!-1 u =) P r[X 7! 1 u] - Note that there is no need to do an occur-check because left-hand side variables are distinct from right-hand side variables in a matching problem. As shown in [51], these rules dene sound and complete nominal unication and nominal matching algorithm, where the most general solution to the input problem is obtained by computing its normal form and by composing the substitutions used in the instantiation rules. We give an example and refer to [51] for more details. Example 1 Since there is a reduction sequence: [a]X [b]b =) the most general solution of X a; a#b X 7!a =) a#b =) ; [a]X [b]b is (;; [X 7! a]). Although in the case of rst order terms we can, without loss of generality, restrict ourselves to matching problems with ground right-hand sides (it is sufcient to consider all variables as constants), this is not the case for nominal problems: when terms contain variables we may need to compute dierence sets of permutations. 53 4.3 Nominal Problems 4.3.1 Morphisms Denition 28 A nominal -equivalence structure is a 5-tuple (T ; ; ; ; #) where (T ; ; ) is a nominal structure, an equivalence relation on terms and # a relation between atoms and terms such that: ` a # s ^ ` s t ) `a#t 8 2 ` s t ) ` s t 8 2 ` a # t ) ` (a) # t 8 2ST ` s t ^ 0 ` ) 0 ` s t 8 2ST ` a # t ^ 0 ` ) 0 ` a # t Proposition 9 All the nominal structures dened in the previous section, with -equivalence and freshness relations, are nominal -equivalence We write the -equivalence and freshness relation on: their corresponding structures. s and #s compact terms c and #c rst-order terms f and #f standard terms Proof All equations are properties of the -equivalence and freshness relations on standard terms. Please refer to [51] for more details. Proofs on standard terms can be easily adapted to compact terms. First-order syntax, ignoring all naming details, all the above equations are straightforward. Denition 29 A morphism ' from the nominal -equivalence structure (T1 ; 1 ; 1 ; 1 ; #1 ) to (T2 ; 2 ; 2 ; 2 ; #2 ) is a morphism from (T1 ; 1 ; 1 ) to (T2 ; 2 ; 2 ) such that ` a #1 t ) ` a #2 '(t) and ` s 1 t ) ` '(s) 2 '(t) Proposition 10 All the morphisms between nominal structures dened in the previous section are morphism between nominal -equivalence structures. Let 'c7!r be the morphism from compact terms to reduced terms. We need to prove that ` a c t ) ` a c 'c7!r t and ` s c t ) ` Proof # # 54 () 4.3 Nominal Problems 'c7!r (s) c 'c7!r (t). -equivalence: so t tr . Let 's7!e All the rules of the system RedudeCompactT erms preserve t Idt a (a ) f f (t1 ; t2) (t1 ; t2 ) [a]t [(a)](t) (0 t) ( 0 )t the morphism from standard terms to encoded terms. Let t1 and t1 #s t2 , t1 #c t2 and a #s t1 , a #s t1 . Indeed, on encoded terms, the -equivalence and freshness axioms in the standard (resp. t2 two encoded terms, compact) syntax can all be proved in the compact (resp. standard) syntax. Let F the morphism from one of the nominal syntax to its corresponding `a#t) ` a # F (t) and ` s t ) ` F (s) F (t). ` a # t ) ` a # F (t) rst-order structure. We need to prove that for any term t that is trivial because an atom is always fresh in a term without atoms. Note that in rst-order syntax a variable a # X is always true. of s and t: X can only be substituted by a rst-order term, so The latter equation is proved by induction on the structure If s is an atom a , t as to be the same atom a and F (a) is the constant . If s is a constant or a variable, then F (s) = s to the equation is trivial. If s is a pair (s1; s2) then t has to be a pair (t1; t2 with s1 t1 and s2 t2 , by induction hypothesis F (s1 ) F (t2 ) and F (s2 ) F (t2 ) and F (s) = (F (s1 ); F (s2 )) so F (s) = F (t). If s is an abstraction [a]s0 then t has to be an abstraction [b]t0 and s t implies s0 (a b)t0 a # [b]t0 by induction hypothesis F (s0 ) F (t0 ) and of course a # F (t), F (t0 ) = F ((a b)t0 ). Denition 30 Let ' a morphism from the nominal -equivalence structure (T1 ; 1 ; 1 ; 1 ; #1 ) to (T2 ; 2 ; 2 ; 2 ; #2 ). It can be extended to: 55 4.3 Nominal Problems freshness constraints by '(a #1 t) = a #2 '(t) -equivalence constraints by '(s 1 t) = '(s) 2 '(t) a problem P r by applying it to all of the constraints in P r. Proposition 11 Let ' be a morphism from the nominal -equivalence structures (T1; 1; 1; 1; #1) to (T2; 2; 2; 2; #2) and P r a problem whose terms are in T1. Then if is a solution of the -equivalence problem P r, then is solution of the -equivalence problem '(P r). if (; ) is a solution of the unication problem P r, then (; '()) is solution of the unication problem '(P r ). if (; ) is a solution of the matching problem P r, then (; '()) is solution of the matching problem '(P r ). be a solution of the -equivalence problem P r. For any constraint a # t and s u in P r, ` a # t and ` s u. ' is a morphism so ` a #1 t ) a #2 '(t) and ` s 1 t ) '(s) 2 '(t). ` a #2 '(t) = '( ` a #1 t) and ` '(s) 2 '(t) = '( ` s 1 t) Which means is solution of '(P r) Let (; ) be a solution of the unication problem P r. For any constraint a #1 t and s 1 u in P r, ` a #1 t and ` s 1 u. '( ` a #1 t) = ` a #2 '(t)'() and '( ` s 1 t) = ` '(s)'() 2 '(t)'(). So (; '()) is solution of the unication problem '(P r) Let (; ) be a solution of the matching problem P r. For any constraint a #1 t and s 1 u in P r, ` a #1 t and ` s 1 u. '( ` a #1 t) = ` a #2 '(t)'() and '( ` s 1 t) = ` '(s) 2 '(t)'(). So (; '()) is solution of the matching problem '(P r) Proof Let This result is interesting because it relates solutions of problems in dierent syntaxes. For example it says that if the rst-order problem corresponding to a nominal problem does not have any solution, then neither have the nominal problem. Because rst-order unication and matching problems can be solved 56 4.4 Complexity and Implementation of Permutations and Sets in linear time and space, it provides a fast way to decide if some problems are solvable. It also says that solutions of nominal problem and rst-order problems have the same structure, which means the same function symbol, abstractions, variables, tuples at the same position. From an implementation point of view, it says that an algorithm for the compact syntax can be used to solve a problem in the standard syntax because and ! provide an isomorphism between the encoded and standard structures. Note that the complexity of the translation has to be taken in account in the complexity of the algorithm. So for the rest of the thesis, we will use the standard or compact syntax depending on which one is the more adapted to the situation. Denition 31 -equivalence, unication or matching and space into a single constraint s t In the compact syntax, any problem can be encoded in linear time such that the set of solutions is preserved. Proof Indeed the following equalities form a rewriting system whose normal fs tg. s1 t1 s2 t2 , (s1 ; s2 ) (t1 ; t2 ) fa1; : : : ; ang # t , (a1 a2)(a2 a3) : : : (an 1 an)(an c)[c]t [c]t forms are problems of the form where c is a fresh atom (not already present in the problem). The system termi- and # present in the problem because one or one # is consumed every time but none is created. nates in linear time in the number of 4.4 Complexity and Implementation of Permutations and Sets The complexity of the algorithms presented in this thesis depends a lot on the implementations of permutation and sets of atoms. Because we will have to use dierent implementations of permutations and sets of atoms even for the same algorithm, we need to abstract their actual implementation to make the algorithm to be able to use several implementations. 57 4.4 Complexity and Implementation of Permutations and Sets 4.4.1 Implementation This section describes the implementation and abstraction of permutations and sets. It has been implemented in the in the Objective Caml Haskell module A.6. The implementation B.1 does not need all the mechanisms presented here, it only needs the array implementation. The operations on permutations and sets of atoms used in the algorithms will be : a 2 A: membership test A [ f a g: add an atom to a set A n fag: remove an atom from a set A [ A0 : compute the union of two sets A \ A0 : compute the intersection of two sets a: compute the image of an atom by a permutation 1: compute the inverse of a permutation 0 : compose two permutations supp(): compute the support of a permutation (A) = f(a) j a 2 Ag: compute the image of a set by a permutation. Operations in this list are called the basic operations on permutations and sets of atoms. Denition 32 We call updating the action of adding/removing an atom to a set or to compose a permutation by another one. Denition 33 (Implementation as arrays) A and can be implemented respectively as mutable arrays arrA and arr indexed by atoms. Let a be an atom, arrA is dened as arrA [a] = (a 2? A) and arr [a] = (a). 58 4.4 Complexity and Implementation of Permutations and Sets Mutable arrays have constant access time but computing Afag requires either to modify the array in place by that arrA [a] := > or to create another array arr0 such arr0 [b] = ( = > if b a otherwise arrA [b] Modifying arrA in place to get arr0 takes a constant time but modies all the other occurrences of A which become A0 . To keep A, we need to create a new array arr0 but creating the array takes is linear in time and space in the size of arrA . Denition 34 The size of an array arr is dened as j arr j= l n where l is the length of arr (the size of its domain) and n the size taken, in the array, by an element. If the elements are pointers to other structures, the size an element is the size of the pointer and not the size of the structure pointed. Another implementation is using persistent (resp. maps) are size balanced binary trees sets and maps. Persistent sets whose nodes are elements in the sets (resp. a pair of a key and a value). They correspond to the Haskell module Data.Set (resp. Data.Map) and are described in [8, 39]. Persistent maps represent partial functions. Their domain of denition is a pm be a persistent map. For a key k in the domain of denition of pm, the value associated to k is pm[k ]. set of keys for which they associate a value. Let Denition 35 (Size) Because such data are trees, their size is the size on graphs. Denition 36 (Implementation as persistent data) A can be implemented directly as the corresponding persistent set psA can be implemented as the nite map pm whose domain of denition is the support of and 8a 2 supp() pm [a] = (a) Membership tests and value look up take logarithmic time. Computing A nfag (resp. (a b)) returns a new set (resp. map) and takes logarithmic time too. The original set (resp. map) remains unmodied. Thus using persistent data, sets permutations can be passed away without risks of side eects, so no need to copy them. 59 4.4 Complexity and Implementation of Permutations and Sets 4.4.2 Optimisation The type of sets is actually 1 data Set s e t 2 = | SetEmpty SetNative set where set represents an abstract implementation of set like arrays, persistent sets, . . . Adding the value SetEmpty enable optimisations: there is no need to create a whole array, which takes a linear time and space in the size of A0 , for the empty set A [ ; or A \ ; can be computed in constant time The type of permutation is: 1 2 3 4 data Perm s e t perm = PermId | PermSwap Atm | PermNative perm Atm perm ( Set s e t ) where set is as before and perm represents an abstract implementation of permu- id permutation and PermSwap a b the swapping (a b). PermNative p i s represents any permutation where p is the native implementation of , i the native implementation of 1 tations like arrays or persistent data. PermId represents the and s its support. It also enable lots of optimisations: the identity permutation or a swapping do not depend on the native implementation composing by PermId is done in constant time in the case of arrays, composing in place by PermSwap a b can be done in constant time whereas it would take a linear time in the size of A0 otherwise getting the inverse of a permutation and its support is done in constant time because it is already there. 60 4.4 Complexity and Implementation of Permutations and Sets When permutations are composed, we only need to update the inverse and the support and not to recompute them. For example, let 0 be a permutation ( (a b)) 1 = (a b) 1 ( (a b)) is computed by adding or removing a and b whether ( (a))a = a and ( (a))a = a. and supp 4.4.3 Abstraction of sets and permutations To abstract the native implementation of sets we add another layer in the monadic tower for set handling. This layer is implemented by the rst-class monadic signature SetCall set where set represents the native type of sets. SetCall set provides the following calls: SetIsIn checks if an atom is in a set SetIsSubset checks if the rst set is subset of the second SetSize returns the size of a set SetSet add or remove an atom from a set, of the rst argument is > the modication can be done in place otherwise a new set has be returned SetEmptyNative returns a native empty set SetToList returns the list of the elements of a set SetUnion/ SetInter compute the union/intersection of two sets. The rst ar- gument tells if the modication can be done in place and if yes, on which set. The native implementation of permutation is also abstracted. A new layer is added to the monadic tower for permutation handling. It is implemented by the rst-class monadic signature data type PermCall perm where perm represent the native type of permutations. The following calls are provided: PermImage returns the image of an atom by a permutation 61 4.4 Complexity and Implementation of Permutations and Sets PermSwapRight composes a permutation by a swapping on the right side PermCompose composes two permutations. The rst argument tells if the composition can be done in place and if yes on which permutation. PermSupp computes the support of a permutation PermIdNative the native identity permutation PermUnshare makes a fresh copy of a permutation 4.4.4 Complexity Because it will happen that the same algorithm uses dierent implementations of permutations and sets of atoms we need to have a way to abstract the actual implementation also in complexity proofs. Denition 37 we dene For a given implementation of permutations and sets of atoms CA as an upper bound of the time complexity of the basic operations on permutations and sets of atoms ( cf. section 4.4.1). A. Instead we take a big enough set such as the set A0 of atoms in the problem. CA , in every In practice it is impossible to handle the innite set of atoms implementation considered, depends on the number of atoms (i.e. the size of written j A0 j). AO , Denition 38 CA for the implementation of permutations and sets of atoms as arrays is j A0 j where A0 is the set of index of the arrays. Proof Every basic operation time complexity is bounded by CA Denition 39 CA for the implementation of permutations and sets of atoms as persistent data is log (j A0 j) j A0 j where A0 is the set of atoms used in the implementation. Every basic operation time complexity is bounded by CA The interpretation of SetCall and PermCall as persistent data is given in the Proof appendix A.6. 62 Part II Unication 63 An implementation of the unication algorithm using trees to represent terms, is exponential. For instance, the problem: X1 Y1 ::: Xn 1 Yn 1 Xn Yn X1 (X2; X2) (Y2; Y2) (Xn; Xn) (Yn; Yn) a a Y1 which does not involve abstraction, requires a number of reduction steps that is exponential in the size of the problem because the solution size, without subterm sharing, is exponential in the size of the problem. For rst-order unication problems, there are linear-time unication algorithms that represent terms as directed acyclic graphs. However, it is well-known that these algorithms cannot be generalised to higher-order problems (i.e. unication of -terms modulo and ); higher-order unication is undecidable. In the case of unication modulo , no linear algorithm is known, but we will show that using a representation of problems as directed acyclic graphs we can obtain a quadratic algorithm which is the most ecient implementation so far. We will start by presenting in chapter 5 a representation of nominal terms and problems as directed acyclic graphs. Then we will present 3 nominal unication algorithms in chapters 6, 7 and 8. The rst one is the most ecient (it is quadratic in time and space) but not the easiest to to maintain and extend. The second one is a almost as ecient as the rst one and easier to use in practice. The last one presents a polynomial algorithm based on graph rewriting. 64 Chapter 5 Nominal Unication Terms and Problems as Directed Acyclic Graphs This chapter presents an encoding of compact nominal terms as directed acyclic graphs. We use compact terms because we will deal with permutations in a lazy way: permutations will suspend always, unless performing the permutation is necessary to trigger a simplication rule in a problem. We will dene a recursive function to transform terms, and more generally problems, into graphs. Nodes are represented by circles, annotated by a con- [] structor. The term-constructors are: abstraction , pairs mutations , permutation application , variables (), constants f , per- X , atoms a and sets of atoms A. In addition we use two constructors for freshness and -equality: #, , and a node P r which is the root of the problem. The constructors []; #; ; ; () are binary, f is unary, X; ; a are 0-ary, P r has variable arity, equal to the the number of constraints in the problem, respectively. We will often overload the notation, by using a node annotated with a term t, as in the following diagram: t to denote the root node of the subgraph representing t. In the same way, we 65 sometimes annotate a node with a constraint Pi to denote the root of the subgraph representing the constraint. Denition 40 The graph representation of a term, and more generally a prob- lem, is inductively dened by: c where c is an atom a, a constant f or a variable X : c (t1; t2): () t1 t2 [a]t: [] a t t: t In particular, the graph representing X. X A # t, where A is a set of atoms: # A 66 t has two leaves labelled by and t u: u t P r = P1; : : : ; Pn, where each Pi is a constraint: Pr P1 ... Pn In this case, we x an arbitrary ordering on the constraints P1 ; : : : ; Pn , to obtain a unique representation. After transforming a problem into a graph as above, we identify the leaves that are annotated with the same atom, constant or variable but not leaves that are annotated by the same permutation. Having a root node Pr for a problem is not essential, but it will be useful when we dene garbage collection on graphs. The graph representation of a nominal problem, as dened above, is a nal graph (see Denition 41 below). nomi- Nominal graphs are directed acyclic graphs, they can be seen as termgraphs [43], that is, trees where subterms may be shared. We will also include ? nodes to represent unsolvable constraints, and nodes to represent solutions (substitutions). Remark 3 Denition 40 species a function that transforms a nominal problem into a graph. This function is linear in the size of the problem. Denition 41 (Nominal graph) with constructors (i.e., term constructors, and a set arity N of nodes labelled , P r), together with A nominal graph is a set #, , ?, E N N of edges, such that if n is a node decorated with a symbol m, then there are m outcoming edges for n (i.e., it has m children). 67 of If e = (n1 ; n2 ) 2 E , written n1 ! n2 , we say that e is an incoming edge for n2 , and outcoming for n1 . The edges connected to each node are ordered, and we will call the points of attachment ports. A root is a node without incoming edges. Dierent nodes may have the same decoration, however, leaves with the same label are identied (they are the same node) except for permutations. Example 2 The nominal graph representing the term [a](a b)X is: [] a (a b) X and the nominal graph representing the problem [a]a [b]b; (a b)X X is: Pr [] [] a b (a b) X Note that we have xed an arbitrary order for the constraints above. The root of a graph representing a problem is labelled by roots labelled by P r, and its children have or #. We will say that the root of the i-th constraint is the i-th root of the problem. We presented here an encoding of compact nominal terms as directed acyclic graphs. The same technique can be used to dene an encoding of standard, reduced, head reduced, encoded and rst-order terms as directed acyclic graphs. 68 Chapter 6 Extending Paterson and Wegman's First-Order Unication Algorithm In this chapter we present an adaptation of Paterson and Wegman 's First-Order Linear Unication Algorithm (FLU) [40] to nominal unication. We will show that, the rst-order and nominal algorithms have the same structure. Because any unication problem can be encoded in linear time and space to a single constraint s t, in the rest of this chapter, we only consider unication problems fs tg. The set of atoms in the problem, written A0 is dened as A0 = A(s) [ A(t). In the rest of the chapter, we consider a nominal graph d representing the problem s t. of the form Section 6.1 gives a theoretical characterization of nominal unication as a relation on the nodes of d. It will be used to prove the correction of the algorithm presented in section 6.2. A concrete implementation of the algorithm in Objective CAML [6] is in appendix B.1. Please refer to it for a complete description of the actual implementation of the algorithm. 6.1 Nominal Unication as a Relation on Term Nodes The FLU algorithm computes the solution, if it exists, by computing a relation on the nodes of the directed acyclic graph representing the problem. To prove the 69 6.1 Nominal Unication as a Relation on Term Nodes correctness of their algorithm Paterson and Wegman developed in [40] the concept of valid equivalence relation. To extend their algorithm to nominal terms, we need to start by extending their notion of (valid) equivalence relation to nominal directed acyclic graphs. Denition 42 R is a pair of a 3-ary relation R? ? ; between nodes and permutations, written s R ; t, and a binary relation F between atoms ? ? and nodes, written a F s. Two nodes s and t are related by R, written s R t if and only if it exists a permutation such that s R ; t. ? ? A nominal relation A nominal relation is a nominal equivalence relation if: s R? ? ;id s for any s (Reexivity) s R? ? ; t ) t R? ? ; 1 s (Symmetry) u R? ? ;2 t ) s R? ? ;1 2 t (Transitivity) s R? ? ;1 u s R? ? ; s ) (a F s)a2supp() (Disagreement set) a F s s R? ? ; t ) 1 (a) F t (Congruence) Denition 43 Let (R? ? ; ; F) fc over nodes as Proposition 12 Proof If be a nominal relation. Let us dene the function fc(s) = fa j a F sg (R??;; F) is a nominal equivalence relation then s R? ? ; t ) fc(s) = (fc(t)) a be in fc(s), a F s and s R? ? ; t so (a) F t, 1 (a) 2 fc(t) and s R? ? ; t ) t R? ? ; 1 s gives fc(t) 1 (fc(s)) Let a 2 (fc(t)). If two rst-order terms do not have, on their root node, the same function symbol and the same arity, then they can not be unied. Paterson and Wegman's algorithm uses it to detect when a problem does not have any solution. We extend this notion of compatibility to nominal graphs: Denition 44 (; F-compatibility) Two nodes s and t are said ; F-compatible if and only if at least one of the following properties hold: 70 6.1 Nominal Unication as a Relation on Term Nodes s = 0 s0 t = 0 t0 and s0 and t are (0 1 ); F-compatible ( 0); F-compatible s and t are two atoms, s = t, s 62 fc(s) and t 62 fc(t) s and t are two constants and s = t and s and t0 are s and t are two pairs s and t are two abstractions s is a variable t is a variable When there is no ambiguity on F, ; F-compatibility will be called -compatibility Denition 45 Two nodes s and t, representing rst-order terms, are said compatible if and only if they are Id; ;-compatible. Any directed acyclic graph can provide a partial ordering on the nodes of the graph: Denition 46 The partial order derived from the directed acyclic graph d is dened as: n1 >d n2 if and only if there exists a path from the node n1 to the node n2 in d (n1 6= n2 ). Now, we can extend the notion of valid equivalence relation to nominal rela- tions. Paterson and Wegman dene a valid equivalence relation as: An equivalence relation on the nodes of a dag is valid if it has the following properties: (i) if two function nodes are equivalent then their corresponding sons are equivalent in pairs, (ii) each equivalence class is homogeneous, that is it does not contain two nodes with distinct function symbols, (iii) the equivalence classes may be partially ordered by the partial order on the dag. 71 6.1 Nominal Unication as a Relation on Term Nodes We wanted our denition as close as possible to Paterson and Wegman's. The rst rule is essentially a propagation rule, the second one a compatibility rule on the nodes of a class and the third a rule to check there is no cycle in the graph. So here is the denition of a valid nominal equivalence relation: Denition 47 A nominal equivalence relation (R? ? ; ; F) on the nodes of a nominal dag is valid if: Propagation: (s1; s2) R??; (t1; t2) ) [a]s R??; [b]t ) a F (s1 ; s2 ) a F [b]s a F [a]s Compatibility: each nominal class is homogeneous, s R? ? ; t, (s; t) is ; F-compatibles. such that ) ) s1 R? ? ; t1 s2 R? ? ; t2 s R? ? ;(a (b)) t ( (b) F s if a 6= (b)) a F s1 a F s2 a F s (if a 6= b) that is for any s and t Occurrence check: the partial order derived from the directed acyclic graph, quotiented by the equivalence relation, is a partial order on the equivalence classes. The last remaining thing we need to extend is the notion of minimal relation : Denition 48 inal relations. Let us extend the standard partial ordering over relations to nom- (R??;1; F1) (R??;2; F2) if and only if 8s; t; s R??;1 t ) 90 s R??;20 t fa F1 tg(a)6=0(a) dom() dom(0) and 8a; s a F1 s ) a F2 s Now we can state the same property as in [40] between valid relations and solutions of unication problems: 72 6.2 A Quadratic Nominal Unication Algorithm Proposition 13 Two nominal terms s and t are uniable if and only if there exist a valid nominal equivalence relation on the graph representing that s R? ? ;id t. s t such If such a relation is minimal, then it denes a most general unier. s and t are uniable then let (; ) be a solution of the unication problem. Let R? ? ; be the relation containing s R? ? ; t if and only if s t under . Let F be the relation containing a F s if and only if a # s under . If such a relation exists, can be derived from the dag by (X ) = t if X R? ? ; t and t not a variable, or (X ) = X is there is no such t. And = f a # X j a F X (X ) = X g Proof If 6.2 A Quadratic Nominal Unication Algorithm The Paterson and Wegman First-Order Linear Unication Algorithm is as described by the algorithm 1 and 2. It is linear in time and space in the size of the directed acyclic graph. The nominal version of FLU, (FLU) is described in algorithms 3 , 4 and 5. Like in the algorithm proceeds by computing a valid relation. There are two kinds of edges in the algorithm: the edges of s t (also s R??; t). d and undirected edges s 1 $ t which pointer() is a partially dened function whose domain is empty at the beginning of the algorithm. pointer(s ) represents the class of s with the corresponding permutation. In the rest of this chapter, we will use the similarity between FLU and QNU to prove the correctness and the complexity of QNU. represent Proposition 14 Applying the morphism terms to every operation in QNU F from compact terms to rst-order gives exactly FLU: F (QNU) = FLU Proposition 15 Proof Like QNU is correct. FLU, QNU computes a valid nominal equivalence relation. So if the algorithm terminates without raising an error, then the computed relation is valid. Furthermore the relation is minimal. 73 6.2 A Quadratic Nominal Unication Algorithm 1 2 3 4 unify-flu(s,t ) = ; create-edge-flu(s,t ) ; while There is a non-variable node r do finish-flu(r ) 5 end 6 ; 7 8 9 10 11 while There is a variable node r do finish-flu(r ) end ; print Unied; 13 create-edge-flu(s,t ) = ; create the undirected edge 14 propagate-unification-edges-flu(r,s ) = ; 12 15 16 17 18 19 20 21 22 23 24 25 26 27 28 s $ t; switch (r; s) do case ((r1 ; r2 ); (s1 ; s2 )) create-edge-flu(r1 ; s1 ) ; create-edge-flu(r2 ; s2 ) case ([]r0 ; []s0 ) create-edge-flu(r0 ; s0 ) otherwise do nothing endsw endsw doFathers-flu(s ) = ; while there is a father t of s do finish-flu(t ) end Algorithm 1: First-Order Linear Unication (FLU): Part 1 74 6.2 A Quadratic Nominal Unication Algorithm 1 2 3 4 5 6 7 8 9 10 11 12 finish-flu(r ) = ; Create a new empty stack stack; push(r ) on stack; while stack is non empty do s := pop(stack) ; if pointer(s ) is dened then r' := pointer(s ) ; if r' 6= r then error(FAIL-LOOP ) end else pointer(s ) := r 13 end 14 ; 15 16 if s are not compatible then error(FAIL-CLASH ) r and 17 end 18 ; 19 doFathers-flu(s ); if s is a variable node (s ) := r 20 21 22 23 else propagate-unification-edges-flu(r,s ) 24 end 25 ; 26 while there is a undirected edge s push(t) on stack; delete the edge s $ t 27 28 29 30 31 $ t do end if r and s are not the same node then delete s 33 end end 34 delete 32 then r ; Algorithm 2: First-Order Linear Unication (FLU): Part 2 75 6.2 A Quadratic Nominal Unication Algorithm 1 2 3 4 unify-qnu(s,t ) = ; create-edge-qnu(s; Id; t) ; while There is a non-variable and non-suspended node r do finish-qnu(r ) 5 end 6 ; 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 while There is a variable node r do finish-qnu(r ) end ; print Unied; create-edge-qnu(s, ,t ) = ; switch (s; t) do case (0 s0 ; t) create-edge-qnu(s, 0 1 ,t ) case (s; 0 t0 ) create-edge-qnu(s, 0 ,t ) otherwise create the undirected edge s 1 $ t endsw endsw Algorithm 3: Quadratic Nominal Unication (QNU): Part 1 76 6.2 A Quadratic Nominal Unication Algorithm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 propagate-unification-edges-qnu(r, ,s ) = ; switch (r; s) do case ((r1 ; r2 ); (s1 ; s2 )) create-edge-qnu(r1 ; ; s1 ) ; create-edge-qnu(r2 ; ; s2 ) case ([a]r0 ; [b]s0 ) create-edge-qnu(r0 ; (a if a 6= (b) then add 1 (a) to fc(s0 ) end otherwise (b)) ; s0 ) ; do nothing endsw endsw doFathers-qnu(s ) = ; 17 while there is a father t of s do if t is a suspended node then 18 doFathers-qnu(t ) 16 19 20 21 22 else finish-qnu(t ) end end Algorithm 4: Quadratic Nominal Unication (QNU): Part 2 77 6.2 A Quadratic Nominal Unication Algorithm 1 2 3 4 5 6 7 8 9 10 11 12 finish-qnu(r ) = ; Create a new empty stack stack; push((id,r) ) on stack; while stack is non empty do ( ,s) := pop(stack) ; if pointer(s ) is dened then ( 0 ,r') := pointer(s ) ; if r' 6= r then error(FAIL-LOOP ) end else pointer(s ) := ( 1 ,r) 13 end 14 ; 15 16 if s are not -compatible then error(FAIL-CLASH ) r and 17 end 18 ; 19 doFathers-qnu(s ); if s is a variable node (s ) := 1 r 20 21 22 23 else propagate-unification-edges-qnu(r, ,s ) 24 end 25 ; 26 27 28 29 30 31 32 33 34 35 36 37 38 then while there is a nominal undirected edge s push(( 00 ; t)) on stack; delete the edge s 00 1 00 1 $00 t do $00 t end if r and s are the same node then add supp( ) to fc(r ) else add 1 (fc(s)) to fc(r ) ; delete s end end propagate fc(r ) to its children ; 78 delete r ; Algorithm 5: Quadratic Nominal Unication (QNU): Part 3 6.2 A Quadratic Nominal Unication Algorithm Proposition 16 Let d be a nominal directed acyclic graph representing the problem s t. The complexity unify-qnu(s,t ) is at most CA (j F (d) j +n (d)) where j F (d) j is the size of the rst-order directed acyclic graph corresponding to d and n (d) the number of permutation application () nodes in d. Proof Let F (d) be the direct acyclic graph obtained by applying F to every d the same way it is done with terms. Let us compare the execution of unify-flu(F (s); F (t)) and unify-qnu(s,t ). The only possible dierence in the node in execution of the two functions is line 14 of algorithm 2 and line 14 of algorithm 4 when checking ( )-compatibly. It may happen that two terms -compatibly but F (r ) and F (s) on the error FAIL-CLASH while are compatible. In that case, FLU r and QNU s are not will halt continues its execution. Otherwise, the (s); F (t)) and unify-qnu(s,t ) pass through the same steps. Most of the operation which take n time in FLU take CA n in QNU. The only one which does not, is the edge creation. When creating an edge s !e t, s execution of unify-flu(F and t may be suspended terms but we want s and t to be head reduced terms so we reduce them before creating the edge. Fortunately, this extra cost is bounded in the whole algorithm by CA j F (d) j. Implementation and complexity Atoms, constants and variables are imple- mented as integers. Permutations and sets are implemented as arrays indexed by atoms. The actual implementation of permutations and sets is in appendix B.1. Composing and inverting permutations and permuting and computing the union of sets takes a linear time in the number of atoms in the problem. Accessing the image of an atom by permutation, or performing a membership test takes a constant time. So with this implementation of permutations and sets of atoms CA =j A0 j where A0 is the set of atoms appearing in the problem. Remark 4 When j d j is close j A0 j (j F (d) j +n (d)) and with mutable arrays as permutations and sets implementation, the unication algorithm becomes linear in time and space. 79 Chapter 7 A Simple and Ecient Nominal Unication Algorithm QNU is a good algorithm and provides a concrete proof that nominal unication can be done in quadratic time. But QNU relies a lot on very ad-hoc imperative techniques which make it dicult to extend and in practice manipulations on directed acyclic graph are much harder than on trees. In practice it is often better to have a simple good algorithm, easy to maintain and extend, than a more ecient, but complex and hard to maintain and extend, one. In this chapter we present a modular algorithm (AQNU), whose complexity is almost quadratic in time, to solve nominal unication. It has been inspired by the Martelli and Montanari ecient rst-order unication algorithm [34]. Martelli and Montanari use the good but not linear set union Tarjan's algorithm [50], more well known as the Tarjan 's union-nd algorithm, to maintain equivalence classes. To maintain nominal equivalence classes, we need to adapt Tarjan 's union-nd algorithm to nominal equivalence relations. Section 7.1 presents such a nominal union-nd algorithm and section 7.2 uses it to build a simple and ecient nominal unication algorithm. Concrete implementations in Haskell of the nominal union-nd and the nominal unication algorithm are respectively in appendix A.7 and A.8. 80 7.1 A Nominal Union-Find Algorithm 7.1 A Nominal Union-Find Algorithm disjoint set union problem is to S1 ; : : : ; Sn with two operations on them: The maintain a collection of disjoint sets find(x ) to nd the set Si containing the element x. union(Si ; Sj ) unite Si and Sj into a single set: The sets Si and removed from the collection and replaced by the set Si [ Sj . Denition 49 A union-nd Sj are algorithm is an algorithm implementing the col- lection and the two functions of a disjoint set union problem. Tarjan's union-nd algorithm 6 represents each set as a tree. The collection is called a y forest, written F. Edges are written x !e y where x is the origin and the destination of the edge. Each set is identied by the element at the root node of its tree called the representative of the set. find(x ) returns the root node of tree containing x and union(x,y ) merges the trees whose root nodes are find(x ) and find(y ). To be ecient, the merging strategy of union() is to minimize the depth of the trees and every time find(x ) is computed, the path from x to the root node is rewritten so that x points directly to it. The collection of trees are implemented as an array arruf indexed by the elements of [ni=1 Si so that : arruf [x] = where r ( Indirect y Direct r is an integer called the where y is the father of if x is a root node rank of x. x The rank represents a bound of the depth of the tree. Tarjan's union-nd algorithm is well suited to maintain rst-order equivalence classes. But in nominal unication, two variables x and y in the same equivalence class do not directly represent -equivalent terms but there exists a permutation such that x and y do. We need to adapt Tarjan's algorithm to take account of these permutations and the freshness constraints they generate. We annotate every edge x !e y by a permutation , written x ! e y , arruf arruf [x] = ( Indirect y Direct r; d ( ) becomes: where y is the father of if x is a root node 81 x 7.1 A Nominal Union-Find Algorithm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 find(x ) = ; if arruf [x] = Indirect y then r = find(y ) ; arruf [x] := Indirect r ; return r else return x end union(x,y ) = ; zx = find(x ) ; zy = find(y ) ; if zx 6= zy then Direct rx = arruf [zx ] ; Direct ry = arruf [zy ] ; if rx = ry then arruf [zy ] = Direct (ry + 1) ; arruf [zx ] := Indirect zy else if rx < ry then arruf [zx ] := Indirect zy else arruf [zy ] := Indirect zx end end end Algorithm 6: Tarjan's union-nd algorithm (TUF) 82 7.1 A Nominal Union-Find Algorithm r In the unication algorithm, arruf [x] = Indirect y will mean that x R??; y. is still the rank of the tree. The new value d is a data attached to the root of every tree. Sdat is a tuple of (G; f; p) where G is a group (its internal law is written +), f a group action from P(A) on G where P(A) = fA j A Ag and p is a group action of on G such that Denition 50 A nominal data set 8A Satms; 2 ; g 2 G p(; f (A; g)) = f ((A); p(; g)) For the sake of simplicity, in the rest of this section, given a nominal data set Sdat , we use g 2 Sdat for g 2 G, A # g for f (A; g) and g for p(; g). A suspended node is a pair of a permutation and a node v written v . The structure of arruf denes a relation R over suspended nodes and nominal data. Let R be the relation dened as: ids R e , s ! ee2F idr R id(data r) if r is a root node and R the reexive, transitive, symmetric and invariant closure of R, ie the minimal relation satisfying: s s R s s V s R 0 s ids R idd ) d = ds(; 0 ) # ds s s R e e ) e e R s s V s s R e e e e R f f ) ss R f f 8 ss R ee ) ( s)s R ( e)e where s, e, and f are nodes or nominal data and d is a nominal data. The Nominal Union-Find algorithms 7 and 8 provide four functions: union, data and putData nd, such that ( ) (r (root s)) where ss R r (root s) union(s s1 ; s s2 ) does not return anything but merge the trees of s1 and s2 such that s s1 R s s2 nd s s returns 1 2 1 2 83 7.1 A Nominal Union-Find Algorithm (s) returns d such that s R d putData( s; d) does not return anything puts stores d in such a way that s R d data where s, 1 find(s s) = ; if arruf [s ] = e e then r r = find(e e) ; arruf [s] := r r ; return (s r )r else return s s end 2 3 4 5 6 7 8 9 10 11 data(s s) = ; z z = find(s s) ; Direct (r; d) = arruf [z ]; return z d 12 13 14 15 16 s1 and s2 are nodes and d is a nominal data. putData(s s,d ) = ; z z = find(s s) ; Direct (r; d0 ) = arruf [z ] ; arruf [z ] := Direct (r,z 1 d) Algorithm 7: Nominal Union-Find Algorithm (NUF): Part 1 Proposition 17 (Complexity) The complexity of find() (resp. union()) in NUF is bounded by CA C where C is the complexity of find() (resp. union()) in TUF. ack So the amortized complexity in time of find() and union() 1 (j [n Si j) where ack 1 () is the inverse Ackermann function. i=1 is CA union() has the same structure and pass through the same steps in and NUF. And any step in TUF that takes n time takes at most CA n Proof TUF in NUF Now that we have an algorithm well suited to maintain nominal relation equivalence classes, we can use it to develop a nominal unication algorithm. 84 7.1 A Nominal Union-Find Algorithm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 union(1 s1 ; 2 v2 ) = ; (z1 z1) = find(1s1) ; (z2 z2) = find(2s2) ; Direct (r1 ; d1 ) = arruf [z1 ] ; Direct (r2 ; d2 ) = arruf [z2 ] ; if z1 = z2 then putData(idz2 , ds(z1 ; z2 ) # d1 ) else if r1 = r2 then arruf [z2 ] := Direct (r2 + 1; d2 ) ; union(z1 z1 ; z2 z2 ) else if r1 > r2 then union(z2 z2 ; z1 z1 ) else 3 = z11 z2 ; arruf [z1 ] := Indirect 3 z2 ; putData(idz2 ,d2 + 3 1 d1 ) end end end Algorithm 8: Nominal Union-Find Algorithm (NUF): Part 2 85 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) In this section we present a nominal unication algorithm inspired by Martelli and Montanari's almost linear rst-order unication algorithm [34]. The algorithm uses the same techniques of using multi-equations and maintaining, for each class of variable, a counter of the number of occurrence of this class still in the problem. Once again the nominal algorithm will have the same structure as the rst-order algorithm. Because any unication problem can be encoded in linear time and space to s t, in the rest of this chapter, we consider that the input tg. The set of atoms in the problem, written A0 is dened as a single constraint fs A0 = A(s) [ A(t). problem is When we encounter X Y in the process of the algorithm, it means that if the problem has a solution then there exists a valid nominal equivalence relation (R??;; F) such that X R??; Y . X could be replaced by Y so X X and by Y Y and Y by 1 X , can be considered more or less as the same variable. Replacing (or Y by 1 X ) would be too costly but maintaining the nominal equivalence classes of variables using the above nominal unication algorithm is cheap. Denition 51 (Equations) Here equations are A # t1 ? ? : : : ? ? tn is a set of atoms and t1 ? ? : : : ? ? tn a multiset of terms. Denition 52 The unication nominal data attached to a variable nominal union-nd algorithm are triples X A in the (o; A; lt) where o is an integer counting the number of occurrences of variables in the class of set of atoms and where X lt is a list of terms [t1 ; : : : ; tn ] such that in the problem, A is a A # X ? ? t1 ? ? : : : ? ? tn is an equation of the problem. The set of unication nominal data is called Proposition 18 Sudat with 86 Sudat 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) (o1; A1; lt1) + (o2; A2; lt2) = (o1 + o2; A1 [ A2; lt1 + lt2) where lt1 + lt2 is the list forms of all the elements of lt1 followed by all the elements of lt2 (0; ;; []) as neutral element (where [] is the empty list) form a group. With the two following actions: 8 2 (o; A; [t1; : : : ; tn]) = (o; (A); [t1; : : : ; tn]) 8A 2 A A # (o; A0; lt) = (o; A0 [ A; lt) it forms a nominal data set. The algorithm uses a global stack to store the equations of a nominal unication problem. Let stack be this stack and emptyStack be the empty stack. The main function of the algorithm, unify(), is described in algorithm 9. It initializes the nominal union-nd array, the global stack stack while the stack is not empty, pops an equation from it and treats it. When the stack is empty, it performs an occurrence check. The normalizing function, normalizeEquation(), makes sure all terms in the equation are in head reduced normal form and that there is at most only one term that can be a suspended variable. Denition 53 At any time t in the execution of the problem, the problem P rt at time t is formed of the equations in the global stack, the ones in the nominal unication data and the constraints A # X and X 7! t (which represents X t) in the output. Decomposition function Let eq = A # t1 ? ? : : : ? ? tn be a normalized equation. If eq does not contains head variables, then it has to match one of this 87 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) Input: Two terms s and t to unify 1 ; 2 3 4 5 unify(s,t ) = ; for X 2 Vars(s ) [ Vars(t ) do putData(IdX; (oX ; ;; [])) ; where oX is the number of occurrences of end 6 X in stack:= emptyStack ; push(; # s ? ? t) on stack; while stack is not empty do eq = pop(stack) ; eq0 = normalizeEquation(eq ) ; 7 8 9 10 11 if eq0 12 has a head variable storeEquation(eq 0 ) 13 then else 14 treatDecompose(eq 0 ) 15 end end 16 17 occurCheck() 18 Input: an equation A # t1 ? ? : : : ? ? tn 19 ; 20 21 22 23 24 25 26 27 28 29 normalizeEquation(A # t1 ? ? : : : ? ? tn ) = ; for 0 ilen do h replace ti by ti end while there are ti = i Xi and tj = j Xj union (i Xi , j Xj ) ; remove tj from the equation ; decrement the counter of occurrence of with Xj i 6= j do by one end return the remaining equation Algorithm 9: Unication and normalizing functions 88 ; # s ?? t 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) rules: (AtomsRule) (CstRule) (P airRule) (AbsRule) A # a ? ? : : : ? ? a ) ; a and atom and a 62 A A # f ? ? : : : ? ? f with ) ; f a constant A # (t11 ; t12 ) ? ? : : : ? ? (tn1 ; tn2 ) ) fA # t11 ?? : : : ?? tn1 ; A # t12 ? ? : : : ? ? tn2 g A # [a1 ]t1 ? ? : : : ? ? [an ]tn ) f(A [ fa1 : : : ang) n fa1g # (a1 a1)t1 ?? : : : ?? (a1 an)tng with Then resulting equations are put in the stack. If eq does not match any of these rules then the problem has no solution and the program raise an error. The function taking eq or raising an error is called as input, putting the resulting equation in the stack decompose If the normalized equation does contain a term X X , then it can not be decomposed but it can be store in the unication nominal data Storing equation of X. The function which stores such equations is called storeEquation() and described in algorithm 10. OccurCheck The function occurCheck checks that the data attached to every class is (0; ;; []). If not, it means that the problem does contains a loop and so has no solution. Thus, if one of the data is not (0; ;; []) the function raise an error, otherwise returns nothing. Proposition 19 (Correctness) does not have any solution. If the problem raised an error, then the problem Otherwise, the output of the algorithm is a most general solution of the problem. Proof All the steps of the algorithm transform the problem into an equivalent one. So the set of the solutions of the problem is preserved. 89 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Input: A normalized equation eq = A # X ? ? t1 ? ? : : : ? ? tn storeEquation(A # X ? ? t1 ? ? : : : ? ? tn ) = ; d = data(( X )) ; (o; A0; [tn+1; : : : ; tm]) = d ; which represents the equation A0 # X ? ? tn+1 ? ? : : : ? ? tm ; the two equations can be combined into :; A00 # t1 ? ? : : : ? ? tm with A00 = A [ A0 ; if o = 1 then this is the only occurrence of X in the problem; then the class of X is a root class and ca be processed; if n = 0 then then equation is the freshness constraints output the freshness constraint 1 (A00 ) # X else X 7! 1 t1 ; push(A00 # t1 ? ? : : : ? ? tm ) on stack end putData( X; (0; ;; [])) else putData( X; (o 1; A00 ; [t1 ; : : : ; tm ])) end Algorithm 10: Equation storing function output the substitution 90 A00 # X ; 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) 7.2.1 Complexity Let d be a nominal data (o; A; lt). Representing lt as a list is too costly because of the number of times we need to apply a permutation to its elements and l1 ; : : : ; l4 be 4 lists and let us consider l1 + l2 + l3 + l4 . Doing it in one shot is linear in jl1 j + jl2 j + jl3 j + jl4 j but computing l5 = l1 + l2 , then l6 = l3 + l4 and nally do l5 + l6 is not. So performing lazily appending two lists. For example, let these operation is more ecient. In this section we implement a better structure to represent lt with lazy permutations and lazy appending. This structure is the algebraic datatype: T ree a = j j j Nil Singleton a Node (T ree a) (T ree a) Remap (T ree a) the empty tree the leaf a a node with two children With this structure we can merge two trees Node t1 t2 . t1 and t2 in constant time with And we can lazily apply a permutation to all the elements of a tree in constant time with Remap t. If we have to apply many permutations to a tree, permutations will be composed together and only then the resulting permutation will be applied on the elements of the tree, which means only one tree and term traversal. When we need to get the list of terms, we atten the tree, applying permutations and giving back the list of leaves. Input: A nominal tree t 1 2 3 4 5 6 7 8 flatten(t ) = flattenAux(id , t ); where; flattenAux(id , t ) = switch (; t) do case (,Nil) ! return [] ; case (,Singleton a) ! return [a] ; case (,Node t1 t2 ) ! Node flattenAux(; t1 ) flattenAux(; t2 ) ; case (,Remap 0 t) ! flattenAux(( 0 ); t) endsw 91 7.2 An Almost Quadratic Nominal Unication Algorithm (AQNU) Proposition 20 ture is at worse ack The complexity of the unication algorithm with the lazy struc- CA (j F (s) j +n (s)+ j F (t) j +n (t)) 1 () is the inverse Ackermann function. Proof ack 1 (j X j) where Once again, when we forget all nominal details in the nominal algo- rithm, we get a rst-order unication algorithm. The complexity in time of this (j F (s) j + j F (t) j) ack 1(j X j) and traversing a permutation application node takes CA time. ack 1 (j X j) comes from the nominal union-nd algorithm. algorithm is Remark 5 When j F (s) j + j F (t) j is close to j F (s) j +n (s)+ j F (t) j +n (t), with mutable arrays as permutations ans sets implementation the nominal algorithm becomes linear in time and space. Unlike to QNU, AQNU is convenient to use in practice: its input is simply two nominal terms, it is modular and quite functional. That is why it has been implemented in the Haskell library whereas isolated algorithm. 92 QNU has been implemented as an Chapter 8 Nominal Unication via Graph Rewriting In this chapter we present a polynomial nominal unication algorithm based on graph rewriting. The algorithm takes a directed acyclic graph representing the problem as input and performs a series of transformations on it until either raising an error or reaching a solution. The encoding of nominal terms is a bit dierent from what was presented in chapter 5. The two dierences are in the syntax of functions and tuples: Denition 54 f t: f t (t1; : : : ; tn): () t1 ... tn Denition 55 (Graph rewriting rule) A graph rewriting rule is a pair of nominal graphs, written l ) r , where leaves may be labelled by (graph) metavariables 93 s; t; : : :, l has exactly one root. All in l , and we will also assume and where must also occur l the metavariables occurring in r that each metavariable occurs at most once in . A graph rewriting rule may have several roots in the right-hand side; rules with multiple roots in the right-hand side will be used in Section 8.1.1. Example 3 The following is a graph rewriting rule, using a metavariable Id t t. ) t l ) r to a graph G, we rst create a copy of r and add it to G, and then pointers towards the instance of l in G are redirected towards the copy of r. Occurrences of metavariables in r are replaced by a pointer to the corresponding subgraph in G (thus, they are shared). Other pointers are To apply a graph rewriting rule not aected. If a node is no longer needed (i.e. there is no path from the root node) we garbage-collect it. We now dene graph reduction more precisely: Denition 56 (Graph reduction) The one-step reduction relation generated 0 by a graph rewriting rule l ) r is dened as a set of pairs G ) G generated as follows: When a subgraph g in G matches the left-hand side can be obtained by replacing the metavariables in redex), G0 l l of a rule (that is, by graphs; we say that g g is a is obtained by: G a copy of the right-hand side r, where the metavariables t in r are replaced by pointers to the corresponding instance of t in g . 1. Adding to g towards the root of r (the pointers are duplicated if r has more than one root, or erased if r is empty); in the special case in which r is just a metavariable t, then pointers to the root of g are updated to point directly to the instance of t in g . The use of a 2. Redirecting all the pointers to the root of rule with multiple roots in the right-hand side is only permitted if the parent nodes of g have variable arity. 94 3. Nodes that have no incoming edges are erased (garbage collected), except for the node Pr (the root of the problem). In particular, the root of g will be garbage-collected since all pointers to it are redirected, but the other nodes in g may still be of use. Example 4 The rule in Example 3 can be applied to the graph of we assume there are incoming edges H O): and Id [a]X (below H O [] Id a X since we can identify an instance of the left-hand side in this graph (replacing by the graph representing representation of [a]X : t [a]X ). After one step of rewriting we obtain the graph H O [] a where the nodes and Id X have been garbage collected, and the pointer updated, so that it points to the instance of t (O H has been is not aected). The following sections specify a correct and complete graph rewriting algo- rithm to solve nominal problems. We rst consider constraints in Section 8.1.1, and show how the graph representing a set of constraints can be reduced to ? if there is no solution, or to a graph representing a substitution and set of freshness constraints. Section 8.1.2 then deals with freshness constraints: we specify an algorithm to transform a set of freshness constraints into an equivalent freshness context. We will show that both algorithms are polynomial in time and space. 95 8.1 A polynomial Algorithm via graph rewriting 8.1 A polynomial Algorithm via graph rewriting 8.1.1 A graph rewriting algorithm to solve constraints -equality constraints using three kinds of graph rewriting rules: normalisation rules to simplify the graph representing the problem (eliminating trivial constraints, identity permutations, etc), rules to solve equality constraints, and neutralisation of permutations to We will present an algorithm to solve propagate lazy permutations in case no -rule can be applied. In the sequel, we assume that we start with a graph representing a nominal problem and we only consider graphs that are obtained by reduction of the initial graph. We show below that all the graph rewriting rules we will dene transform the graph into a graph representing an equivalent problem. 8.1.1.1 Normalisation of the graph Denition 57 not A skeleton node is a node labelled by a term constructor that is or a permutation. A value node is either an atom, a set of atoms, or unit (i.e., a 0-ary tuple). Skeleton nodes are therefore value nodes, variables, abstractions, tuple constructors and function nodes. For example, in the graph representing the term a, there is only one skeleton node, which is the one labelled by a. The following function W will be used to distinguish suspended variables on variable nodes from other suspended variables, ignoring permutations. Denition 58 (Weight) The function W : N 1) to each node n. It is dened by cases on n: ! f0; 1g assigns a weight (0 or If n is a variable, W (n) = 0. If n is neither a variable nor an , W (n) = 1. If n is and it is the root of a subgraph as shown below, then W (n) = W (t). 96 8.1 A polynomial Algorithm via graph rewriting t The computation of the weight of a node can be done in polynomial time in the size of the graph, since in the worst case it requires the traversal of a path in the graph (and the graph is acyclic). To reduce the graph we apply a set of normalisation rules, which are parti- tioned into three groups: simplication, ordering, and compacting. Simplication rules Rule Id-Perm eliminates identity permutations: t Id ) t Note that when applying this rule, no node is created since t was already present in the left-hand side. According to Denition 56, all pointers to the node in the left are redirected towards the node t, and the node is garbage collected. The node Id will be garbage collected if there are no other pointers to it. It is clear that this rule preserves all the solutions of the problem, but notice that in the case in which t is a variable, we are replacing Id X by X , which strictly speaking is not a term. We could impose a condition that the rule applies only when t is not a variable, but in practice it is better to use the rule also with variables and take this fact into account when we read the problem represented by the graph (see Section 8.1.1.2). Rule Apply-Perm applies permutations on value nodes. We give below a rule scheme which encompasses three kinds of rules (one for each kind of value node, see Denition 57): 97 8.1 A polynomial Algorithm via graph rewriting ) t t t is a value node. if Note that, according to Denition 56, when we reduce a graph using Apply-Perm , all pointers to the node are redirected towards the node t, and the node is garbage collected. The node instead may have other pointers to it (it will only be erased if there are no pointers to it). This rule preserves the solutions of the problem, since it replaces a suspended permutation by its result. Ordering rules Rule Order-Unif puts the heaviest side of an equation on the right, using the notion of weight dened above (see Denition 58): s if t ) t s W (s) > W (t) Only the edges of the graph are aected by this rule, no nodes need to be created or erased. Clearly, solutions are preserved since is commutative. Rule Order-Perm moves permutations towards the right-hand side of equations: 98 8.1 A polynomial Algorithm via graph rewriting ) s t if 1 s t W (s) W (t) and s is a skeleton node . In other words, this rule transforms a constraint of the form s 1 t, which is equivalent (see [20, 51]). s t into Compacting rules Rule Consec-Perm composes consecutive permutations: ) 0 0 if t t is a skeleton node t . If there are more than two consecutive permutations, the condition in this rule ensures that they are composed bottom-up (because t cannot be an application of permutation). 8.1.1.2 Properties To obtain a graph representation of a nominal unication problem, in the sequel we assume that the translation function described in Denition 40 is used, with a further optimisation: suspended variables of the form represented as a node labelled by X. Id X may simply be Proposition 21 (Correctness of normalisation) The normalisation rules are correct; more precisely: if a graph G representing a unication problem P r reduces 0 to a graph G , then 99 8.1 A polynomial Algorithm via graph rewriting 1. G0 is the representation of a problem represented simply by a node 2. P r0 is equivalent to P r0 (except that a term X ); and IdX may be P r. Proof 1. We consider each normalisation rule in turn. Since the translation function from problems to graphs is inductive, we obtain the problem P r0 associated to G0 by replacing in P r the subproblem corresponding to the left-hand side of the rule by the problem represented by the right-hand side. The only special case is Id-Perm , which may replace Id X by X . 2. The normalisation rules can be justied as follows: Id-Perm and Apply-Perm are correct by denition of permutation (see Section 4.2). is commutative. 1 Order-Perm is justied by the equivalence: s t , s t. 0 0 Consec-Perm is justied by the fact that ( t) and ( )t represent Order-Unif is correct because the same nominal term. To show that the normalisation rules are terminating (i.e., reduction sequences are nite), we introduce the notion of position in the graph. Denition 59 (Positions) A position in a graph G representing a unication problem P r is a string of integers. We use to denote the empty string, which will be associated to the root of the graph, and use the notation p:i to denote the string containing all the elements of p followed by i. The node at position p in G, written Gjp , is dened as follows: Gj is the root of the problem, the only node labelled by P r. Gji is the i-th root, that is, the root of the i-th constraint. Gjp:i is the node attached to the i-th port of the node Gjp. 100 8.1 A polynomial Algorithm via graph rewriting The weight of a position is the weight of the node at that position. Due to sharing, the same node may be associated to several positions as the following example illustrates. Example 5 In the graph G representing the problem [a]a [b]b; (a b)X X 2 is labelled by (that is, Gj2 is the root of the second constraint), and the node at position 2:2 (i.e., the second child of the node at position 2) is labelled by the variable X . Since X is shared, this node is also at position 2:1:2. given in Example 2, the node at position All the normalisation rules, except Order-Unif , preserve the weight of posi- tions; more precisely: G be a nominal graph, p a position in G, and G0 the graph obtained by applying a normalisation rule l =) r dierent from Order-Unif on G. If G0 jp exists, and p corresponds to a position that exists in both l and r, then W (Gjp ) = W (G0 jp ). Proposition 22 Proof Let By inspection of the rules. The weight of a node at a position left-hand side is the same as the one in position p in the p in the right-hand side. Intuitively, this property says that the graph positions that survive reduction, that is, the positions that are in the left and right-hand sides of the rule, maintain their weight, except when Order-Unif is used. We will now show that the rewrite system dened by the simplication, ordering and compacting rules is terminating. Unfortunately we cannot use a simple interpretation based on sizes, because the rule Consec-Perm , although having a left-hand side which is bigger than the right-hand side, may increase the size of the graph. Indeed, this rule creates a node 0 , and the nodes in the left-hand side may be shared, so cannot be erased. We will dene the following patterns, which include the left-hand sides of the rules above and some additional graphs that will be useful to dene a decreasing measure: 101 8.1 A polynomial Algorithm via graph rewriting Pattern Apply-Perm : Pattern t Consec-Perm : 0 Pattern Order-Unif t : s where Pattern t W (s) > W (t) Order-Perm-Left : where Pattern t s W (s) W (t) Order-Perm-Right : s 102 t 8.1 A polynomial Algorithm via graph rewriting where W (s) > W (t) We associate to each pattern a number of points: Apply-Perm Consec-Perm Order-Unif Order-Perm-Left Order-Perm-Right and to each node n : : : : : 1 1 1 3 3 point point point points points in a graph the sum of the points of all the patterns that match the subgraph rooted by n (a subgraph may match several patterns). More precisely: Denition 60 (Reduction Points) Let G be a graph and n a node in G. Let RedP ts(G; n) be the sum of the points of patterns matched by the subgraph with P RedP ts(G; n). root n in G. RedP ts(G) = n 2G Proposition 23 Proof Let n For any node n in a graph G, RedP ts(G; n) 4. node, it may only match node it may match Apply-Perm Order-Perm-Left , 4 points at most. Otherwise, and or Consec-Perm : Order-Unif and Let 2 points. Let n is a Order-Perm-Right , n matches no pattern so has zero points. G be a graph and G0 be the graph 0 normalisation rule on G. RedP ts(G ) < RedP ts(G). Proposition 24 Proof If n is a be the root of a subgraph that matches a pattern. If so obtained by applying a n be the root of a subgraph that matches a rule; we say that n is the node on which the rule is applied. We consider each rule in turn: n is deleted, and the value node t created by Apply-Perm has zero points. RedP ts() may have changed for some nodes: Let n0 be a node of G0 and G (so n0 6= n). Either n0 was not aected by the Apply-Perm and Id-Perm : rewrite step, or it is a node in a position belonging both to the left- and righ-hand sides. Apply-Perm and Id-Perm preserve the weights of those positions (Proposition 22), and pattern conditions depend only on weight conditions so a condition is satised after the application of the rule if and 103 8.1 A polynomial Algorithm via graph rewriting only if it was satised before. If a pattern is matched by n0 in G0 so it was in G. So RedP ts(G0 ; n) RedP ts(G; n), hence RedP ts(G0 ) < RedP ts(G) (because n has points and n is not in G0 ). Order-Unif : n in G0 cannot match the patterns Apply-Perm or Consec-Perm ), and Order-Perm-Right (because of weight conditions). (because it is It matches the pattern Order-Perm-Right RedP ts(G; n) G0 in Order-Perm-Left if and only if it matched in G and it matched Order-Unif in G. So RedP ts 1. (G0; n) = The rule neither deletes nor creates any node, the subgraph of the other nodes is the same, and so are the points. Thus, RedP ts(G0 ) = RedP ts(G) 1. n cannot match any pattern in G0 , but in G it matched the 1 are created, with at most 2 Order-Perm-Left . Nodes and Order-Perm : pattern points. The subgraph of the other nodes remains unchanged, so do the points. Hence, RedP ts(G0 ) RedP ts(G) Consec-Perm : n matches only the pattern Apply-Perm in G0 (not Consec-Perm because t is a skeleton Consec-Perm tions satised is an or G. in G0 in node ) and it matched the patterns Apply-Perm and The rule Consec-Perm are also satised in node and matches patterns Order-Perm-Right , Consec-Perm 3 + 2 = RedP ts(G) 1. so it was in G. preserves weights, so condiLet n0 Order-Unif G. If n0 is a be a parent of and n, if n Order-Perm-Left , node, and matches or Apply-Perm , so it was in G. Otherwise n matches nothing. Other subgraphs are not modied. Hence RedP ts(G0 ) = RedP ts(G) 1. As a corollary, we obtain the termination property for reduction: Corollary 1 The graph rewriting system dened by the simplication, ordering and compacting rules is terminating. The normalisation process terminates in at most RedP ts(G) steps. We can give an upper bound on RedP ts(G), that is, a bound on the number of reduction steps needed to compute a normal form (i.e., an irreducible graph): 104 8.1 A polynomial Algorithm via graph rewriting Proposition 25 RedP ts(G) 2Nb(G; ) + 4Nb(G; ) where Nb(G; ) number of nodes in G and Nb(G; ) is the number of nodes in G. is the nodes have at most 2 points, nodes at most 4, other nodes have no points. Proof Corollary 2 The number of normalisation steps for a graph nominal problem is at most 2Nb(G; ) + 4Nb(G; ). We will now compute a bound on Nb(G; ). G representing a This will be needed to evaluate the cost of the unication algorithm. First we introduce some notation: Denition 61 We denote by App(G) the set of nodes in the graph G (so Nb(G; ) = jApp(G)j), P ortssk (G) (resp. P orts (G)) denotes the set of all the ports of skeleton nodes (resp. nodes) in G, P ortssk; (G) denotes the set of all the ports of skeleton and nodes in G, and P ortssk; (G) denotes the set of all the ports of skeleton nodes and nodes in G that are attached to an node. Proposition 26 Let G be a normal form of a graph representing a problem P r. Then Nb(G; ) = jApp(G)j jP ortssk; (G)j. : P ortssk; ! App(G) be the function that for each port in P ortssk; (G) returns the node attached to it. We can show that f is surjective as follows: Each node n has at least one parent (otherwise it is garbage collected); let np be a parent of n, then np is not an node (otherwise we would have two Proof Let f consecutive applications of permutations, which contradicts the fact that G is in normal form). Thus, np is either a skeleton node or a node. Let p be the port of np to which n is attached. Then p is in P ortssk; G and f p n. Therefore ( ) jApp(G)j = jIm(f )j jP ortssk;(G)j P ortssk;(G)j. ( )= Note that in the unication algorithm it is not necessary to reduce the full graph to normal form. In fact, in our implementation we normalise locally, reducing only the subgraphs that are relevant for the application of an rule. The local normalisation process terminates too, and it has a smaller cost. 105 8.1 A polynomial Algorithm via graph rewriting -rules 8.1.1.3 We will now solve -equality constraints using -rules which apply to normalised graphs. Before giving the rules, it is useful to characterise graphs in normal form. Proposition 27 The normal forms of graphs representing nominal terms are the graph representation of terms generated by the following grammar: Tnf Tnf:np Tnf:cons Tnf:val Tnf:var Tnf:npvar ::= (Tnf:consjTnf:var ) j Tnf:np ::= Tnf:cons j Tnf:val j Tnf:var ::= f (Tnf ) j (Tnf1 ; : : : ; Tnfn ) j [a]Tnf ::= a j () ::= X ::= Tnf:cons j Tnf:val 6= Id n 6= 0 nf stands for normal form, np for non permutation, cons for term-constructor, val for value, var for variable and npvar for not variable-or-permutation. Here Proof A normal form is irreducible, so it cannot contain Id , consecutive permutations, or permutations acting on value nodes. Proposition 28 GR nf Proof -constraints are generated by: ::= (Tnf:var Tnf ) j (Tnf:npvar ( j )Tnf:npvar ) The normal forms of By inspection of the rules: A constraint in normal form is irreducible, so its left-hand side cannot be a permutation application. If we have a variable right-hand side (possibly with one permutation) then we must have a variable on the left-hand side. Note that we can have at most one application of a permutation in the right-hand side (otherwise the rule Denition 62 Two term nodes unify a graph rooted by n1 n1 and Consec-Perm n2 would apply). are incompatible if it is impossible to with a graph rooted by n2 . n1 , n2 are incompatible if they are labelled by two dierent, non-variable, term constructors (e.g., dierent atoms a; b; an atom and In other words, two nodes an abstraction; two dierent function symbols; a function and an abstraction; etc.). We divide the rules into two groups: simplication and propagation rules. Simplication rules have priority. 106 8.1 A polynomial Algorithm via graph rewriting -simplication rules Rule Id-Unif erases trivial equations: ) ; t Rule ?-Unif detects failure due to incompatibility: s if Rule Same-Term t ) ? s and t are incompatible solves equations where both sides involve the same term s, with a suspended permutation on the right-hand side: s Note that # ) supp() s supp() may be empty; we deal with freshness constraints later. -propagation rules Each application of a propagation rule will be combined with the meta rule Redirect : 107 8.1 A polynomial Algorithm via graph rewriting P s t P ) s t t has s as a child. Here P is any parent node of s. if no node in The aim of this rule is to move all the pointers to the left-hand side of the equation (except the pointer from the root of the equation) towards the right- s t, then we can replace any occurrence of s by t. However, this operation should not create a cycle; in other words, if s and t are -equivalent then s cannot be a subterm of t. We call the latter a generalised hand side. The idea is that if occur check. If Redirect cannot be applied because a node in equation has no solution, and we rewrite it to s if a node in t ) ?: t has s as a child, then the ? .. . t has s as a child (generalised occur-check). We will assume that each time an -propagation rule R has been selected to be applied to an equation, the that the Redirect Redirect rule is applied rule does not change the terms redirected to an -equivalent term. Rule Subst : X t ) 108 X applying R. Note s and t, only pointers to s are The propagation rules are: before t 8.1 A polynomial Algorithm via graph rewriting There is no need to perform an occur check in the rule done by Subst , since it is Redirect . Recall that once this rule is selected to be applied, all pointers to left are redirected to t by the Redirect rule (provided X X in the does not occur in t), which corresponds to applying the substitution [X 7! t] in the graph. Rule Fct-Unif : Rule f f s t ) s t Abs-Unif-Same : [] a Rule Abs-Unif-Di ) [] s t : 109 s t 8.1 A polynomial Algorithm via graph rewriting [] ) [] a s s if Rule Tpl-Unif t b # (ab) t ... a a 6= b : () t1 () t1 ... s1 ) sn ... t1 s1 tn sn Here if the tuple has arity 0, the right-hand side is empty (i.e., the constraint is eliminated). Notice that the rules Abs-Unif-Di and Tpl-Unif rewrite a constraint into a set of constraints. The graph in the right-hand side has two roots in the case of Abs-Unif-Di , and may have zero or more roots in the case of Tpl-Unif . According to the denition of graph rewriting, in a reduction step using one of these rules, each pointer to the root of the redex is replaced by a set of pointers to the roots of the right-hand side. Since the parent node of is P r, which has a variable arity, these rules are well-dened. Before showing that the -simplication and -propagation rules are sound, we introduce a rule to deal with suspended permutations that prevent the application of the rules above. 110 8.1 A polynomial Algorithm via graph rewriting 8.1.1.4 Computing permutations: Neutralisation In order to be able to match the left-hand side of an -rule, we may need to (partially) compute a permutation in a normalised graph. We will do this in a lazy way, moving the permutation down the term only the minimum that is needed to match an -rule. However, due to sharing, such propagations have to be computed carefully. Consider the following example: M u f where M t represents any node pointing towards . If we apply directly on t, we obtain: f u which is incorrect (f apply on t t becomes M t f t). and redirect parents of ( 1(t)) as follows: However, since t = 1(t), we can towards (t) and parents of t towards u f 1 111 M t 8.1 A polynomial Algorithm via graph rewriting This is the basis of the neutralisation Denition 63 (Neutralisation rule) of n is dened by: rule dened below. n be a non leaf node. Let M Neutralisation N N ) n n1 The ... M 1 n nn The neutralisation rule will only be applied to ... n1 nn t when it occurs immediately below node, as shown in Figure 8.1 below. This rule is used only to trigger the application of an -rule in a normalised graph, by bringing a term constructor up to match the left-hand side of an -rule. an rule can be applied, the neutralIt is easy to see that after applying this rule, the node In the graph shown in Figure 8.1, if no isation rule applies. will point to s and to the root of t. Proposition 29 If G is a graph representing a nominal problem, and G nor0 0 malises to G , then either there are no nodes in G or an - rule can be applied, possibly preceded by an application of Neutralisation. s t Figure 8.1: Graphs where neutralisation may be applied 112 8.1 A polynomial Algorithm via graph rewriting G0 contains an node. By Proposition 28, this node is the root of a subgraph G00 representing a constraint s t, and we can Suppose the graph Proof distinguish two cases: If t is not of the form v , then using Propositions 27 and 28 we can check that rule for each kind of normal form (i.e., G00 matches the left-hand side of an -rule). there is an If G00 is a graph as depicted in Figure 8.1 (note that s and t must be dierent, otherwise the graph would not be normalised since rule Same-Term applies) then will point to s and to the root of t, and again using Proposition 28 we can check that the graph can be reduced by an -rule. after applying neutralisation, rules) When applied to a normalised graph representing a nominal problem, all the rules, as well as Neutralisation, pre- Proposition 30 (Correctness of serve the solutions. Proof First notice that the -simplication rules deal with 'simple' equations (constraints of the form t t, which are trivially solvable and can be eliminated; equations between incompatible terms, which have no solution and thus are re- ?; and equations of the form t t, which have solutions only if the atoms aected by are fresh in t). It is easy to see that the -propagation placed by rules follow the format of the rules given in Section 4. The only interesting case is Subst , by which replaces the node by a node representing a substitution, or ? (depending on the result of the occur check). In the rst case, the meta- rule Redirect performs the substitution by updating the relevant pointers. Rule Redirect generates ? only if the equation has no solution. 8.1.1.5 Putting all together In the rst phase of the algorithm to solve nominal unication problems, we solve constraints in the graph representation of the problem, using Normalisation rules, -rules, and the Neutralisation rule. We deal with freshness constraints in the second phase of the algorithm. More precisely, the rst phase of the algorithm can be described as follows: we start by normalising the graph 113 G0 representing 8.1 A polynomial Algorithm via graph rewriting the initial nominal problem and then we apply cycles of -rules followed by nor- malisation, until no constraints are left (if necessary, we use the Neutralisation rule before applying an -rule). The algorithm is given below in pseudo-code: algorithm: Normalise G while G has at least one node do if G is irreducible for -rules then Apply Neutralisation end if Apply an Normalise end while on G -rule on G G If at any point ? is generated, we raise an exception since this means that the problem has no solution. Below we show that the algorithm above terminates and all the eventually eliminated, obtaining nodes are ? or the graph representation of a substitution and a set of freshness constraints. We analyse the cost of the algorithm in terms of number of iterations, and reduction steps. 8.1.1.6 An upper bound on the number of iterations We use a measure based on the arities of nodes (note that the number of nodes does not necessarily decrease with each rule application, because of sharing and because rule Tpl-Unif creates several nodes). Denition 64 Let G be a nominal graph and n a node in G. P ResSize(G) = n2G ResSize(n), where ResSize(n) is dened by: 8 > > <0 ResSize(n) = 2 arity(n) > > :arity(n) 114 if n is a if n is a , , # node tuple node otherwise 8.1 A polynomial Algorithm via graph rewriting For example, ResSize( ) = 2, ResSize(?) = 0. Remark 6 ResSize(n) depends only on n (not G). Proposition 31 Let G be a graph and G0 be the graph -rule on G. Then ResSize(G0) < ResSize(G). Proof Let n obtained by applying an be the root of the subgraph on which the rule is applied, we consider each rule: Id-Unif ?-Unif , Subst , Same-Term delete n (that is, an -node of arity 2), and create only nodes of null ResSize(). Therefore ResSize(G0 ) ResSize(G) ResSize(n) = ResSize(G) 2. Fct-Unif , , Abs-Unif-Same and Abs-Unif-Di only create nodes of null ResSize. [] They do not erase the two roots (f or , resp.) because there may be other pointers to these nodes. Let n. Redirect , l (resp. r) be the left (resp. right) child of which is combined with each rule, transforms parents of l into parents of r so that after applying the rule l becomes useless and is garbage- (l) > 0, except if l is a 0-ary function, in which case the -node n is erased. Hence ResSize(G0 ) < ResSize(G). Tpl-Unif : Let m be the arity of the tuple. If m = 0 then the -node n is erased. Otherwise, this rule creates m 1 -nodes but as in the previous case the left tuple-node (l) becomes useless and so is erased. ResSize(G0 ) = ResSize(G) + (m 1) ResSize(n) ResSize(l) = ResSize(G) + 2m 2 2m = ResSize(G) 2. collected. Note that ResSize G0 be the graph obtained by applying 0 normalisation or neutralisation rule on G. Then ResSize(G ) ResSize(G). Proposition 32 Proof Let G be a graph and For each rule, only nodes of null Proposition 33 There are at most ResSize are created. ResSize(G0 ) iterations of the while-loop. 115 a 8.1 A polynomial Algorithm via graph rewriting Proof ResSize(G), and neither does Also, -rules decrease ResSize(G) by Propo- Normalisation rules do not increase Neutralisation , by Proposition 32. sition 31, and by Proposition 29 one of these rules is applicable in each iteration of the while-loop, hence ResSize(G) decreases. -rules terminate on their own (but in the algorithm we apply only one step of -reduction As a corollary of Proposition 31 we can also deduce that the in each iteration of the while-loop). 8.1.1.7 An upper bound on the number of normalisation steps Using Proposition 2, we know that in each iteration of the while-loop there are at most 2Nb(G; ) + 4Nb(G; ) normalisation steps. upper bound which depends only on the initial graph We will now compute an G0 . Denition 65 MaxArity(G) = maxfarity(n)jn 2 Gg Proposition 34 Let G be a nominal graph and G0 be the graph obtained by ap- , or neutralisation rule on G. Then MaxArity(G0 ) MaxArity(G), and jP ortssk (G0)j jP ortssk (G)j. plying a normalisation, 1. 2. Proof For the rst part: note that each node on the right-hand side of a rule has an arity which is less than or equal to the arities of the nodes in the left-hand side. The second part is a consequence of the fact that rules do not create skeleton nodes. Proposition 35 reduced version of Let G0 G0 be the initial graph representing a problem, and G a at any moment in the algorithm. Nb(G; ) Nb(G0 ; ) + Nb(G0 ; ()) MaxArity(G0 ) = K : Proof The only rule that adds nodes in the graph is Tpl-Unif . It consumes ()) and creates at most MaxArity(G0) -nodes. No rule creates tuple nodes, hence at most Nb(G0 ; ()) MaxArity (G0 ) nodes can be created. a tuple node (denoted by 116 8.1 A polynomial Algorithm via graph rewriting Proposition 36 Let G be a normalised graph. Nb(G; ) jP ortssk (G0 )j + 2K : Proof By Proposition 26, we know that in a normalised graph, Nb(G; ) jP ortssk;(G)j = jP ortssk (G)j + jP orts(G)j. Now using Propositions 34 and 35 we deduce Nb(G; ) jP ortssk (G0 )j + 2K . Proposition 37 Let G be a normalised graph. By applying an -rule and/or Neutralisation, at most MaxArity (G0 ) + 1 -nodes are created. Proof By inspection of the rules we see that at most MaxArity (G) + 1 nodes are created, and using Proposition 34 we deduce that MaxArity (G) + 1 MaxArity(G0 ) + 1. Proposition 38 1. At any step in the iteration: Nb(G; ) jP ortssk (G0 )j + 2K + MaxArity(G0 ) + 1 = K . 2. Let jGj denote the size of the graph. jGj K + 2K + ResSize(G0) + Nb(G0; sk) = Kjj Proof 1. In each iteration, we start with a normalised graph and apply at most -rule (possibly with an application of Neutralisation ), before normalising again. Since G is normalised, by Proposition 38 Nb(G; ) jP ortssk (G0)j + 2K, and by applying a neutralisation and rule, at most (MaxArity (G0 ) + 1) -nodes are created (by Proposition 37). There are then at most jP ortssk (G0 )j + 2K + MaxArity (G0 ) + 1 -nodes. We count the number of nodes of each kind: #-nodes and -nodes are only created by -rules, one at a time, hence Nb(G; #) + Nb(G; ) ResSize(G0 ). Since every permutation is attached to a -node, Nb(G; ) Nb(G; ). Hence: jGj = Nb(G; ) + (Nb(G; #) + Nb(G; )) + Nb(G; sk)+ Nb(G; ) + Nb(G; ) K + ResSize(G0) + Nb(G0; sk) + 2K one 2. G 117 8.1 A polynomial Algorithm via graph rewriting The constant K N dened in the proposition below is an upper bound on the number of normalisation steps in each iteration of the algorithm. Proposition 39 There are at most 2K +4K = KN normalisation steps in each iteration of the while-loop. Proof By Proposition 2, when we normalise the graph in each iteration of 2Nb(G; )+4Nb(G; ) steps, hence (by Propositions 35 and 38) we obtain an upper bound: 2K + 4K = K . the while-loop there are at most N We have therefore an upper bound for the number of iterations, and the num- ber of normalisation steps, which shows that the algorithm is indeed terminating. Below we will analyse the cost of each operation, in order to establish the complexity of the algorithm, but we can already show its correctness. Proposition 40 (Soundness and completeness of -solving) The algorithm dened above is sound and complete. Proof Consequence of Propositions 21 and 30, together with Propositions 39 and 33, which entail the termination of the loop. At the end of the rst phase of the unication algorithm no -constraints remain, and provided that ? has not been generated, the second phase can start. At this point, the only children of the node introduced by the rules. P r will be either # nodes or nodes 8.1.1.8 Cost of the rules Let A0 be the set of atoms in the initial problem. A permutation is implemented as an AVL tree of pairs (a; (a)) for a 2 supp(). The algorithm does not introduce new atoms in the problem, so at each step there are at most jA0 j atoms in G, and in particular in permutations in G. Thus, (a) in 's AVL, which can be done in O(log(jA0 j)) . Inversion and union of permutations are also O(jA0 j log(jA0 j)). applying to a requires searching the value 1 Below we calculate the cost of applying each normalisation rule: 1 We follow the notations of [49], to which we refer the reader for a denition of O. 118 8.1 A polynomial Algorithm via graph rewriting Rule Apply-Perm : Since we use AVLs to represent permutations, computing t for a value node t has a cost O(log(jA0 j)). Rule Id-Perm eliminates identity permutations, its cost is constant: Rule Order-Unif also has a constant cost: O(1) Rule Order-Perm involves the computation of the inverse of a permutation. This can be done in O(1) Rule Consec-Perm O(jA0 j log(jA0 j)) with AVLs. composes consecutive permutations. Again the cost is O(jA0 j log(jA0 j)) with AVLs as permutations. Hence, applying a normalisation rule is at most in We now turn our attention to rules. O(jA0 j log(jA0 j)). First note that some of these rules require a redirection of all parents of a node to another node. This operation is linear in the number of parents, so linear in jGj. The generalised occur check (i.e., checking whether a node in t has s as a child, see below) is also linear in the size of t, therefore linear in jGj. P s if no node in t ) P s t t has s as a child (generalised occur-check), otherwise the right-hand side is Cost: Rule Same-Term Rule Subst Rule Id-Unif at most ?. jGj erases trivial equations, its cost is constant: O(1) Rule ?-Unif detects incompatibility failure, again its cost is constant: O(1) requires the computation of the support of . The cost is O(jA0 j log(jA0 j)) with permutations as AVLs. creates a substitution, its cost is constant: 119 O(1) 8.1 A polynomial Algorithm via graph rewriting Rules Fct-Unif , Abs-Unif-Same operations, with constant cost Rule Tpl-Unif tuple: and O(1). Abs-Unif-Di involve simple pointer has a cost proportional to the number of elements in the O(arity of the tuple), that is O(MaxArity(G0 )). (max(jGj+MaxArity(G0); jA0j log(jA0 j))) O(jGj + MaxArity(G0 ) + jA0 j log(jA0 j)). Note that MaxArity(G0 ) Kjj and jGj is bounded by Kjj , therefore the cost is at most O(jA0 j log (jA0 j) + Kjj ). Hence, the cost of applying an rule is at most O Finally, we consider an application of the Neutralisation rule: M N N ) n n1 ... M 1 n nn ... n1 nn Cost: O(jGj + MaxArity(G0 ) + jA0 j log(jA0 j)) (jGj + MaxArity(G0)+ jA0j log(jA0j)), hence again it is O(jA0j log(jA0j) + Kjj). Proposition 41 This phase is O(ResSize(G0 ) (Kjj + KN jA0 j log(jA0 j))). The application of an rule or Neutralisation is in O Proof For each iteration, by Proposition 39, there are at most K N steps of (jA0jlog(jA0j)). In addition, one -rule is applied, and at most one neutralisation step, each in O(jA0 j log (jA0 j) + Kjj ). The cost of an iteration is then O(K jA0 j log (jA0 j) + 2(jA0 j log (jA0 j) + Kjj ), or, simplifying, O(K jA0 j log (jA0 j) + Kjj ). Because there are, by Proposition 33, at most ResSize(G0 ) iterations, the loop is in O(ResSize(G0 ) (Kjj + K jA0 j log (jA0 j))). The normalisation phase before entering the loop is also O(K jA0 j log (jA0 j)) and hence does not change the complexity of the algorithm. normalisation, each one in O N N N N 120 8.1 A polynomial Algorithm via graph rewriting 8.1.2 Freshness constraints The algorithm to solve nominal problems has two phases, as mentioned above. The rst phase deals with constraints, as described in the previous section. Assuming the problem has a solution, at the end of the rst phase we are left #t (where A is a set of atoms, see rule Same-Term in Section 8.1.1.3) and a substitution of the form [Xi 7! ti ]i2I . with a set of freshness constraints of the form A We describe in this section an algorithm to check that a set of atoms is fresh in a term t. The idea is to traverse the graph representing the term, and annotate term nodes with the set of atoms for which freshness has already been checked (to avoid repeated computations). Denition 66 (Annotated terms) Given a graph representing a term t, we annotate each term node by a set of atoms, and denote the set of atoms attached to the node n by (n). Initially, all term nodes are annotated with the empty set of atoms. Denition 67 Let G be the graph representing a problem subgraph representing a freshness constraint t where n is its root node. One step of freshness checking for (fresh) where the function A#n freshl ) ( G0 A#t and G00 P r. Let G0 the graph representing is dened by the following rule: freshl (A n (n) # n) with side eect (n) := (n) [ A is dened as follows (we assume freshl (; # n) freshl (A # f (n)) freshl (A # (ni )i=1:::m ) freshl (A # [a]n) freshl (A # n) freshl (A # X ) = = = = = = freshl (A # a) = 121 ; be the A 6= ;): A#n (A # ni)i=1:::m (A n a) # n 1 (A) # n A 8 #! X <; a 62 A :? a 2 A 8.1 A polynomial Algorithm via graph rewriting Note that when freshl is applied to A # X we generate a new constraint A #! X which will be part of the solution to the problem. Denition 68 Let G be the graph representation of a nominal problem P r. As0 0 sume G is the graph obtained after running the rst phase of the algorithm and G does not contain the rule ?. The algorithm to check freshness constraints simply applies (fresh) dened above until the graph is irreducible. Proposition 42 (Irreducible forms) Irreducible graphs do not contain # nodes. Only freshness constraints of the form A #! X may remain. Proof If there is a Proposition 43 Proof # node, rule (fresh) can be applied. The freshness check is correct. ( ) The rule fresh memorises the freshness information that has already been checked (or is being checked), to prevent checking multiple times that the same atom is fresh in the same term. Formally, checking A # t is equivalent (a # t)a2A. So checking A # t; B # t is equivalent to checking (AnB ) # t; (A\B ) # t; (B nA) # t; (B \A) # t. Although we have the constraint (B \ A) # t twice, we only need to check it once, so we do A # t; (B n A) # t. to checking freshl follows the inductive denition of the freshness predicate (see Section 4). When the propagation reaches a variable (A # X ) we save this resolved constraint as A #! X so that (fresh) can no longer be applied. The denition of Proposition 44 The freshness check terminates in at most 2 jGj jA0j steps of rewriting. Proof We compute the maximum number of steps by counting the number of times that an atom is checked for freshness in a given term node in this phase. A = ; or if we have already checked all atoms in A, i.e. A n (n) = ;, constraint is simply removed. Let jGjtn be the number of term nodes in Note that if the G, term nodes are never created in this phase. Thanks to annotations there is jA0j steps of (fresh) on n where A n (n) 6= ; so jA0jjGjtn application of (fresh) where A n (n) 6= ;. An empty set can either be created in the previous phase, or be generated by freshl (A # [a]n); hence there for each term node n at most 122 8.1 A polynomial Algorithm via graph rewriting Nb(G; #) + jA0 j jGjtn applications of (fresh) where A n (n) = ;. Therefore, we have at most Nb(G; #) + 2 jA0 j jGjtn 2 jA0 j jGj steps of are at most rewriting. Before considering the cost of the freshness check, we can state the following property, which is a direct consequence of Propositions 40 and 43: Proposition 45 The algorithm to solve nominal problems by 1. transforming the problem into a graph; 2. applying the rst phase to eliminate constraints; 3. applying the second phase to eliminate freshness constraints; is sound and complete. 8.1.3 Cost We now analyse the complexity of the second phase in the algorithm (freshness checking). We start by analysing the cost of freshl for each case: freshl (; # n) : O(1) freshl (A # f (n)) : O(1) freshl (A # (ni )i=1:::m ) : O(m) freshl (A # [a]n) : O(log(jA0j)) since sets are represented as AVL trees freshl (A # n) : O((jA0j log(jA0j))2); we discuss this case below freshl (A # X ) : O(1) freshl (A # a) : O(log(jA0j)) due to the use of AVL trees The cost of freshl (A # n) is due to the computation of 1 in O(jA0 j log(jA0 j)) and the application of 1 to A in O(jA0 j log(jA0 j)) too. Proposition 46 Freshness checking is ( O(jGj jA0 j3 log(jA0 j)2 ). ) freshl and the update of (n), which is a union of sets and can be done in O(jA0 j log (jA0 j)). We deduce that each step of rewriting with these rules is at most in O((jA0 j log (jA0 j))2 ). Since there are at most 2 jGj jA0 j steps of rewriting by Proposition 44, the Proof A rewrite step of fresh consists of one step of result follows. 123 8.1 A polynomial Algorithm via graph rewriting 8.1.4 Total cost in time for the unication algorithm An upper bound on the total cost of the unication algorithm can be obtained by adding the cost of the phase of resolution of constraints and the phase of freshness checking. Proposition 47 The following is an upper bound for the unication algorithm: O(ResSize(G0 ) (Kjj + KN jA0 j log(jA0 j)) + Kjj jA0 j3 log(jA0 j)2 ) G for the freshness algorithm is the output of the resolution algorithm, hence jGj Kjj . The result is then obtained by adding the Proof The input graph two bounds (see Propositions 41 and 46). To simplify the formula above, we use the following properties: Proposition 48 1. ResSize(G0 ) = O(jG0 j MaxArity(G0 )) 2. K = O(jG0 j MaxArity(G0 )) 3. K = O(jG0 j MaxArity(G0 )) 4. Kjj = O(jG0 j MaxArity(G0 )) 5. KN = O(jG0 j MaxArity(G0 )) Proof 1. For each node n in G0 , ResSize 2. 3. 4. (n) 2MaxArity(G0) then ResSize(G0) 2jG0j MaxArity(G0). K = Nb(G0 ; ) + Nb(G0 ; ()) MaxArity(G0 ) jG0j + jG0j MaxArity(G0) = O(jG0j MaxArity(G0)). K = jP ortssk (G0 )j + 2K + MaxArity(G0 ) + 1. jP ortssk (G0)j jG0j MaxArity(G0) because the arity of each node is less than or equal to MaxArity (G0 ). Kjj = K +2K +ResSize(G0 )+Nb(G0 ; sk) and each element in the addition is bounded by O(2jG0 j MaxArity (G0 )). 124 8.1 A polynomial Algorithm via graph rewriting 5. K = 2K + 4K and again O(2jG0 j MaxArity(G0 )). N each element in the addition is bounded by Proposition 49 (Cost in time for the unication algorithm) The follow- ing is also an upper bound for the unication algorithm: O(K 4 log2 (K )) where K = jG0 j MaxArity(G0 ) Proof jG0j K . Consequence of Propositions 47 and 48, using the fact that 125 jA0j 8.1 A polynomial Algorithm via graph rewriting 3 We have presented algorithms to solve nominal unication. QNU is the most ecient but as it has been seen, it is not easy to use in into a larger program. Though being a bit less ecient that QNU, AQNU is simple and can easily be part of a larger program, which is nice in practice. The graph rewriting algorithm is the less ecient but shows an interesting way of solving nominal equivalence. QNU and AQNU extend the corresponding algorithms on rst-order terms to nominal terms without modifying the structure of the algorithm but only adding name management. This management adds an extra cost which depends on the implementation of atoms, permutations and sets of atoms. Generally, the complexity of the extended algorithm is the product of CA and the complexity of the original algorithm. Finally, we presented a concrete implementation of 126 QNU and AQNU. Part III -equivalence, Matching and Rewriting 127 Let P r = fs tg be a nominal -equivalence (resp matching) problem. P r can be solved by considering it as a nominal unication problem whose solutions (; ) have to satisfy that the domain of (dom() is empty (resp. dom() \ V(s) = ;). However, in some cases, some optimisations can lead to more ecient nominal -equivalence and matching algorithms. This part presents a very modular algorithm which adapts itself to the input problem to reach better eciency. This algorithm is based on the one we presented in [10]. The implementation has been completely rethought and is now using the ideas presented in the chapter 3 and streams. This implementation is better than the previous one on many points: it is as close as possible to the abstract description of the algorithm. it completely abstracts the actual implementation of permutations and sets. it is extremely modular. it is much easier to read, understand and to use. The implementation in Haskell is in appendices A.6, A.9 and A.10. Chapter 9 introduces the notions of environment and presents the algorithm in an abstract way. Then chapter 10 describes the implementation. 128 Chapter 9 A modular algorithm to check -equivalence and solve matching constraints Ecient implementations of nominal unication rely on the use of lazy permutations: permutations are only pushed down a term when this is needed to apply a transformation rule, and then, they are only pushed one level down the term. Since lazy permutations may grow (they accumulate), in order to obtain an ecient algorithm we will compose the swappings eagerly. The key idea is to work with a single current permutation, represented by an environment. The algorithms to check -equivalence constraints and to solve matching prob- lems (linear or non-linear) will be built in a modular way. The core module is composed of four phases and is common to both algorithms; only the nal phase will be specic to matching or -equivalence. 9.1 Environments We begin by introducing the notion of an environment. Environments will be associated to terms and used to store a permutation and a set of atoms. Denition 69 Let s and t be terms, be a permutation, and A be a nite set of atoms. An environment is a pair (; A). We denote by the permutation 129 9.1 Environments A the set of atoms) of an environment. We write s t s t; A # t and call s t an environment constraint. (resp. X b A = fag represents the problem X (a b)b; a#b. For example, the environment constraint Denition 70 An where to represent = (a b) and environment problem P r is either ? or has the form s1 1 t1 ; : : : ; sn n tn iti (1 i n) are environment constraints. n abbreviate it as (si i ti )1 . where si Denition 71 We will sometimes The problems dened in Section 4 will be called standard to dis- tinguish them from environment problems (standard problems have no environ- standard form of an environment problem is obtained by applying ments). The the rule: s t =) s t; A # t as many times as possible. We denote by ment problem P r. [ P r] the standard form of an environ- Thus: [ s1 1t1; : : : ; sn ntn] = s1 (1 ) t1 ; (1 )A # t1 ; : : : ; sn (n ) tn ; (n )A # tn This rule is terminating because it consumes a each time, without creating any. There are no superpositions, so the system is locally conuent and because it terminates it is conuent [38]. Therefore the standard form of an environment problem exists and is unique. Denition 72 The solutions of an environment problem are the solutions of its standard form (see Section 4). A problem problems are equivalent ? has no solutions. Two environment if their standard forms are equivalent, i.e., have the same solutions. As a consequence of Denition 72, two equivalent environment problems have the same set of solutions. 130 9.2 Core algorithm The set of reduction rules on standard problems given in Section 4 transform a problem into an equivalent one (i.e., solutions are preserved, see [51]). Below we will give a set of transformation rules for environment problems, and we will prove that a step of rewriting P r =) P r0 on an environment problem can be simulated by some steps of reduction on its standard form using the rules in Section 4, that is, [ P r] =) [ P r0] . Therefore P r and P r0 are equivalent. It is easy to translate standard problems into environment problems, as shown below. Denition 73 The translation Env from standard problems into environment problems is inductive, with base cases: Env(s t) = s t where = (Id; ;) and Env(A # t) = t t where = (Id; A). This transformation is linear in time and in space. Therefore, from a (log- )linear algorithm solving environment problems we can derive a (log-)linear algorithm to solve standard problems. In the following sections, we restrict our attention to checking -equivalence constraints and solving matching problems. In the latter case, in environment s t, the term t will not be instantiated and variables in s and t are disjoint. If right-hand sides t are ground terms, we will say that the problem constraints is ground, and non-ground otherwise. 9.2 Core algorithm (si iti)n1 into an equivalent standard problem of the form (Xi ti )i2I ; (Aj # Xj )j 2J . There are four phases in the core algorithm. The rst one reduces constraints, by The core of the algorithm transforms an environment problem propagating i over ti . The second phase eliminates permutations on the lefthand side of constraints, and the third phase reduces freshness constraints, also by propagating i over ti . Finally, the fourth phase computes the standard form of the resulting problem. Denition 74 P r be an environment problem. of applying the core algorithm on P r . Let 131 We denote by Pr c the result 9.2 Core algorithm Below we describe each phase of the core algorithm. Phase 1 The input is an environment problem P r = (si i ti )n1 , which we will reduce by applying the following transformation rules. ( if a = t and t 62 A t =) ?P r otherwise ( P r; (si ui )m 1 if t = (u1 ; : : : ; um ) P r; (s1 ; : : : ; sm ) t =) otherwise (? P r; s u if t = f u P r; f s t =) otherwise (? 0 if t = [b]u P r; [a]s t =) ?P r; s u otherwise P r; where 0 a = ((a b) ; (A [ f 1ag) n fbg) in the last rule, and a; b could be the same atom. We show below that the rules transform an environment problem into another equivalent environment problem. The environment problems that are irreducible for the rules above will be called phase 1 normal forms or ph1nf for short. Proposition 50 (Phase 1 normal forms) The normal forms for phase 1 rules n are either ? or (i Xi i si )1 where si are nominal terms and n 0. Proof If the left-hand side of an constraint is an atom, function applica- tion, tuple or abstraction, then the constraint can be reduced using the phase 1 rules. Note that if the problem is ground, the ph1nf is either ? or empty. Proposition 51 (Correctness) Let P r be an environment problem and assume P r =) P r0 using the phase 1 rules. Then P r and P r0 are equivalent. Proof To prove that Pr and P r0 are equivalent we have to show that their standard forms have the same solutions. We prove this by showing that if a ? then it has no solutions, and that if P r =) P r0 6= ? then there is a problem Q such that [ P r] =) Q using standard reduction rules problem reduces to 132 9.2 Core algorithm (given in Section 4), and Q is equivalent to [ P r0] . Since standard reduction steps preserve solutions, this implies the equivalence of Pr and P r0 . We distinguish cases according to the rule applied: P r; a t =) P r0. [ a t] = a t; A # t so if t is not an atom or if t is an atom but a 6= t or t 2 A then there is no solution. If t is an atom b, t = a and t 62 A , then P r0 = P r, and the reduction Then step can be simulated as follows: [ P r] ; a t; A # t [ P r] ; a a; (c # b)c2A =) [ P r] ; (c # b)c2A =) [ P r] P r; f s t =) P r0. If t is not a term of the form f u then there is no solution. If t f u the step can be simulated by standard reduction steps as follows: [ P r] ; f s f u; A # f u =) [ P r] ; s u; A # u = [ P r; s u] = [ P r0 ] P r; (s1; : : : ; sn) t =) P r0. If t is not a tuple, there is no solution. If t = (u1 ; : : : ; un ) the step can be simulated by standard steps as follows: [ P r] ; (s1; : : : ; sn) (u1; : : : ; un); A # (u1; : : : ; un) =) [ P r] ; (si ui)n1 ; (A # ui)n1 = [ P r; (si ui)n1 ] = [ P r0] P r; [a]s t =) P r0. If t is not an abstraction, there is no solution. If t [b]u, where a and b denote two atoms not necessarily dierent, then we distinguish two cases: = a, then [ P r] ; [a]s [b]u ; A # [b]u =) [ P r] ; s u; A n fbg # u = [ P r] ; s ((a b) )u; (A [ f 1(a)g) n fbg # u = [ P r; s 0u] = [ P r0 ] where 0 = ((a b) ); (A [ f 1 a)g n fbg). If b 133 9.2 Core algorithm = If b 6 a, then, since freshness is preserved by permutation (i.e., it is an equivariant relation, see [20, 51]), we obtain: [ P r] ; [a]s [b]u ; A # [b]u =) [ P r] ; s ((a b) )u; A n fbg # u ; a # ( u) = [ P r] ; s ((a b) )u; (A [ f 1(a)g) n fbg # u = [ P r; s 0u] = [ P r0 ] where 0 = ((a b) ); (A [ f 1 a)g n fbg). Phase 2 This phase takes as input an environment problem in ph1nf, and moves the permutations to the right-hand side. More precisely, given a problem in ph1nf, we apply the rule: X t =) X ( 1 )t 1 = ( 1 ; A ). Note that 1 X t represents X t; A #t. where ( 6= ) Id applies only to here, because If the problem is irreducible (i.e., it is a normal form for the rule above), we say it is a phase 2 normal form, or ph2nf Proposition 52 (Phase 2 normal forms) for short. Given a ph1nf problem, it has a unique normal form for the rule above, and it is either form (Xi iti)1 Proof n, where the terms ti ? or a problem of the are standard nominal terms. The rule is clearly terminating (each application consumes a permuta- tion on the left) and it does not overlap with itself, therefore it is conuent [38]. This implies the unity of normal forms. Given a ph1nf problem, its normal form cannot contain a suspended variable on the left-hand side. Proposition 53 (Correctness) X t is equivalent to X ( 1 )t. Proof We need to show that the standard forms of both problems are equiv- [ X t] = X t ; A # t, and [ X ( 1)t] = X ( 1 )t ; A # t, the result follows directly from the preservation of by permutations (see [20, 51]). alent. Since 134 9.2 Core algorithm Phase 3 In the phases 1 and 2 we deal with constraints. Phase 3 takes a ph2nf and simplies freshness constraints, by propagating environments over terms. Since the input is a problem in ph2nf, each constraint has the form X t. We reduce it with the following rewrite rules, which propagate over t and deal with problems containing ? (denoted P r[?]): ( =) ? a aa 262 A A f t =) f ( t) (t1 ; : : : ; tj ) =) ( ti )j1 [a]s =) [ a](( n fag)s) (X ) =) ( )X P r[?] =) ? where n fag = ( ; A n fag) and = (( ); 1 (A )). a These rules move environments inside terms, so formally we need to extend the denition of nominal term, to allow us to attach an environment at any position inside the term. We omit the denition of terms with suspended environments, and give just the grammar for the normal forms, which may have environments suspended only on variable leaves: Denition 75 The language of normal environment terms is dened by: T = a j f T j (T ; : : : ; T ) j [a]T j X If the problem is irreducible (i.e., it is a normal form for the rules above), we say it is a phase 3 normal form, or ph3nf for short. Proposition 54 (Phase 3 normal forms - ph3nf) n phase are either ? or (Xi ti )1 where ti 2 T . Proof The normal forms for this By inspection of the rules, it is easy to see that environments move down, and suspend on variables. To give a semantics to the problems generated in phase 3, we extend the denition of a standard form (see Denition 70) as follows: 135 9.2 Core algorithm Denition 76 (Standard Form) The standard form of a problem with sus- pended environments is obtained by normalising with the rewriting rule s C [ t] =) s C [ t]; A # t where s and t are nominal terms. Remark 7 The rule in Denition 71 is a particular case of this one, taking an empty context, that is, C [ ] = [ ]. This rule is terminating and conuent, therefore normal forms are unique; we will use the notation [ P r] as before. Proposition 55 (Correctness) Let P r be a problem in ph2nf, and P r =) P r0 0 using a phase 3 rule. Then P r and P r are equivalent. Proof As before, it is sucient to show that if ? is obtained then the problem has no solutions, and otherwise the standard forms of P r and P r0 are equivalent. We show the three interesting cases (recall that standard reductions preserve solutions). Assume we have s a. Notice that C [ a]. Then its standard form is [ s C [ a]]]; A # ? can only be obtained if a 2 A, and in this case the stan- dard form has no solutions. If a 62 A then [ s C [ a]]]; A # a is equivalent to [ s C [ a]]]. Assume we have s C [ [a]t]. Then its standard form is [ s C [ [ a] t]]]; A # [a]t which is equivalent to [ s C [ [ a] t]]]; A n fag # t, which is the standard form of s C [ [ a]( n fag)t]. Assume we have s C [ (X )]. Then its standard form is [ s C [( )X ]]]; A # X and using standard reduction rules we obtain [ s C [( )X ]]]; 1 (A ) # X , equivalent to [ s C [( )X ]]]. 136 9.2 Core algorithm Phase 4 This phase computes the standard form of a ph3nf, using a particular case of the rule given in Denition 76: X C [ X 0 ] =) X C [ X 0 ] ; A # X 0 Denition 77 We dene the sets of variables on the left-hand (resp. right-hand) -constraint as Vl (s t) = V(()s) (resp. Vr (s t) = V(t)). We extend the notation to problems as follows: Vl=r (A # t) = ; and Vl=r (P r1 ; P r2 ) = Vl=r (P r1 ) [ Vl=r (P r2 ). side of an Proposition 56 (Phase 4 normal forms - ph4nf) If we normalise a ph3nf using the rule above, we obtain either ? or (Xi ui )i2I ; (Aj # Xj )j 2J where ui are nominal terms and I; J may be empty. Moreover, (Xi )i2I Vl (P r ) and (Xj )j 2J Vr (P r ). Thus, if the right-hand sides of -constraints in P r are ground, there are no freshness constraints in the ph4nf (because Vr (P r ) = ;). Proof By Proposition 54, environments can only be suspended on variables in a ph3nf. So if the problem is irreducible by the rule above, it cannot contain any environment. The variable property follows from the fact that the rules never move subterms -constraint into the right-hand side, and all the freshness constraints generated involve subterms of the right-hand side. of the left-hand side of an Clearly, this phase preserves the set of solutions, it terminates and it does not ?, therefore if the resulting problem is ? so was the ph3nf. Since all the reduction rules, except the rule dealing with ?, are local (i.e. raise only modify one constraint), the result of applying the core algorithm to a set of constraints is the union of the results obtained for each individual constraint (assuming ?; P r ?): (si iti)n1 c = (si ti c)n1 Thus, without loss of generality we can consider the input of the core algorithm to be one single constraint s t . 137 9.3 Checking the validity of -equivalence constraints 9.3 Checking the validity of -equivalence constraints P r of -equivalence constraints is valid, we rst run the core c algorithm on P r and then reduce the result P r by the following rule: To check that a set ( ) where ( P r ; X t =) P r ; supp() # X ? = if t X otherwise supp() is the support of : supp() = fa j a 6= ag. Since this rule is terminating (each application consumes one -constraint) and locally conuent, it is conuent [38], therefore normal forms exist and are unique. Denition 78 We will denote by P r the normal form of Pr c by the rule above. Proposition 57 (Normal forms P r ) P r is either ? or (Ai # Xi)n1 . P r c is a ph4nf, so by Proposition 56 it is either ? or (Xi ti )n1 ; (Aj # Xj )j 2J where ti are standard terms. While there are constraints Xi ti , the problem is reducible by (), and each -constraint is replaced by a set of freshProof ness constraints. Proposition 58 (Correctness) n is (Ai # Xi )1 then P r ` P r. Proof If P r is ? then P r is not valid. If P r The core algorithm preserves solutions, as a consequence of Proposi- X t is valid (see Section 4) if and only if t is X and supp( ) # X , because ds(; Id) = supp( ). Hence () is correct. c If the left-hand sides of -constraints in P r are ground, then P r = P r ; rule () is not necessary in this case (by Prop 56). Formally: tions 51, 53, 55. Moreover, Proposition 59 P r = P r c . Let P r = (si i ti )n1 138 (si) = ; for 1 i n. where V Then 9.4 Solving Matching Problems Remark 8 The rule (), when it does not involve ?, is also local (i.e. it only aects one constraint in the problem), so in practice we will implement two functions, one taking only s t as input and computing s t , and the other taking an environment problem as input and applying the rst function on each constraint: (si iti)ni = (si iti )ni In the case of ground terms, our algorithm to check -equivalence relies only on the information stored in . Thus, it could be seen as an implementation of the inductive denition of ground -equality used in [25] (and attributed to Shankar [47]), which also relies on a list of pairs of names. 9.4 Solving Matching Problems P r, we rst run the core algorithm on P r and then c if the problem is non-linear we normalise the result P r by the following rule. To solve a matching problem (?) P r; X s ; X t =) ( P r ; X s ; s t ? if s t otherwise This rule is terminating: each reduction consumes at least one since Pr does not introduce 6= ? -constraint -constraints (by Proposition 57) and is also terminating. Denition 79 We denote by P r ? a normal form of Pr c by the rule (?). Proposition 60 (Normal forms P r ? ) If we normalise P r c using the rule n m above, we obtain either ? or (Xi ti )1 ; (Ai # Xi )1 where ti are standard terms, n all Xi in the equations (Xi ti )1 are dierent variables and 8i; j : Xi 62 V(tj ). (?) does not produce -constraints. The property then follows from Proposition 56, since V(()ti ) Vr (P r), Xi 2 Vl (P r), and Vl (P r) \ Vr (P r) = ; in a matching problem. n A problem of the form (Xi ti )1 where all Xi are distinct variables and Xi 62 V(tj ) is the coding of an idempotent substitution dened by ( t if 9i X = Xi (X ) = i Proof By Proposition 57, rule X otherwise. 139 9.4 Solving Matching Problems (Ai # Xi)n1 is a freshness context . So the result of the algorithm is either ? or a pair (; ) of a substitution and a freshness context. Proposition 61 (Correctness) P r ? is a most general solution of the matching problem P r . Proof Rule (?) is justied by the equivalence: X s; X t , X s; s t ? is raised, either: P r c = ? and the problem has no solution by correctness of the core algoIf rithm. Rule (?) raised ?, so s t = ?, i.e., s and t are not -equivalent, and the problem has no solution. Note that if variables occur linearly in patterns (i.e., we have a linear matching problem), then the result of the core algorithm is already the solution of the problem. Formally: Proposition 62 Let P r = (s t) ? = P r c. once in s. Then P r Proof for each X If all variables and rule X in where for all X 2 V(s), X occurs only s are linear, then P r c contains only one equation (?) cannot be applied. 140 Chapter 10 Implementation In this chapter we describe the implementation of the core algorithm, then the implementation of the nal phases specic to -equivalence and matching respectively. In the sequel, occurring in P r0 . P r0 will denote the input problem and A0 the set of atoms The core algorithm is implemented using a monadic tower made of rst-class monadic signatures as presented in chapter 3. The lower layer is a agement layer. A store store man- is a structure associating a value to some variables. Substitution, freshness context and arruf are stores. Because we want the de- scription of the algorithm and the implementation to be as simple as possible, we consider in the rest of the chapter that stores are arrays but it may sometimes be easier to take another implementation of stores: for example as persistent data. In the implementation, the actual representation of stores is abstracted and all algorithms use the rst-class monadic signature StoreCall dened in A.4 to manage stores. The next layer is the exception handling monad transformer SCont (DMonadError error) presented in chapter 3.5.2. For the sake of simplicity the lifting operations required to call an operation of a layer from an upper layer will not be shown. For a detailed implementation of these algorithm, please refer to appendices A.4, A.6, A.9 and A.10. 141 10.1 The Freshness Layer The complexity of the core() algorithm depends heavily on the implementation of sets and permutations. Unfortunately the appropriate implementation choice depends on the input. So we add the two layers SetCall and PermCall dened in section 4.4. For now the tower has four layers, the lowest is StoreCall for store management, the next one is DMonadError for exceptions, then SetCall set for native sets and the top one is PermCall perm for native permutations. The Monad is 1 2 3 4 SContT ( SContT ( SContT ( SCont ( PermCall ( SetCall ( DMonadError ( StoreCall perm ) set ) ) s t o r e Var ) ) ) ) error 10.1 The Freshness Layer In the process of the core() algorithm, freshness constraints erated. For solving A #X are gen- -equivalence and matching problem they have to be added to the solution if any but for rewriting they will have to be checked against the freshness context of the rule. Once again we dene a new layer to the tower. This layer is implemented in the Haskell module A.10 as the rst class monadic signature data type FreshContextCall set where set is the native type of sets. It provides the following calls: FreshConstraint deals with a freshness constraint FreshContextRule gives, for a variable A#X X , the set of atoms A = f a j a # X 2 g is the freshness context of the rewriting rule. When solving an -equivalence or matching problem, is considered empty. where FreshToList returns the list corresponding to the freshness context Let arr an array of sets of atoms indexed by variables. arr represents the freshness context if and only if arr [X ] = fa j a # X 2 g 142 10.2 Only one environment: The environment layer When solving an -equivalence or matching problem, we start with an empty context and ll it with constraints generated by the algorithm. The interpretation of FreshContextCall for this chapter is described in the algorithm 11. 1 2 3 4 interpretFreshContextSolve(c ) = ; switch c do case FreshConstraint A # X arr [X ] = arr [X ] [ A ; k k(()) 5 case FreshContextRule X 6 k(;) 7 k case FreshToList k let l be the list corresponding to arr ; k(l) 8 9 10 11 endsw Algorithm 11: Interpretation of Freshness when Solving an -equivalence or 12 matching problem We now have ve layers in the tower: 1 2 3 4 5 SContT ( SContT ( SContT ( SContT ( SCont ( FreshContextCall ( PermCall ( SetCall ( DMonadError ( StoreCall set ) perm ) set ) ) s t o r e Var ) ) ) ) ) error 10.2 Only one environment: The environment layer As it is presented above, the core algorithm uses many environments, created by either copying (for example in the tuple case of phases 1 and 3) or modifying (for example in abstraction case of phase 1 and 3) an existing one. Here we will use only on environment. Modications will be done in place and copies will point to the same environment. 143 10.2 Only one environment: The environment layer In practice this single global environment is abstracted into the sixth monadic layer implemented in the Haskell module A.9. The rst-class monadic signature data type is EnvCall set perm where set (resp. perm) is the type of native set and (resp. permutations) . The layer provides the following operations: envImage of type A ! A ! m A. envImage a returns the image of a by the environment envImageInv of type envIsFresh of type A ! m Bool. envIsFresh a returns whether or not there is a # in the environment envSetFresh of type the atom envImage a returns the inverse image of a by the environment a constraint A ! A ! m A. A ! Bool ! m (). envSetFresh a b add (resp. remove) a from the freshness constraint set if b is true (resp. envComposeRight of type > m (). false). pi perform in place > m (). envComposeLeft pi perform in place envLocal of type F ORALLaDOT m a ! m a. envLocal c performs the comenvComposeLeft of type envComposeLeft putation c locally which means that even if c modies the environment, it will be the same as before after the call. envPerm of type envSet of type m returns the permutation of mS returns the freshness set of A environment layer is not a simple state monad because envLocal would require to save the whole environment before computing c, which would be done in linear time in the size of the environment but we need constant time. Instead the monad maintains a stack of the modications done and undoes the operations on the stack when exiting envLocal. Storing these modication is cheap: storing envComposeLeft pi (resp. envComposeRight) will only require to store a data (ComposeLeft, pi) what is linear in the size of pi. envComposeLeft (resp. envComposeRight) with To undo it we only need to call 1. easy, we have only to remember whether or not Storing envSetFresh a b is also a is in the freshness set and, when exiting envLocal, adding (resp. removing) it if it was (resp. was not) in it. 144 10.2 Only one environment: The environment layer Phase2 and Phase4 Phase 2 is: X t =) X ( 1 )t ( 6= ) Id and Phase 4: X C [ X 0 ] =) X C [ X 0 ] ; A # X 0 . The two functions are described in algorithm 12. Actually Phase 2 has been a bit modied to be able to perform nominal rewriting. Because freshContextRule() returns always ; when solving a matching problem, these modication aect nei- ther the correctness nor the complexity of Phase 2. Input: A suspended variable X and a term t 1 ; 2 3 4 5 phase2( X; t) = ; envComposeLeft( 1 ) ; A = freshContextRule(X ); 0 = envPerm; for a 2 (0 1 (A) do 6 envSetFresh(a,>) 7 8 end 9 ; return (X; t); 10 Input: A variable X 11 ; 12 13 14 15 16 phase4(X ) = ; A = envSet(); = envPerm(); freshConstaint(A; # X ); return X ; Algorithm 12: Phase 2 and 4 145 10.3 Combining the phases: Streams 10.3 Combining the phases: Streams Modifying the environment in place requires running the phases in a specic order. For example, let us consider the reduction of a phase3, it is reduced into ( [ a]( n fag)s; u. (nomAbsat; u) by the We can keep virtually two dierent environment thanks to envLocal, but switching from one environment to another would require to undo all the modication made on the rst, to apply those of the second. It would require too many operations. It is more ecient to reduce completely one branch, for example the environment to reduce completely reduction strategy. u. ( n fag)s and only then restore Such a reduction is called a local The problem is running phases one after another is not a local reduction strategy. The idea is to interleave the phases. It could be done simply by calling phase 2 in phase 1, phase 3 in phase 2 and phase 4 on phase 3 but this solution is neither exible nor aesthetic. Instead we transform phase 1 and 3 into a stream. Phase 2 and 4 do not need be transformed as they do not fork the computation. Phase 1 and 3 become (To simplify the code, lifting operations have been hidden) as described in algorithms 13 and 14. streamStep is the only call of the seventh layer of the tower. The signature data type of the layer is 1 data StreamCall arg r e t n r = StreamStep arg ( r e t > r) streamStep give a value to the interpretation function and returns what the inter- pretation send back. This can be seen as stream for which it is possible to act on the ow. It is implemented in the Haskell module A.10. Combining the phases is done by instantiating the stream layer for each function. core() is then as described in algorithm 15. core() is also a stream because of streamStep((X; s0 )) in interpretPhase1(). The interpretation of this stream will give either the -equivalence or the matching algorithm. 146 10.3 Combining the phases: Streams Input: Two nominal terms t 1 ; 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 and u switch (t,u) do case ([a]t; [b]u) a0 = envImageInv(a ); b0 = envImage(b ); phase1(t,u ) = envLocal(; envSetFresh(a0 ; >) ; envSetFresh(b; ?) ; envComposeLeft((a b0 )) ; phase1(t , u ); ); case ((t1 ; : : : ; tn ); (u1 ; : : : ; un )) phase1(t1 ; u1 ) ; : : : phase1(tn ; un ) case ((f t') , (f u')) where f is a function symbol phase1(t0 ; u0 ) case (a; b) where a and b are two atoms if envIsFresh(b ) then dthrowError(freshness constraints not satisfied) end if a = envImage(b ) then return () else dthrowError(atoms not equal) end case (X; u) streamStep(( X; u)) otherwise dthrowError(t and endsw endsw u do not match) Algorithm 13: Streamed version of Phase 1 147 10.3 Combining the phases: Streams Input: A nominal term t 1 ; 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 phase3(t ) = switch t do case a where a is an atom if envIsFresh(a) then dthrowError(freshness constraints not satisfied) else envImage(a) end case fu ! phase3(u ); case (u1 ; : : : ; un ) s1 = phase3(u1 ); : : :; sn = phase3(un ); return (s1 ; : : : ; sn ) case [a]u s = envLocal(; envSetFresh(a; ?); phase3(u ); ); = envImage(a ); 17 18 19 20 21 22 23 24 25 26 b return [b]s case X envlocal(; envComposeRight( ); streamStep(X ); ); 27 28 endsw Algorithm 14: Streamed version of Phase 3 148 10.4 -equivalence and Matching Input: Two nominal terms t and u 1 ; 2 3 4 core(t,u ) = instantiate(interpretPhase1() , phase1(t,u )) ; where; interpretPhase3(StreamStep X k ) = k(phase4(X )); 5 ; 6 phase3and4(t ) = instantiate(interpretPhase3() , phase3(t )); 7 ; 8 9 10 11 12 interpretPhase1(StreamStep ( X; u0 ) k ) = ; (X; s) = phase2(X; u0); s0 = phase3and4(s) ; streamStep((X; s0 )); k(()); Algorithm 15: The core algorithm 10.4 -equivalence and Matching Interpretation as -equivalence Solving the -equivalence problem s t is interpreting the stream of (X; s) that core(s,t ) returns with the rule () 9.3. The rule () is implemented as the interpretAlpha() interpretation function in algorithm 16. Interpretation as Matching Solving the matching problem s t is interpreting the stream of (X; s) that core(s,t ) returns with the rule (? ) 9.4. The rule (?) is implemented as the InterpretMatch() interpretation function in algorithm 17. This function uses a global array representing the current substitution. Such an array is dened by: Denition 80 arr is an array representing the substitution 8 <Nothing arr [X ] = :Just t (X ) = X if (X ) = t and t 6= X if 149 if and only if 10.4 -equivalence and Matching Input: two nominal terms t and u 1 ; 2 3 4 5 alpha(t,u ) = ; instantiate(interpretAlpha(), core(t,u )) freshContextToList() where; interpretAlpha((X; s)) = Switch(X; s) case (X; X ) freshConstaint(supp( ) # X ) otherwise 6 dthrowError(Substitutions 7 not allowed ) endsw Algorithm 16: -equivalence Interpretation of core 8 Input: two nominal terms t and u 1 ; 2 3 4 5 7 8 9 10 11 12 match(t,u ) = ; ll arr with Nothing; instantiate(InterpretMatch, core(t,u )); return arr ; 6 where InterpretMatch((X; s); k) = ; if arr [X ] = Nothing then arr [X ] = Just s else (X; t0) = arr [X ] ; if j s j<j t0 j then arr [X ] = Just s 13 end 14 ; 15 alpha(s,t0 ) 16 end 17 k(()) Algorithm 17: Matching Interpretation of core 150 10.5 Complexity Choosing the implementation of Permutations and Sets We will use either interpret permutations and sets of atoms as either as mutable arrays or as persistent data depending on the kind of problem to be solved: Case Ground Linear Non-linear Alpha-equivalence mutable arrays persistent data persistent data Matching mutable arrays persistent data mutable arrays Note that when the problem is ground, the streams of all the phases are empty, so no interpretation of StreamCall is required. Since in this case we only need to access and update the environment, arrays are more ecient. With linear, nonground problems, we need to interpret StreamCall, and the cost is quadratic using arrays, but log-linear using functional maps. We will discuss the non-linear case in Section 10.5. 10.5 Complexity In this section we analyse the complexity of the core algorithm and the nal -equivalence and matching phases. Let P r0 = s t be the input problem; s and t are coded as trees and is coded as a pair of a permutation and a set of atoms (permutations being a tuple of the actual permutation, its inverse, and its support), as discussed in Section 10. Atoms are coded as integers, as explained above. Let MA0 be the maximum atom in A0 (the set of atoms occurring in P r0 ). Let jtjn be the number of nodes in the tree representing t. Let P be the multiset of permutations in s, t, and jj be the size of the array representing 2 P (or the size of the map if (t) be the multiset of we are dealing with non-ground problems). Finally, let MV the occurrences of variables in t. Core algorithm Below we analyse the cost of the traversal of the data struc- ture, and the cost of the operations involved in the rules. Proposition 63 The core algorithm is linear in the size of the problem the ground case, using mutable arrays. using functional maps and P r0 in In the non-ground case it is log-linear #(js tj + jMA0 j jtjn ) using mutable arrays. 151 10.5 Complexity The idea of the proof is that the core algorithm is essentially a traversal of the data structure representing the problem. Phases 1 to 3 are trivially linear with arrays and log-linear with functional maps. Phase 4, with functional maps is done in Proof #(jMA0 j) time and #(jMA0 j jtjn ) with arrays. There are three kinds of rules in the core algorithm: propagation rules, such as the rules in phase 1 and phase 3, except the rule rules dealing with input permutations (i.e., permutations that occur in the (X ) =) ( )X ; initial problem )X ; P r0 ), such as phase 2 rules and the rule (X ) =) ( rules dealing with X to compute standard form. The propagation rules visit each node in the tree representation of t at most once. They may apply at most one swapping (resp. adding/removing an atom) to modify the environment and the same again (resp. removing/adding this atom) to restore it. All these operations are done a constant number of times and in constant (resp. logarithmic) time with arrays (resp. maps). Hence, they take #(jtjn ) time with arrays and #(log(jtjn ) jtjn ) with maps. (X ) =) Rules dealing with permutations, i.e., phase 2 rules and the rule ( )X , compose an input permutation and the environment. This takes #(jj) (resp. #(log(jj) jj)) time per input permutation with arrays (resp. maps), as described in Section 4.4. They are applied at most once per input P jj in total with arrays and P log(jj) jj with permutation, so take maps. 2P 2P If the right-hand side terms are ground, only these two kind of rules can be applied, so the algorithm is linear in the size of the problem. Otherwise, there's also the rule dealing with X in phase 4 to compute standard form (i.e., the output of the algorithm). It is applied at most once per occurrence of a variable in t, and is done in constant time with functional maps and space with arrays. #(jMA0 j) time and If the problem consists of more than one matching or -equivalence constraint, they can be solved independently. Thus, we run the core algorithm on each of 152 10.5 Complexity them and concatenate the results, obtaining again an algorithm of the same complexity. Remark 9 Our algorithm checks -constraints and the freshness constraints that are generated, all in one traversal of the data structure representing the problem (relying on the environment to provide information about the freshness constraints generated). We have chosen to develop the algorithm in this way because it is simpler, but it is possible to solve the -constraints and the generated freshness constraints separately, while keeping the same complexity. Instead of using an environment with a permutation and a set of atoms, we can split the environment into two parts, one storing just a permutation and the other just a set of atoms. In phase 1, it is sucient to reduce all propagating any freshness constraints. -constraints without That is, when we reach an abstraction [a]s [b]t, instead of adding a to the set of atoms in the environment and propagating it, we just put a tag a # on the current node of t. At the end of the -reduction phase, an environment (consisting just of a set of atoms) will be t and some nodes will be tagged with freshness constraints. It remains to propagate the environment with the set of atoms over t, adding a to the set each time we reach a node tagged by a. attached to the root of To obtain an ecient algorithm it is important to avoid doing several freshness checks of the form (a # b)a2A, which would require time proportional to Instead, we should do just one check jAj. b 2? A, which can be done in constant time using an array. -constraint, after running the core algorithm we have to normalise the problem using the rule (), as deAlpha-equivalence To check the validity of an scribed in Section 9.3. If the right-hand sides of -constraints are ground, the core algorithm is sucient and it is linear (ground case). Otherwise, each application of the rule () requires to know the support of a permutation, which we do because supports are always created with permutations and maintained when they are updated. Thanks to the use of functional maps, the support is copied in constant time when the permutation is copied, therefore the algorithm is also log-linear in the size of the problem in the non-ground case. 153 10.5 Complexity Matching The algorithm to solve matching constraints consists of the core algorithm, followed by a normalisation phase in which the result of the core algorithm is reduced using a rule that deals with variables occurring multiple times in the pattern (called ? in Section 9.4). In the case of linear matching this rule is not needed the core algorithm is sucient. In Section 10 we discussed the implementation of the rule ? using an array S indexed by variables and a rule which we called Rl-Check-Subst. The construction of S requires the traversal of the term s and every term in the output of the core algorithm. This is done in time proportional to the size of the output of the core algorithm. At worst, the size is jMA0 j MV (t)+ js tj because phase 4 can add a suspended permutation and freshness constraints on every variable occurring in t. Therefore the output can be quadratic in the size of the input. Then Rl-Check-Subst will compute S [Xi] ti for each constraint Xi ti in the result of the core algorithm. Phase 1 to 3 are linear in its size and phase 4 has a complexity #(jMA0 j MV(ti )), hence at worst quadratic in time in the size of the input problem. The worst case complexity arises when phase 4 suspends permutations on all variables, making the output terms bigger than the input ones. Since permutations are bounded in size, in the worst case (an input problem with no permutations but many variables and abstractions), the output of phase 4 may be quadratic in the size of the input problem. On the other hand, if the input problem already has in each variable a permutation of size jMA0 j (i.e. variables are 'saturated' with permutations), then, since permutations cannot grow, the -equivalence and matching algorithms are linear even using arrays. Note that the representation of a matching problem or an -equivalence prob- lem using higher-order abstract syntax does saturate the variables (they have to be applied to the set of atoms they can capture). Thus, our algorithms (whether for ground or non-ground, linear or non-linear problems) are all linear in time with respect to the size of the problem represented in higher-order abstract syntax. The table below summarises the results: Case Ground Non-ground and linear Non-ground and non-linear Alpha-equivalence linear log-linear log-linear 154 Matching linear log-linear quadratic 10.6 Benchmarks 333 3 3 3 0.45 +33333 0.4 3333333333333 3 3 3 33333 33333 0.35 3 3333333333333 3 3 0.3 3 3333333333333 3 3 3 Time 0.25 33333333333333 + + ++ + 3 3 3 0.2 ++++++++ ++ ++ 3333333333 3 + + 3 3 + 3 0.15 333333 +++++++++++++++++++ 3 3 0.1 33333333++++++++++++++++++ + 3 3 3 3 0.05 3++3+3+3+3+++++++++ + 3 3 3 + 3++3+3+3+3+++++ 0+ 0.5 alpha match 0 100002000030000400005000060000700008000090000100000 Size Figure 10.1: Benchmarks 10.6 Benchmarks The algorithms described above to check -equivalence and to solve ground matching problems have also been implemented in OCAML [6], using arrays. In Figure 10.1, we show benchmarks generated by building random solvable ground problems (i.e., problems that do not lead to taken by the as ?) and measuring the time -equivalence and matching algorithms to give the result (marked 3 and + in the graph) . 1 The benchmarks suggest that for problems of similar size, the -equivalence algorithm takes more time than the matching algorithm. This may be explained by the fact that to check -equivalence we need to traverse the whole problem, whereas a full traversal is not always needed to solve a matching problem. Note that, unlike rst-order matching, nominal matching might produce freshness constraints that have to be checked by traversing the term. 1 The program is available from: www.dcs.kcl.ac.uk/staff/maribel/CANS 155 Chapter 11 A Simple Nominal Rewriting Framework In this chapter, we recall the denition of nominal rewriting systems [20] and we show that by only changing the interpretation of the freshness context layer, the nominal matching algorithm can become a nominal rewriting algorithm. 11.1 Nominal rewriting nominal rewriting rule is a tuple (rl ; l; r), written rl ` l ! r, where rl is a freshness context and l,r are nominal terms such that V (r; rl ) V (l). A Intuitively, in a rewriting rule, variables represent unknown terms and the rewrite relation is generated by instantiating the variables in the rules using substitutions. For example, the two rules a#X ` ([a]X )Y !X and a #Y ` ([a]Y )X ! Y have dierent syntax but should generate the same rewrite relation. Atoms are R(a b) for that rule obtained by swapping a and b in the rule R throughout. For example, if R a#X ` [a]X ! X then R(a b) b#X ` [b]X ! X . In general, we write R for that rule obtained by applying the permutation to the atoms in R. A set of rewrite rules is equivariant when it is closed under ( )(a b) for all atoms a and b. not substituted, but they can be swapped: We write 156 11.1 Nominal rewriting Denition 81 A nominal rewriting system R is an equivariant set of nom- inal rewriting rules. Nominal rewriting rules generate a rewrite relation on nominal terms in context. We denote a term t with a freshness context t by t ` t. In order to apply a rewrite rule at the root position in t, we use nominal matching. Not only l has to match t, but also the constraints in rl have to be satised by t . For example, consider the rule a # X ` f X ! X and let f a be the term to rewrite. In this case, even if f X and f a match (with the substitution = [X 7! a]) the rule cannot be applied because a is not fresh in X . Formally, given a nominal rewrite rule rl ` l ! r and a term t with freshness context t , we say that t ` t matches the left-hand side l of the rule under the constraints rl if and only if the matching problem l t has a solution (; ) and [ Env ( (rl )) t, where (rl ) = fa#X j a#X 2 rl g. The result of applying this rewrite rule to t ` t is then the term t ` r . Note that since nominal rewriting systems are equivariant, in order to decide whether there exists a rule in the system that matches a term at a given position we need equivariant nominal matching (i.e., nominal matching with respect to variants of the rules obtained by changing the names of the atoms). However, nominal matching is sucient if the rules are closed. Closed nominal rules are equivalent to the rules used in standard notions of higher-order rewriting (for instance, any Combinatory Reduction System [31] can be dened as a closed nominal rewriting system, see [21]). Equivariant nominal matching, which is an NP-complete problem in general [12], can be avoided for closed rules, simply by using a copy of the rewrite rule where all the variables and atoms are chosen to be dierent from the ones in the term to be rewritten (this is always possible because nominal rewriting systems are equivariant). We refer the reader to [20] for the formal denition and properties of closed nominal rewriting. For example, the -rule in the rule scheme We can specify -calculus x:Mx ! M if is usually written informally as a x 62 F V (M ) using a nominal rewriting system dened by the (closed) rule a#X ` ([a]Ap(X; a)) ! X 157 11.2 A nominal rewriting algorithm: two improvements on the matching algorithm and then use this system to rewrite the term context as follows: ([b]Ap(a; b)) in an empty freshness 1. We choose a fresh copy of the rewrite rule that does not use any variables or atoms occurring in the term to be rewritten. For instance, we take c#X ` ([c]Ap(X; c)) ! X 2. We solve the matching problem: ([c]Ap(X; c)) ([b]Ap(a; b)) which has solution ([X 7! a]; ;). the term a, as expected. Since c #a is valid, our term rewrites to 11.2 A nominal rewriting algorithm: two improvements on the matching algorithm t ` t matches rl ` l ! r eciently, we will use a modied version of our matching algorithm. Since checking [ Env ( (rl )) t To check whether can be expensive because of the set unions and -equivalence problems it involves, we will do these operations on-the-y, directly in the matching algorithm. For this, the rst modication consists of computing traverse the term, so that it will directly be in X Below we use the notation in a freshness context . Env((rl )) as we X for the set of atoms in constraints relating to , more precisely: X = fa j a#X 2 g Proposition 64 If for each equation X t at the end of phase 2, we 1 1 the set (rlX ) = f a j a # X 2 rl g to the current freshness set A , is equivalent to computing Env((rl )) [ . In fact, it is sucient to do this once for each distinct variable 158 X. add this 11.2 A nominal rewriting algorithm: two improvements on the matching algorithm Proof By Proposition 52, at the end of phase 2, the problem, if it has a solu- (Xi iti)n1 . Let Xj j tj be one of these constraints. Notice that for each i such that Xi = Xj , i ti and j tj must be -equivalent for the problem to have a solution . In addition to satisfying Xj (j tj ), must also satisfy rlXj # Xj . Since rlXj # Xj is equivalent to rlXj # j tj , which is in turn equivalent to j 1 (rlXj ) # tj , any solution of the original problem is also a solution of: tion, is of the form Xj (j ; j A [ j 1 (rlXj ))tj Furthermore, since j tj i ti when Xi = Xj , we also have rlXj # j tj , rlXj # i ti which means we only need to do it once for each distinct variable. The second improvement consists of computing t directly without . For this, in phase 4, when reaching X , instead of adding A # X to we directly check if A tX . computing Proposition 65 In phase 4, when reaching equivalent to adding to and then checking t. is # by generating constraints A X for each in the problem. Therefore, checking t is equivalent to checking Proof X A # X X , checking whether A tX Phase 4 computes # whether every A X t , which is equivalent to A tX . These two modications can actually be made without changing the phases. All we need to do it to interpret FreshnessContextCall dierently. The interpretation of FreshnessContextCall to match terms in context is described in the algorithm 18. Because we no longer need to copy freshness sets, we can always use mutable arrays to represent A . Unfortunately we still need to copy permutations , so in the following we will use mutable arrays for sets, and persistent maps for permutations. We will represent rl using an array indexed by variables, such that the element X in the array will contain the list of atoms that should be fresh for X according to rl . 159 11.2 A nominal rewriting algorithm: two improvements on the matching algorithm 1 2 3 4 5 interpretFreshContextCheck(c ) = ; switch c do case FreshConstraint A # X if A 6 tX then dthrowError(Constraints 6 end 7 ; 8 k(()) 9 10 11 12 13 14 k case FreshContextRule X S = rlX ; remove k(S ) not satised ) k rlX from rl ; case FreshToList k k([]) 15 endsw Algorithm 18: Interpretation of Freshness for matching terms in context 16 160 11.3 Complexity of nominal rewriting 11.3 Complexity of nominal rewriting We start by dening the size of a matching problem for terms in context. Denition 82 The size of a matching problem Pr = rl ` l t ` t is dened as jPrj = jrl j + jlj + jtj + jtj where the sizes of rl and t are the size of their representations. Proposition 66 The total cost of the algorithm to solve the matching problem for terms in context: is at most rl ` l t ` t #((jrl j + jlj + jtj) log jAj + m jMV ar(t)j) A is the set of atoms occurring in the problem, m MV ar(t) is the multiset of variable occurrences in t. where = maxX (jtX j) and F reshContextRule adds 1 (rlX ) to the current freshness set, once for each variable X . Freshness sets Proof With this interpretation of FreshContextCall, being coded as mutable arrays and permutations as functional maps, it takes #(log jAj) time to access each atom a in rlX (coded as a list of atoms), compute its image by 1 and add it to A . The cost of Phase 2 is therefore #(jrl jlog jAj). Phase 4, when reaching X , rewrites it into X and checks whether A tX . The former is done in constant time thanks to the use of functional maps. The latter, if done in a naive way, would take time proportional to jAj since A is represented as an array of size jAj. However, checking A tX is equivalent to checking jtX \ A j = jfa j a 2 A gj. We call this number sA . We can, as we did for permutations, compute this number when A is created and then update it in constant time on every update of A . In this way, we only need to compute tX that are also in A and compare it with sA , which can be done in time proportional to tX (because tX is a list of atoms and A the number of atoms in X , we can bound the total cost is m MV ar(t). an array). Thus, for any 161 the cost by m, so in the worst case 11.4 The Zipper Layer Summarising: 1 2 2 3 4 Phase Phase Phase 0 Phase Phase 0 : #(jlj log jAj) : #(jlj log jAj) : #(jrl j log jAj) : #(jtj log jAj) : #(m jMV ar(t)j) 11.3.0.1 Special cases First-order matching is a particular case of nominal matching, and it is interesting to see that the rewriting algorithm specied above behaves as expected when the terms involved are ground or rst-order. Proposition 67 If t is ground or if t is empty, the complexity of the algorithm is at most #((jrl j + jl j + jtj) log jAj). Proof If t is ground, jMV ar(t)j = 0 and if t is empty, m = 0. Proposition 68 For rst-order terms, where t is ground, the algorithm is linear in time. Proof (jAj = 0). In this case there are neither variables (jMV ar (t)j = 0) nor atoms 11.4 The Zipper Layer A zipper is a data structure that has been introduced by Huet [26] to represent a pointer within a term in a purely functional way. There are many implementations of zippers. The Haskell module Zipper in appendix A.11 presents a very simple implementation of a generic zipper on algebraic datatypes. This section presents the zipper on compact nominal terms using the implementation in appendix A.11. Let t be the compact nominal term navigate withing [a]((a; b); [b]X ). We want a simple way to t in a purely functional way. 162 11.4 The Zipper Layer Denition 83 Let H be a new variable symbol ( H 62 X ). Until now we have X . We will keep calling compact nominal terms such terms. Compact nominal terms c over (X [ fH g) with exactly one occurrence of X are called compact nominal context or only considered compact nominal terms whose variables where in context for short. H For example, is called a hole. [a](X; H ) is a context but [a](X; X ) and [a](H; H ) are not. The following equalities are valid: t = H [X 7! t] [a]H [H 7! ((a; b); [b]X )] ([a](H; [b]X ))[H 7! (a; b)] [a]((a; b); [b]H )[H 7! X ] u can be represented as a pair (c; u0) where c is a context and u0 a subterm of u and u = c[H 7! u0]. Furthermore let c1 and c2 two contexts, c1 [H 7! c2 ] is also a context. As we can see any can see any compact term Denition 84 A zipper z on a nominal term is a pair (t; [c1 ; : : : ; cn ]) where t is a nominal term and [c1 ; : : : ; cn ] a list of contexts. z represents the term cn [H 7! cn 1 [H 7! : : : c1 [H 7! t]]]. t is called the subterm of the zipper z = ([a]u; [c1 ; : : : ; cn ]) be a zipper. We can move a step downward by the new zipper z 0 = (u; [[a]H; c1 ; : : : ; cn ]). Let z = ((u1 ; u2 ); [c1 ; : : : ; cn ]), there are two downward move possible: moving to u1 by z 0 = (u1 ; [(H; u2 ); c1 ; : : : ; cn ]) or to u2 by z 0 = (u2 ; [(u1 ; H ); c1 ; : : : ; cn ]). Going a step upward from z = (u; [c1 ; : : : ; cn ]) is as simple as z 0 = (c1 [H 7! u]; [c2 ; : : : ; cn ]) if n > 0. It also possible to update the subterm of a zipper. Let z = (u; [c1 ; : : : ; cn ]). We can replace u by t by z 0 = (t; [c1 ; : : : ; cn ]). Let The Haskel module Zipper in appendix A.11 implements a state monad whose state is a zipper. Along with the rewrite function it makes a very simple but complete rewriting framework. Any stategy can be written in Haskell, using the zipper to navigate within the term and the rewrite function to rewrite at the desired position. 163 Part IV Conclusions 164 Related Works On Nominal Algorithms Gabbay, Pitts and Urban [51] were the rst to prove that nominal unication is decidable. They presented a simple algorithm based on problem rewriting. Unfortunately, because subterms were not shared, the algorithm is exponential in time and space. Cheney proved that equivariant unication [11] (deciding whether two terms involving swapping can be made equal up to a permutation) is NPcomplete. Higher-Order Patterns Higher-Order Patterns syntax is another syntax, introduced by Miller [35] to represent systems with binders. Higher-Order Pat- -terms in -long forms, in which free variables F only occur in the form F (xk ) with xk is a sequence of k (k > 0) distinct bound variables. For example, x:y:z:F (y; x) and x:y:y (z:F (z; y ); F (x; y ); G(y )) are higherorder patterns, provided that they are -long forms. But x:F (c; x), x:y:F (x; x) and z:F (G(x)) are not. terns are Higher-order patterns and nominal unication are strongly related. Cheney [13] showed that higher-order pattern unication can be reduced to nominal unication. Latter Levy and Villaret [33] proved that nominal unication can be reduced to higher-order pattern unication. Their reduction proves that nominal unication can be decided in quadratic deterministic time. Their proof is based on Qian's linear higher-order pattern algorithm [45]. This algorithm is intricate and, to our knowledge, has never been implemented (certainly because of its intricacy), whereas QNU (presented in chapter 6) is simple, based on a well known rst-order algorithm and has been implemented. 165 Qian's algorithm is linear in time and space on the size of the input problem whereas the nominal unication algorithm is quadratic in time and space in the size of the input problem. This does not mean that Qian's algorithm is faster because the syntax is dierent. Nominal syntax can be much more concise than higher-order patterns. The reason of that lies with the variables. In nominal syntax a variable is just X and there is no restriction on the possible free variable of X. This is not the case with higher-order patterns where a variable X is always followed by the list of its possible free atoms (called bound variables in A0 is the set of atoms in the pattern syntax can be j A0 j times higher-order pattern terminology). It means that if problem, the same problem in higher-order bigger than expressed in nominal syntax. Furthermore, as seen in chapter 6, when all variables in a nominal problem are suspended, then unication become also linear. First-class monadic signature The idea of using continuations to stop the computation to return value and a computation is not new. It has been used in many situations: Kiselyov, Shan Claessen and Sabry used them to implement a concurrency monad [14], showed they can be used in operating systems to im- plement system calls [30], Filinski used them to represent monads and monadic towers [22, 23], they have been used to implements streams, . . . To our knowledge it has never been used to implement monadic classes. lyov, Shan Kise- and Sabry 's approach is the closest to ours as system calls and monadic operations are related (the operation system can be seen as a monad). However, unlike their, our concern is not only to implement system calls but to have a correct and complete way to implement monadic classes. Furthermore their calls are only monomorphic and do not have arguments of the same monad (the example of section 3.3) whereas we presented how to implement monomorphic and polymorphic calls even in that case. Their approach works ne for one layer but problems arise with more: our approach explicits the number of layers and the type of each layer. With more typing information, programs are easier to read, to develop and overall safer. The prompt not dened error that can occur at run time with their approach is impossible with ours. Every level error is detected by the type checker at compile time. Furthermore our approach is based on the 166 CPS Hierarchy, which is simple and well known [9, 18] whereas they rely on a complex monadic framework [19]. 167 Future Works The main problem when implementing an algorithm in a syntax involving binders is the amount of technical details needed everywhere like freshness check or applying the right permutation at the right place at the right time. Our modular approach has two main benets: most of these details are handled automatically by the corresponding layer instead of interacting in a chaotic way, nominal aspects (naming, handling of the context, actual implementation, combing the phases, . . . ) are separated so that it is easier to check each of them is OK. The idea of environment shows an important aspect of syntax involving as- pect. On the contrary to rst-order syntax where a term comes alone, nominal terms are the combination of a term and a position map. This position map called environment is the link between names and positions. For example: let us consider two nominal terms abstraction in [a]a and [b]b. a (resp. b) is just the name of the [a]a (resp. [b]b). The two abstractions have the very same position, only their name is dierent. All nominal terms come with such a map and the complexity of nominal algorithms comes from translating position names from terms to terms. The reason why the nominal -equivalence and matching algorithms can be more ecient than unication whereas in rst-order syntax they have all the same complexity is that for -equivalence and matching algorithms, the closer two nodes are in the directed acyclic graph the closer are their environments. That is not true for unication. The key to a faster than set of atoms in the problem P r0 ) j A0 j P r0 (where A0 is the complexity algorithm for nominal unication 168 is either to be able to compute the suspended permutation and set of freshness constraints at any node in less time than j A0 j or to prove that knowing it for a smaller subset of nodes is sucient. Unfortunately the dependency relation between permutations and freshness set in a unication problem is chaotic and depend only on the problem. A possibility could be to have a less chaotic dependency. Let us consider the [a](X; [a]X ). This term means explicitly that a free occurrence of a in X in bound by two dierent abstractions. Having [a](a; [a]a) is not annoying because the two a are not the same occurrence and [a](a; [a]a) is equivalent to [a](a; [b]b) but in the [a](X; [a]X ), the two occurrences of X represent the very same term. term Do we need in practice to have many dierent environment in a term? Maybe not. t = [a](X; ((f; X ); [b]y)). If c is an free atom in X all the occurrences of c in t[X 7! c] will be either all bound and bound by the same abstraction or all free. Let u = (X; [a]X ) and X = a, a in at the same time free and bound in u[X 7! a]. More generally, when in a term s there is a variable X such that a free atom a in X can be bound by dierent abstractions or at the same time free and bound, then u is said to be schizophrenic. Let anti-schizophrenic restriction is: let t be a nominal term For any atom a, if two occurrences of a (from X ) in t[X 7! a] are under two dierent abstractions, or one free in t[X 7! a] and not the other, then a has to be fresh in X . Denition 85 and X 2 V(t). The This restriction ensures that all occurrences of X in t have the same environ- X s 0 X t s t. If managing the ment. Because of this, when reducing a nominal problem we know that ds(; 0 ) # X so we can directly write set of freshness constraints for every variable can be done eciently, then we conjecture a better than quadratic unication algorithm can be found under this restriction 169 Conclusion We showed in this thesis that even if nominal algorithms may seem simple at rst look, developing and implementing ecient nominal algorithms requires powerful theoretical and practical tools. Their complexity depends a lot on the implementation of permutations and sets and on the strategy used. Furthermore there are not (yet) a unique implementation and strategy that are the best for all the situations. The algorithms need to adapt themselves to the input problem to perform specic optimisations. This complexity is not specic to nominal algorithm. Even if a linear algorithm to solve Higher-Order Pattern Unication exist since 1993 [44], to our knowledge it has never been implemented in practice. Practical higher-order pattern unication implementations, like Teyjus [7, 37], seem to prefer using a simpler, but less ecient, algorithm. The unication algorithms presented in part II are simple and ecient. The -equivalence and matching algorithms presented in part III are the most ecient known algorithm for these problems. On linear matching problems without permutations, the nominal algorithm complexity is log-linear, but the size of these problems in higher-order pattern syntax can be j A0 j (where A0 is the set set of atoms in the problem) times bigger. So even if higher-order pattern matching is linear, on these problems, it is less ecient than nominal matching. The techniques developed in the thesis are interesting results by themselves. The nominal union-nd algorithm and the theory behind can be useful in any situation where a union-nd algorithm able to handle names is required. The permutations, sets of atoms, freshness contexts and environment monadic layers can be used as-is in any program manipulating nominal terms thus enabling it to 170 use several actual implementations of permutations and sets of atoms, dierent interpretations of freshness context calls and one single environment. First-class monadic signatures can be partially implemented in other languages than Haskell thus bringing monadic classes to theses languages. For languages with native rst-class continuations (Scheme, (bytecode), Ruby, Objective CAML . . . ) it can even be done transparently using Filinski's implementation of shift /reset in terms of callcc [22]. 171 Bibliography [1] Brics website:. http://www.brics.dk/. 11 [2] Ghc documentation:. http://www.haskell.org/ghc/docs/latest/html/. 15 [3] Haskell 98 revised report:. http://www.haskell.org/onlinereport/. 15 [4] Haskell website:. http://haskell.org/. 15 [5] Hnt website:. http://www.brics.dk/. 13 [6] Objective caml website:. http://www.dcs.kcl.ac.uk/pg/calves/hnt/. 69, 155 [7] Teyjus website:. http://teyjus.cs.umn.edu/. 170 [8] S. Adams. Functional Pearls Ecient sets—a balancing act. Functional Programming, [9] 3(04):553561, 2008. 59 M. Biernacka, D. Biernacki, and O. Danvy. An operational founda- tion for delimited continuations in the CPS hierarchy. [10] C. Calvès and M. Fernändez. BRICS. 32, 167 Nominal matching and alpha-equivalence. Logic, Language, Information and Computation. LNCS, [11] J. Cheney. J. Cheney. 5110:111122. The complexity of equivariant unication. computer science, [12] Journal of Lecture notes in pages 332344, 2004. 165 Equivariant unication. 3467:7489, 2005. 128 157 172 Lecture Notes in Computer Science, BIBLIOGRAPHY [13] J. Cheney. Relating nominal and higher-order pattern unication. In Pro- ceedings of the 19th International Workshop on Unication (UNIF 2005), pages 104119. Citeseer, 2005. 165 [14] K. Claessen. 9(03):313323, 1999. Programming, [15] A poor man's concurrency monad. O. Danvy and A. Filinski. Journal of Functional 166 Abstracting control. In Proceedings of the 1990 ACM conference on LISP and functional programming, pages 151160. ACM New York, NY, USA, 1990. 20 [16] O. Danvy and K. Malmkjær. formation. On the Idempotence of the CPS Trans- BRICS, Computer Science Department, University of Aarhus. 20 [17] O. Danvy and K. Millikin. Computer Programming, [18] [19] 74(8):534549, 2009. O. Danvy and Z. Yang. chy. Refunctionalization at work. 21 An operational investigation of the CPS hierar- Lecture notes in computer science, pages 224242, 1999. 32, 167 R.K. Dyvbig, S.P. Jones, and A. Sabry. delimited continuations. Science of A monadic framework for Journal of Functional Programming, 17(06):687 730, 2007. 167 [20] M. Fernandez and M.J. Gabbay. Computation, [21] 205(6):917965, 2007. Information and 99, 134, 156, 157 M. Fernández, M.J. Gabbay, and I. Mackie. tems. In Nominal rewriting sys- ACM Symposium on Principles and Practice of Declarative Pro- gramming (PPDP’04), ACM Press, [22] Nominal rewriting. A. Filinski. Representing monads. 2004. 157 In Proceedings of the 21st ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 446457. ACM New York, NY, USA, 1994. 166, 171 173 BIBLIOGRAPHY [23] A. Filinski. Representing layered monads. In Proceedings of the 26th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 175188. ACM New York, NY, USA, 1999. 33, 166 [24] M.J. Gabbay and A.M. Pitts. variable binding. [25] P. Homeier. Formal aspects of computing, G. Huet. 13(3):341363, 2002. 10 A proof of the Church-Rosser theorem for the lambda calculus in higher order logic. In [26] A new approach to abstract syntax with The zipper. TPHOLs, pages 207222, 2001. 139 Journal of Functional Programming, 7(05):549554, 1997. 162 [27] M. Jaskelioff. Monatron: An Extensible Monad Transformer Library. 33 [28] M. Jaskelioff. Modular Monad Transformers. In Proceedings of the 18th European Symposium on Programming Languages and Systems: Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2009, [29] pages 6479. Springer, 2009. 25, 33 M.P. Jones. Functional programming with overloading and higher-order polymorphism. [30] Lecture Notes in Computer Science, O. Kiselyov, C. Shan, and A. Sabry. Proceedings of the 2006 ICFP conference, 925:97136, 1995. 30 Delimited dynamic binding. In 41, pages 2637. ACM New York, NY, USA, 2006. 166 [31] J.W. Klop, V. Oostrom, and F. Raamsdonk. systems: introduction and survey. [32] P.J. Landin. Research, [33] TCS, Getting rid of labels. Combinatory reduction 121(1&2):279308, 1993. 157 Report, UNIVAC Systems Programming 1965. 19 Jordi Levy and Mateu Villaret. order perspective. 2008. 165 174 Nominal unication from a higher- BIBLIOGRAPHY [34] A. Martelli and U. Montanari. An ecient unication algorithm. ACM Transactions on Programming Languages and Systems (TOPLAS), 4(2):258282, 1982. [35] D. Miller. 80, 86 A logic programming language with lambda-abstraction, func- tion variables, and simple unication. 1(4):497536, 1991. [36] E. Moggi. Journal of Logic and Computation, 165 Notions of computation and monads. INF. COMPUT., 93(1):55 92, 1991. 19 [37] G. Nadathur and D.J. Mitchell. System description: Teyjus-a compiler and abstract machine based implementation of lambdaprolog. in computer science, [38] MHA Newman. lence". [39] On theories with a combinatorial denition of" equivapages 223243, 1942. 130, 134, 138 J. Nievergelt and EM Reingold. Binary search trees of bounded bal- Proceedings of the fourth annual ACM symposium on Theory of computing, [40] pages 287291, 1999. 170 Annals of Mathematics, ance. In Lecture notes pages 137142. ACM New York, NY, USA, 1972. 59 MS Paterson and MN Wegman. Linear unication. In the eighth annual ACM symposium on Theory of computing, Proceedings of pages 181186. ACM New York, NY, USA, 1976. 69, 70, 72 [41] S.L. Peyton Jones. The Implementation of Functional Programming Lan- guages (Prentice-Hall International Series in Computer Science). Prentice- Hall, Inc. Upper Saddle River, NJ, USA, 1987. 10 [42] A.M. Pitts. Nominal logic, a rst order theory of names and binding. Information and computation, [43] D. Plump. 186(2):165193, 2003. Term graph rewriting. 10 Handbook of Graph Grammars and Com- puting by Graph Transformation, Applications, Languages and Tools, 67 175 2:361. BIBLIOGRAPHY [44] Z. Qian. Linear unication of higher-order patterns. IN COMPUTER SCIENCE, [45] Z. Qian. pages 391391, 1993. 170 Unication of higher-order patterns in linear time and space. Journal of Logic and Computation, [46] LECTURE NOTES John C. Reynolds. languages. In 6(3):315341, 1996. 165 Denitional interpreters for higher-order programming Proceedings of the ACM annual conference (ACM'72), pages 717740, 1972. 20, 21 [47] N. Shankar. Metamathematics, machines and Gödel's proof. Cambridge Univ Pr, 1997. 139 [48] M.R. Shinwell, A.M. Pitts, and M.J. Gabbay. FreshML: Program- ming with binders made simple. In Proceedings of the eighth ACM SIGPLAN international conference on Functional programming, pages 263274. ACM New York, NY, USA, 2003. 10 [49] J. Stern. national, [50] Fondements mathématiques de l'informatique. Ediscience Inter- 1990. 118 R.E. Tarjan. On the eciency of a good but not linear set union algorithm. 1972. 80 [51] C. Urban, A.M. Pitts, and M.J. Gabbay. retical Computer Science, Nominal unication. Theo- 323(1):473498, 2004. 50, 51, 53, 54, 99, 131, 134, 165 176 Appendix A Haskell Code A.1 Base denitions 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 ExistentialQuantification , 7 GeneralizedNewtypeDeriving # } 8 9 10 11 12 13 14 module Base where import import Control . Control . Monad Monad . S t a t e 15 16 17 18 Monad m) => m a liftStateT : : ( liftStateT = l i f t > StateT s m a 19 20 21 mkFst : : ( a > c ) > ( a , b ) mkFst f ( a , b ) = ( f a , b ) > (c , b) mkSnd : : ( b > (a , d) 22 23 24 > d) > (a , b) 177 A.1 Base denitions 25 mkSnd f ( a , b ) = ( a , f b ) 26 27 28 29 30 31 32 33 34 Linear Continuation newtype LContT m a = LContT { unLContT : : forall r . (a > m r ) > m r } instance (Monad m) => Monad ( LContT m) where return a = LContT $ \k > k a ( LContT m) >>= f 35 36 37 = LContT $ \k Monad > m (\ x => return runLContT : : ( m) LContT m a runLContT ( LContT m) = m > unLContT ( f x ) k ) >m a 38 39 40 41 42 43 44 Monad => liftLContT : : ( m) m a > LContT m a l i f t L C o n t T m = LContT $ \k > m >>= k instance MonadTrans LContT l i f t = liftLContT where 45 46 47 48 49 50 51 52 53 54 55 56 Er ro rT newtype of the mtl error EitherT m a = EitherT { unEitherT : :m ( Either error a) } instance (Monad m) => Monad ( EitherT error m) where return = EitherT . return . Right ( EitherT m) >>= f = EitherT $ do v < m case v of Left e > return $ Left Right a > unEitherT $ f 57 58 59 60 61 62 63 l i f t E i t h e r T m = EitherT $ m >>= instance MonadTrans ( EitherT l i f t = liftEitherT 64 65 throwEitherT : : ( return . Right error ) where Monad m) => error > EitherT 178 error m a e a A.1 Base denitions 66 throwEitherT error 67 68 69 70 71 72 73 74 75 76 = EitherT $ Monad error return => catchEitherT : : ( m) EitherT m a > ( > EitherT m a) > EitherT m a catchEitherT ( EitherT m) h = EitherT $ v < m v e > unEitherT $ h e a > $ a error do error case of Left Right error return Right 77 78 79 80 81 82 83 Usefull printing instance Show ( a > b ) where show _ = "<fun>" 179 $ Left error A.2 Fist-Class Monadic Signatures A.2 Fist-Class Monadic Signatures 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 GeneralizedNewtypeDeriving , 6 KindSignatures , 7 ExistentialQuantification 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 module Call ( call , callT , instanciateStream , instanciateStreamT , instanciate , instanciateT , instanciateNoT , IdentityT ( . . ) , liftIdentityT , liftSContT , SCall , SCont , SCallT , SContT , stream , streamT , clv ' p1 , clv ' p2 , clv ' p3 , clv ' p4 , clv ' p5 , clv ' p6 , clv ' p7 , clv ' p8 ) where 36 37 38 39 import import Control . Control . Monad Monad . Trans 180 # } A.2 Fist-Class Monadic Signatures 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 data SCall t h e c l a s s f i n a l v a l u e = End f i n a l v a l u e | C a l l ( t h e c l a s s ( SCont t h e c l a s s ) ( SCall t h e c l a s s f i n a l v a l u e ) ) newtype SCont t h e c l a s s a = SCont { unSCont : : f o r a l l e . ( a > SCall t h e c l a s s e ) > SCall t h e c l a s s e } instance Monad return a ( SCont t h e c l a s s ) = SCont $ \k ( SCont m) >>= f = SCont $ \k return m >>= (m >>= a >>= f return ≡ f ≡ m f ) >>= g ≡ where > k a > m (\ x > unSCont ( f x ) k ) a m >>= ( \ x > f x >>= g ) 56 57 58 59 call :: ( f o r a l l r . ( a > r ) > t h e c a l l ( SCont t h e c a l l ) r ) > SCont t h e c a l l a c a l l a = SCont $ \k > C a l l ( a k ) 60 61 62 63 stream : : SCont t h e c l a s s a stream m = unSCont m End > SCall t h e c l a s s a 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 Monad => instanciateStream : : ( m) ( t h e c l a s s ( SCont t h e c l a s s ) ( SCall t h e c l a s s f i n a l v a l u e ) > m ( SCall t h e c l a s s f i n a l v a l u e ) ) > SCall t h e c l a s s f i n a l v a l u e > m finalvalue i n s t a n c i a t e S t r e a m i n t e r p r e t = aux aux (End r) = r aux ( C a l l c ) = i n t e r p r e t c >>= aux where return Monad => instanciate : : ( m) ( t h e c l a s s ( SCont t h e c l a s s ) ( SCall t h e c l a s s a ) > m ( S Call t h e c l a s s a ) ) > SCont t h e c l a s s a >m a i n s t a n c i a t e i n t e r p r e t t = i n s t a n c i a t e S t r e a m i n t e r p r e t $ stream t 181 A.2 Fist-Class Monadic Signatures 81 82 83 84 85 86 TRANSFORMERS 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 data SCallT t h e c l a s s m e = EndT e | CallT ( t h e c l a s s ( SContT t h e c l a s s m) (m ( SCallT t h e c l a s s m e ) ) ) newtype SContT t h e c l a s s m a = SContT { unSContT : : f o r a l l e . ( a > m ( SCallT t h e c l a s s m e ) ) > m ( SCallT t h e c l a s s m e ) } instance (Monad m) => Monad ( SContT t h e c l a s s return x = SContT $ \k > k x ( SContT m) >>= f = SContT $ \k > m (\ x m) where > unSContT ( f x ) k ) 102 103 104 105 106 l i f t S C o n t T m = SContT $ \k > m >>= k instance MonadTrans ( SContT t h e c a l l ) l i f t = liftSContT where 107 108 109 110 callT : : ( Monad m) => ( forall r . 111 112 113 114 (a > r ) > t h e c a l l ( SContT t h e c a l l m) r ) > SContT t h e c a l l m a c a l l T a = SContT (\ k > $ CallT ( a k ) ) return 115 116 117 streamT : : ( Monad m) => 118 119 streamT m = unSContT m ( SContT t h e c l a s s m a > m ( SCallT t h e c l a s s m a ) . EndT) return 120 121 182 A.2 Fist-Class Monadic Signatures 122 123 124 125 126 127 128 129 Monad => instanciateStreamT : : ( m) ( t h e c l a s s ( SContT t h e c l a s s t ) ( t ( SCallT t h e c l a s s t a ) ) > m ( SCallT t h e c l a s s t a ) ) > SCallT t h e c l a s s t a >m a i n s t a n c i a t e S t r e a m T i n t e r p r e t = aux aux (EndT r) = r aux ( CallT c ) = i n t e r p r e t c >>= aux where return 130 131 132 133 134 135 136 137 138 139 Monad Monad => instanciateT : : ( ( t m) , MonadTrans t , m) ( t h e c l a s s ( SContT t h e c l a s s m) (m ( SCallT t h e c l a s s m a ) ) > t m ( SCallT t h e c l a s s m a ) ) > SContT t h e c l a s s m a > t m a instanciateT interpret t = c < l i f t $ streamT t instanciateStreamT i n t e r p r e t c do 140 141 142 143 144 145 146 147 148 149 clv clv clv clv clv clv clv clv ' p1 ' p2 ' p3 ' p4 ' p5 ' p6 ' p7 ' p8 = = = = = = = = liftSContT clv ' p1 . clv clv ' p1 . clv clv ' p1 . clv clv ' p1 . clv clv ' p1 . clv clv ' p1 . clv clv ' p1 . clv ' p1 ' p2 ' p3 ' p4 ' p5 ' p6 ' p7 150 151 152 153 154 Identity Transformer newtype I d e n t i t y T deriving (Monad) m a = IdentityT { runIdentityT : : m a } 155 156 157 158 159 160 l i f t I d e n t i t y T : : m a > IdentityT m a l i f t I d e n t i t y T = IdentityT instance MonadTrans I d e n t i t y T l i f t = liftIdentityT where 161 162 183 A.2 Fist-Class Monadic Signatures 163 164 165 166 167 Monad => instanciateNoT : : ( m) ( t h e c l a s s ( SContT t h e c l a s s m) (m ( SCallT t h e c l a s s m a ) ) > m ( SCallT t h e c l a s s m a ) ) > SContT t h e c l a s s m a > m a i n s t a n c i a t e N o T i n t e r p m = streamT m >>= i n s t a n c i a t e S t r e a m T i n t e r p 184 A.3 Error Handling Layer A.3 Error Handling Layer 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 ExistentialQuantification , 7 GeneralizedNewtypeDeriving # } 8 9 10 11 12 13 14 15 16 17 module Error where import import Base Call import import import Control . Control . Control . Monad Monad . I d e n t i t y Monad . Trans 18 19 Error 20 21 22 data 23 24 E r r o r C a l l e (m : : > ) r = f o r a l l a . RaiseError e | f o r a l l a . Try (m a ) ( e 25 26 27 28 29 Monad try :: ( > r) > r) => Monad m) => SContT ( E r r o r C a l l e ) m a > ( e > SContT ( E r r o r C a l l e ) m a ) > SContT ( E r r o r C a l l e ) m a m h = c a l l T $ Try m h 31 33 (a > m a) (a raiseError : : ( m) e > SContT ( E r r o r C a l l e ) m a raiseError e = callT $ RaiseError e 30 32 Class try 34 35 36 37 38 try ' l v try Monad Monad => :: ( m, m1) ( SContT ( E r r o r C a l l e ) m1 b > m (m a ) ) ' lv lv m h = $ lv $ ( m) ( join try return 39 185 > b return > (e . h) > b) >m a A.3 Error Handling Layer 40 41 42 43 44 45 Monad Error newtype 48 49 50 51 error 54 55 Monad 58 61 62 63 64 => instance MonadTrans ( ErrorT l i f t = liftErrorT runErrorT : : ( error m a error ) where Monad m) => error Either error ErrorT m a >m ( a) runErrorT = unEitherT . runLContT . unErrorT Monad m) => error error = ErrorT $ l i f t raiseErrorT : : ( raiseErrorT 59 60 m) a} liftErrorT : : ( m) m a > ErrorT l i f t E r r o r T = ErrorT . l i f t . l i f t 56 57 error deriving Monad 52 53 Wright ! ErrorT m a = ErrorT { unErrorT : : LContT ( EitherT ( ) 46 47 Done tryErrorT : : ( Monad m) => error > ErrorT $ throwEitherT error m a error ErrorT m a > ( > ErrorT m a) > ErrorT m a tryErrorT ( ErrorT m) h = ErrorT $ LContT $ \k > catchEitherT ( unLContT m k ) (\ e > unLContT ( unErrorT $ h e ) k ) error error error 65 66 67 68 Interpretation 69 70 71 72 73 74 75 76 77 78 79 of Call Monad as Er ro rT => interpretErrorT : : ( m) E r r o r C a l l e ( SContT ( E r r o r C a l l e ) m) (m ( SCallT ( E r r o r C a l l e ) m a ) ) > ErrorT e m ( SCallT ( E r r o r C a l l e ) m a ) interpretErrorT ( RaiseError e k) = r a i s e E r r o r T e >>= l i f t E r r o r T . k i n t e r p r e t E r r o r T ( Try m h k) = r < tryErrorT ( i n s t a n c i a t e T i n t e r p r e t E r r o r T m) (( instanciateT interpretErrorT ) . h) liftErrorT $ k r do 80 186 A.3 Error Handling Layer 81 82 83 Monad => runErrorCallErrorT : : ( m) SContT ( E r r o r C a l l e ) m a > m ( e a) runErrorCallErrorT = runErrorT . ( i n s t a n c i a t e T i n t e r p r e t E r r o r T ) 187 Either A.4 Store Layer A.4 Store Layer 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 GeneralizedNewtypeDeriving , 7 ExistentialQuantification 8 9 10 11 12 13 14 15 16 17 18 module Store import import Base Call import import import Control . Control . Control . # } where Monad Monad . S t a t e Monad . Trans import qualified Data . Map as Map 19 20 21 22 Implements store calls 23 24 25 26 data elem StoreCall store (m : : > f o r a l l dat . StoreGet ( store elem 27 ( dat 28 elem | f o r a l l dat . StorePut 31 32 33 ( store elem dat ) dat ( store elem dat elem 34 35 36 r = dat ) > r) 29 30 ) | f o r a l l dat . StoreToFun ( s t o r e (( elem > r) elem dat ) > dat ) > r) 37 38 39 | f o r a l l dat . S t o r e I n i t dat ( store elem 188 dat > r) A.4 Store Layer 40 41 42 43 44 elem , dat ) elem dat ) | f o r a l l dat a n . StoreFoldM ( a > ( a ( store (a > r ) > m a) 45 46 47 48 49 storeFoldM storeInit storeToFun f a store dat store storeGet storePut store store elem elem = c a l l T $ StoreGet s t o r e dat = c a l l T $ StorePut s t o r e storeGets storeGetsM store store elem elem f f 50 51 52 53 54 55 56 57 58 61 64 elem f elem >>= = f >>= ( s t o r e P u t s t o r e elem > SContT ( S t o r e C a l l s t o r e 65 66 elem ) Monad m) => storeToList : : ( store dat s t o r e T o L i s t s t o r e = storeFoldM (\ l d > return f a store dat store elem elem dat elem >>= return elem >>= f = ( storePut store storeModifyM s t o r e storeGet store 62 63 = storeGet store = storeGet store elem f elem >>= storeModify store storeGet store 59 60 = c a l l T $ StoreFoldM = callT $ StoreInit = c a l l T $ StoreToFun . f . f elem ) elem ) m [( elem , dat ) ] $ d : l ) [ ] store 67 68 69 70 71 72 Monad => storeFromList : : ( m) dat > [( , dat ) ] > SContT ( S t o r e C a l l s t o r e elem elem ) m ( store elem dat ) 73 74 75 76 storeFromList d e f l t [ ] = storeInit deflt storeFromList d e f l t (( e , v) : l ) = store < storeFromList d e f l t l storePut store e v do 77 78 79 80 Interpretation of Store as Data . Map 189 A.4 Store Layer 81 82 83 data StoreP elem 84 85 86 dat = StoreP { storeP ' storeP ' } , , ) init map : : dat , : : Map . Map deriving ( Eq , Show Read Ord 87 88 89 90 Monad Ord elem ) => elem ) m a runStoreP : : ( m, SContT ( S t o r e C a l l StoreP >m a 91 92 93 runStoreP = i n s t a n c i a t e N o T i n t e r p r e t S t o r e P where i n t e r p r e t S t o r e P ( StoreGet k ( d $ Map . 94 maybe 95 id lookup ( StoreP d m) e e m) k) = 96 i n t e r p r e t S t o r e P ( StoreToFun ( StoreP d m) k (\ e > d $ Map . e m) 97 maybe 98 id k) = lookup 99 i n t e r p r e t S t o r e P ( StorePut ( StoreP d m) e v k ) = k ( StoreP d (Map . e v m) ) 100 insert 101 102 interpretStoreP ( StoreInit k ( StoreP d Map . empty ) 103 104 d k) = 105 i n t e r p r e t S t o r e P ( StoreFoldM f a ( StoreP d m) k ) = r < runStoreP $ f a (Map . t o L i s t m) k r 106 do 107 108 foldM 109 110 111 112 113 114 115 Implements a state data S t a t e C a l l s t a t e (m : : > ) r = StateGet ( state > r ) | StatePut s t a t e ( ( ) > r) 116 117 118 119 120 121 stateGet = callT StateGet s t a t e P u t s = c a l l T $ StatePut s return s t a t e G e t s f = s t a t e G e t >>= stateGetsM f = s t a t e G e t >>= f . f 190 elem dat A.4 Store Layer 122 123 124 s t a t e M o d i f y f = s t a t e G e t >>= s t a t e P u t . f stateModifyM f = s t a t e G e t >>= f >>= s t a t e P u t 125 126 127 128 129 130 Monad interpretStateT : : ( m) S t a t e C a l l s t (m a ) > StateT s m a => 131 132 133 i n t e r p r e t S t a t e T ( StateGet k) = get >>= l i f t S t a t e T . k i n t e r p r e t S t a t e T ( StatePut s k ) = put s >>= l i f t S t a t e T . k 134 135 136 137 138 139 Monad => runStateCall : : ( m) s > SContT ( S t a t e C a l l s ) m a runStateCall s m = r < runStateT ( i n s t a n c i a t e T i n t e r p r e t S t a t e T m) s $ r do return fst 140 141 142 Implements a global stack 143 144 145 146 147 148 149 data S t a c k C a l l s t a c k element (m : : StackEmpty | StackPush element | StackPop > ) Bool ( (() ( Maybe r = element 150 151 152 153 154 stackEmpty = c a l l T $ StackEmpty stackPush e = c a l l T $ StackPush e stackPop = c a l l T $ StackPop 155 156 157 158 159 160 161 162 Monad => interpretStackStateT : : ( m) S t a c k C a l l t a t1 (m b ) > StateT [ a ] m b i n t e r p r e t S t a c k S t a t e T ( StackEmpty k) = gets ( . ) >>= l i f t S t a t e T . k i n t e r p r e t S t a c k S t a t e T ( StackPush e k ) = not null 191 > r) > r) > r) >m a A.4 Store Layer 163 164 165 166 167 168 169 modify ( e : ) >>= l i f t S t a t e T . k i n t e r p r e t S t a c k S t a t e T ( StackPop k) = s < get r < s [] > x : l > put l >> ( liftStateT $ k r do case of return Nothing return Just x) 170 171 172 173 174 175 176 Monad => runStackStateT : : ( m) SContT ( S t a c k C a l l t a ) m a1 > m a1 runStackStateT m = r < runStateT ( i n s t a n c i a t e T i n t e r p r e t S t a c k S t a t e T m) [ ] $ r do return fst 177 178 179 180 181 Implements data Output OutputCall out (m : : > ) r = OutputCall out ( ( ) > r) 182 183 output out = c a l l T $ OutputCall out 184 185 interpretOutputP ( OutputCall out k ) = modify ( out : ) >>= l i f t S t a t e T . k 186 187 188 runOutputP : : ( Monad m) => SContT ( OutputCall out ) m a > m ( a , [ out ] ) 189 190 runOutputP m = runStateT ( i n s t a n c i a t e T interpretOutputP m) [ ] 192 A.5 Nominal Terms A.5 Nominal Terms 1 2 3 4 5 6 7 module Term where import import Naming Call import import Control . Control . Monad Monad . I d e n t i t y 8 9 10 11 12 13 Atoms type type data 14 15 16 defined Cst = Var = in Integer Integer Naming Leaf = Atm Atm | Cst Cst | Var Var ( , , deriving Eq Ord Show , Read ) 17 18 19 20 21 data Nominal s e t perm = Abs Atm | NPerm ( Perm s e t perm ) ( , , , ) deriving Eq Ord Show Read 22 23 24 25 26 27 28 29 | data N om in al Terms Data Structure Term s e t perm = Leaf Leaf | Pair (Term s e t perm ) (Term s e t perm ) | Nominal ( Nominal s e t perm ) (Term s e t perm ) ( , , , ) deriving Eq Ord Show Read 30 31 32 Term Construction 33 34 35 atm : : Atm > Term s e t perm atm = Leaf . Atm 36 37 38 c s t : : Cst > Term s e t perm c s t = Leaf . Cst 39 193 A.5 Nominal Terms 40 41 var : : Var > Term s e t perm var = Leaf . Var 42 43 44 45 perm : : Perm s e t perm > Term s e t perm perm PermId t = t perm p t = Nominal (NPerm p ) t > Term s e t perm 46 47 48 swap : : Atm > Atm > Term s e t perm swap a b = perm ( permSwapping a b ) > Term s e t perm 49 50 51 nomAbs : : Atm > Term s e t perm nomAbs a = Nominal $ Abs a > Term s e t perm 52 53 54 p a i r : : Term s e t perm p a i r = Pair > Term s e t perm > Term s e t perm 55 56 57 58 59 60 termSize termSize termSize termSize Int : : Term s e t perm > ( Leaf _ ) = 1 ( Nominal _ u ) = 2 + ( t e r m S i z e u ) ( Pair t1 t2 ) = 1 + ( t e r m S i z e t1 ) + ( t e r m S i z e t2 ) 61 62 63 64 65 66 67 68 69 Monad => Maybe s ub st it ut e M : : ( m) ( Var > m ( (Term s e t perm ) ) ) > Term s e t perm > m (Term s e t perm ) s ub st it ut e M f = substM ' f ' v = f v >>= . ( ( var v ) where return maybe id ) 70 substM ' ( Leaf ( Var v ) ) = f ' v substM ' ( Nominal m t ) = x < 71 do 72 73 substM ' ( Pair 74 s t ) = 75 76 substM ' 77 t = do substM ' t $ Nominal m x s ' < substM ' s t ' < substM ' t $ Pair s ' t ' t return return return 78 79 80 | "substitute f t" substitute any 194 variable v by (f v) A.5 Nominal Terms in 81 82 83 84 85 t . f is a pure substitute : : function Maybe ( Var > (Term s e t perm ) ) > Term s e t perm > Term s e t perm s u b s t i t u t e f = r u n I d e n t i t y . ( su bs ti t ut eM $ I d e n t i t y . f ) 86 87 88 89 90 91 92 93 94 95 Lazy data Term Multi Set LazyTermSet s e t perm = LtsNil | L t s S i n g l e (Term s e t perm ) | LtsNode ( LazyTermSet s e t perm ) ( LazyTermSet s e t perm ) | LtsRemap ( Perm s e t perm ) ( LazyTermSet s e t perm ) ( , , ) deriving Eq Show Read 96 97 98 99 100 101 102 103 ltsMerge : : LazyTermSet s e t perm > LazyTermSet s e t perm > LazyTermSet s e t perm ltsMerge LtsNil t = t ltsMerge t LtsNil = t l t s M e r g e t1 t2 = LtsNode t1 t2 104 105 106 107 108 109 110 Monad => flattenLTS : : ( m) LazyTermSet s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) [ Term s e t perm ] 111 112 113 114 f l a t t e n L T S l t s = aux PermId [ ] l t s where aux p l LtsNil = return l 115 116 117 118 aux p l ( LtsNode n1 n2 ) = l ' < aux p l n1 aux p l ' n2 do 119 120 121 aux p l ( LtsRemap p2 n ) = p ' < permCompose MutateNone p p2 do 195 A.5 Nominal Terms aux p ' l n 122 123 aux p l ( L t s S i n g l e $ ( perm p 124 return 125 t ) = t) : l 126 127 128 129 130 toLTS : : [ Term s e t perm ] > LazyTermSet s e t perm toLTS [ ] = LtsNil toLTS ( t : l ) = LtsNode ( L t s S i n g l e t ) ( toLTS l ) 131 132 133 134 135 Head data 136 137 138 139 } Permutation Susp s e t perm element = Susp { suspPerm : : Perm s e t perm , suspElem : : element deriving ( Eq , Ord , Show , Read ) 140 141 142 143 144 145 146 Monad => termHeadPerm : : ( m) Term s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( Susp s e t perm (Term s e t perm ) ) 147 148 149 150 151 152 153 termHeadPerm ( Nominal (NPerm p ) t ) = Susp p2 u < termHeadPerm t p3 < permCompose MutateNone p p2 $ Susp p3 u termHeadPerm t = $ Susp PermId t do return return 154 155 156 157 158 159 160 161 162 Head Reduction Function Monad => permStepDown : : ( m) Term s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) (Term s e t perm ) 196 A.5 Nominal Terms 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 permStepDown ( Nominal (NPerm p ) ( Leaf (Atm a ) ) ) = b < permImage p a $ Leaf (Atm b ) permStepDown ( Nominal (NPerm _) ( Leaf ( Cst c ) ) ) = $ Leaf ( Cst c ) permStepDown ( Nominal (NPerm p1 ) ( Nominal (NPerm p2 ) u ) ) = p3 < permCompose MutateNone p1 p2 permStepDown ( perm p3 u ) permStepDown ( Nominal (NPerm p ) ( Nominal ( Abs a ) u)) = b < permImage p a $ nomAbs b ( perm p u ) permStepDown ( Nominal (NPerm p ) ( Pair t1 t2 ) ) = $ Pair ( perm p t1 ) ( perm p t2 ) permStepDown t = t do return return do do return return return 178 179 180 181 182 183 184 Directions for Zipper on No mi na l data TermDir = P a i r L e f t | PairRight deriving ( Eq , Ord , Read , Show) Terms | NominalDown 185 186 187 188 189 190 termDirs ( Leaf _ ) = [] termDirs ( Nominal n t ) = [ ( NominalDown , ( t , (\ x termDirs ( Pair t1 t2 ) = [ ( P a i r L e f t , ( t1 , (\ x ( PairRight , ( t2 , (\ x ] 197 > Nominal n x ) ) ) ] > Pair x t2 ) ) ) , > Pair t1 x ) ) ) A.6 Sets and Permutation Layer A.6 Sets and Permutation Layer 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures # } 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 module Naming import Call where import qualified import qualified import type Control . Atm = Data . Set as Set Data . Map as Map Monad Integer data MutateSide = MutateLeft deriving ( Eq , Ord , Read , Show) | MutateRight | MutateNone 21 22 23 24 25 msFlip msFlip msFlip msFlip : : MutateSide MutateNone = MutateLeft = MutateRight = > MutateSide MutateNone MutateRight MutateLeft 26 27 28 29 30 31 32 33 data S e t C a l l s e t (m SetIsIn set | SetIsSubset set | SetSize set | SetSet Bool True 34 35 36 37 38 | | | | : :: Atm set ) Bool Atm Mutate > , False r = Bool Bool Int ( ( ( set ( set : > > > > r) r) r) r) Do NOT M u t a t e SetEmptyNative SetToList set SetUnion MutateSide s e t s e t SetInter MutateSide s e t s e t ( set ( [ Atm ] ( set ( set 39 198 > > > > r) r) r) r) A.6 Sets and Permutation Layer 40 41 42 43 data Set s e t = SetEmpty | SetNative set ( , , , ) deriving Eq Ord Show Read 44 45 setIsIn : : ( Monad m) => 46 47 Set s e t > Atm > SContT ( S e t C a l l s e t ) m 48 49 50 setIsIn setIsIn Bool return False SetEmpty _= ( SetNative set ) a = callT $ SetIsIn set a 51 52 53 54 setSize : : ( Monad m) => Set t 55 56 57 return setIsSubSet : : ( Monad m) => 60 61 Set t > Set t > SContT ( S e t C a l l t ) m 62 63 64 setIsSubSet SetEmpty _ return True = 65 66 67 68 s e t I s S u b S e t ( S e t N a t i v e s e t ) SetEmpty n < callT $ SetSize set ( n == 0) do = return 69 70 71 setIsSubSet ( SetNative set1 ) ( SetNative set2 ) = callT $ SetIsSubset set1 set2 72 73 74 75 76 77 Monad 80 => setNativeEmpty : : ( m) SContT ( S e t C a l l s e t ) m ( Set s e t ) 78 79 Int s e t S i z e SetEmpty = 0 s e t S i z e ( SetNative set ) = callT $ SetSize set 58 59 > SContT ( S e t C a l l t ) m setNativeEmpty = do s < c a l l T SetEmptyNative $ SetNative s return 199 Bool A.6 Sets and Permutation Layer 81 82 setSet : : ( Monad m) => 83 Bool > Atm > > Set s e t > SContT ( S e t C a l l s e t ) m ( Set s e t ) Bool 84 85 86 87 88 89 90 s e t S e t mutate a v SetEmpty s < setNativeEmpty s e t S e t mutate a v s = do 91 92 93 94 s e t S e t mutate a v ( S e t N a t i v e s e t ) = s < c a l l T $ S e t S e t mutate a v s e t $ SetNative s do return 95 96 97 setToList : : ( Monad m) => Set s e t > SContT ( S e t C a l l s e t ) m [ Atm ] 98 99 100 101 return s e t T o L i s t SetEmpty = [] s e t T o L i s t ( S e t N a t i v e s e t ) = c a l l T $ SetToList s e t 102 103 setFromList : : ( Monad m) => [ Atm ] > SContT ( S e t C a l l s e t ) m ( Set s e t ) 104 105 106 107 setFromList [] = setFromList ( a : l ) = return do s < SetEmpty setFromList l setSet a s 108 109 110 111 112 Monad True True => setUnshare : : ( m) Set s e t > SContT ( S e t C a l l s e t ) m ( Set s e t ) setUnshare s e t = s e t T o L i s t s e t >>= setFromList 113 114 115 setSwap : : ( Monad m) => 116 117 118 119 120 121 setSwap mutate s e t a b = > > > > do Bool Set s e t Atm Atm SContT ( S e t C a l l s e t ) m ( Set s e t ) va < setIsIn set a 200 A.6 Sets and Permutation Layer vb < s e t I s I n s e t b s1 < s e t S e t mutate a vb s e t s e t S e t mutate b va s1 122 123 124 125 126 setUnion : : ( Monad m) => 127 128 129 MutateSide > Set s e t > Set s e t > SContT ( S e t C a l l s e t ) m ( Set s e t ) 130 131 setUnion _ SetEmpty set2 = return set2 = return set1 132 133 setUnion _ set1 SetEmpty 134 135 136 137 setUnion ms ( S e t N a t i v e s e t 1 ) ( S e t N a t i v e s e t 2 ) = s < c a l l T $ SetUnion ms s e t 1 s e t 2 $ SetNative s do return 138 139 140 setInter : : ( Monad m) => MutateSide > Set s e t > Set s e t > SContT ( S e t C a l l s e t ) m ( Set s e t ) 141 142 143 144 145 setInter _ SetEmpty set2 = return set2 = return set1 146 147 setInter _ set1 SetEmpty 148 149 150 151 s e t I n t e r ms ( S e t N a t i v e s e t 1 ) ( S e t N a t i v e s e t 2 ) = s < c a l l T $ S e t I n t e r ms s e t 1 s e t 2 $ SetNative s do return 152 153 { Permutations 154 155 } 156 157 158 159 160 161 162 data PermCall perm (m : : PermImage | PermSwapRight Bool True : | PermCompose Mutate , > ) r = perm Atm perm Atm False : Do Not MutateSide perm perm 201 (Atm Atm ( perm > r) > r) Mutate ( perm > r) A.6 Sets and Permutation Layer | PermSupp | PermIdNative | PermUnshare 163 164 165 perm ( [ Atm ] ( perm ( perm perm > r) > r) > r) 166 167 168 169 170 171 172 173 174 175 data Perm s e t perm = PermId | PermSwap Atm Atm | PermNative perm perm ( Set s e t ) ( , , , ) deriving Eq Ord Show Read 176 177 178 179 Monad m) => permSupp : : ( Perm s e t t > SContT ( S e t C a l l s e t ) m ( Set s e t ) 180 181 182 permSupp PermId permSupp ( PermSwap a b ) = = 183 184 185 permSupp ( PermNative _ _ s ) = return SetEmpty if a == b then return SetEmpty else setFromList [ a , b ] return s 186 187 188 189 190 permInverse : : Perm s e t perm > Perm s e t perm permInverse ( PermNative p i s ) = PermNative i p s permInverse p = p 191 192 permImage : : ( Monad m) => 193 194 Perm s e t perm > Atm > SContT ( PermCall perm ) m Atm 195 196 permImage PermId c = 197 198 199 200 201 202 permImage ( PermSwap a b ) c = return c if c == a then return b else if c == b then return else return 203 202 a c A.6 Sets and Permutation Layer 204 permImage ( PermNative p _ _) c = c a l l T $ PermImage p c 205 206 207 208 209 210 permImageInv : : ( Monad m) => Perm s e t perm > Atm > SContT ( PermCall perm ) m Atm permImageInv = permImage . permInverse 211 212 213 214 215 216 217 permSwapping : : Atm > Atm > Perm s e t perm permSwapping a b = a == b PermId PermSwap a b if then else 218 219 220 221 222 223 224 Monad => permCompose : : ( m) MutateSide > Perm s e t perm > Perm s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( Perm s e t perm ) 225 226 permCompose _ PermId p = return p permCompose _ p PermId = return p 227 228 229 230 231 232 233 permCompose ms ( PermSwap a i a < permImageInv p i b < permImageInv p permSwapRight (ms == do b) p = a b MutateRight ) p i a i b 234 235 236 permCompose ms p ( PermSwap a b ) = permSwapRight (ms == MutateLeft ) p a b 237 238 239 240 241 242 243 permCompose ms ( PermNative p1 i 1 _) ( PermNative p2 i 2 _) = p3 < c a l l T $ PermCompose ms p1 p2 i 3 < c a l l T $ PermCompose ( msFlip ms) i 2 i 1 l < c a l l T $ PermSupp p3 s3 < clv ' p1 $ setFromList l $ PermNative p3 i 3 s3 do return 244 203 A.6 Sets and Permutation Layer 245 246 247 248 249 Monad 250 251 = return permFromSwappings [ ( a , b ) ] = return permFromSwappings [ ] 252 253 => permFromSwappings : : ( m) [ ( Atm, Atm ) ] > SContT ( PermCall a ) ( SContT ( S e t C a l l s e t ) m) ( Perm s e t a ) PermId $ permSwapping a b 254 255 256 257 258 permFromSwappings l where return = aux PermId l aux p [ ] = p aux p ( ( a , b ) : l ) = do 259 p2 < permSwapRight aux p2 l True p a b 260 261 262 263 permSwapRight : : ( 264 265 266 267 268 269 > > > > Bool Monad m) => Perm s e t a Atm Atm SContT ( PermCall a ) ( SContT ( S e t C a l l s e t ) m) ( Perm s e t a ) 270 271 272 273 274 275 276 permSwapRight mutate perm a b = a == b perm perm PermId > $ PermSwap a b if then return else case of return 277 278 279 280 281 282 283 284 PermSwap c d > sameSwap ( a , b ) ( c , d ) PermId p < permFromSwappingsNative [ ( c , d ) , ( a , b ) ] i < permFromSwappingsNative [ ( a , b ) , ( c , d ) ] s < partialSupp p [ a , b , c , d ] $ PermNative p i s if then return else do return 285 204 A.6 Sets and Permutation Layer PermNative p pa < pb < p2 < i2 < s' < 286 do 287 288 289 290 291 i s > c a l l T $ PermImage p a c a l l T $ PermImage p b c a l l T $ PermSwapRight mutate p a b c a l l T $ PermSwapRight mutate i pa pb clv ' p1 $ s e t S e t mutate a ( a /= pb ) s p 292 s2 < 293 294 return 295 296 297 298 299 300 b = p (a b) a = p2 a clv ' p1 $ s e t S e t mutate b ( b /= pa ) s p a = p (a b) b = p2 b $ PermNative p2 i 2 s2 where Bool sameSwap : : (Atm, Atm) > (Atm, Atm) > sameSwap ( a , b ) ( c , d ) = ( ( a == c ) && ( b == d ) ) | | ( ( a == d ) && ( b == c ) ) 301 302 303 304 permFromSwappingsNative l = pid < c a l l T $ PermIdNative auxFSN pid l do 305 306 307 308 309 return auxFSN p [ ] = p auxFSN p ( ( a , b ) : l ) = p2 < c a l l T $ PermSwapRight auxFSN p2 l do True p a b 310 311 312 partialSupp p l = sl < do mapM (\ x > 313 ) l 314 315 316 foldM 317 318 (\ s e t ( a , v ) SetEmpty sl do px < return > clv ' p1 $ s e t S e t 319 320 321 322 323 324 325 326 Monad c a l l T $ PermImage p x ( x , ( x /= px ) ) => permDS : : ( m) Perm s e t perm > Perm s e t perm > SContT ( PermCall perm ) 205 True a v set ) A.6 Sets and Permutation Layer ( SContT ( S e t C a l l s e t ) m) ( Set s e t ) 327 328 329 permDS p1 p2 = do setPermute : : ( Monad m) => 330 331 332 p3 < permCompose MutateNone ( permInverse p1 ) p2 clv ' p1 $ permSupp p3 333 334 335 Bool 336 337 338 339 340 > Perm s e t perm > Set s e t > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( Set s e t ) 341 342 343 setPermute mutate PermId set = return set 344 345 346 setPermute mutate ( PermSwap a b ) s e t = clv ' p1 $ setSwap mutate s e t a b 347 348 349 350 351 setPermute mutate p set = l < clv ' p1 $ s e t T o L i s t s e t l' < ( permImage p ) l clv ' p1 $ setFromList l ' do mapM 352 353 354 355 356 357 358 Monad => permUnshare : : ( m) Perm s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( Perm s e t perm ) 359 360 permUnshare p@PermId = return p permUnshare p@( PermSwap _ _) = return p 361 362 363 364 permUnshare ( PermNative p i s ) = do 365 366 p' < i' < s' < return 367 206 c a l l T $ PermUnshare c a l l T $ PermUnshare clv ' p1 $ setUnshare $ PermNative p ' i ' p i s s' A.6 Sets and Permutation Layer 368 369 370 371 372 { Interpretation 373 374 of Sets and Perms as Data . Map } 375 376 i n t e r p r e t S e t P e r s i s t e n t : : S e t C a l l ( Set . Set Atm) m r > r 377 378 379 interpretSetPersistent ( SetIsIn k ( Set . member a set ) set a k) = interpretSetPersistent ( SetSize k ( Set . s i z e set ) set k) = interpretSetPersistent ( SetIsSubset k ( Set . i s S u b s e t O f s1 s2 ) s1 s2 k) = 380 381 382 383 384 385 386 387 388 389 390 391 392 393 i n t e r p r e t S e t P e r s i s t e n t ( SetSet _ a v s k) = k ( v Set . a s Set . a s ) i n t e r p r e t S e t P e r s i s t e n t ( SetEmptyNative k) = k Set . empty if then else insert delete 394 395 396 i n t e r p r e t S e t P e r s i s t e n t ( SetToList k ( Set . t o L i s t s e t ) set k) = i n t e r p r e t S e t P e r s i s t e n t ( SetUnion k ( Set . s1 s2 ) interpretSetPersistent ( SetInter k ( Set . i n t e r s e c t i o n s1 s2 ) _ s1 s2 k ) = 397 398 399 400 401 union _ s1 s2 k ) = 402 403 404 405 406 407 Monad => runSetPersistent : : ( m) SContT ( S e t C a l l ( Set . Set Atm) ) m a > m a runSetPersistent = instanciateNoT i n t e r p r e t S e t P e r s i s t e n t 408 207 A.6 Sets and Permutation Layer 409 410 411 mapImage : : ( mapImage p Ord 412 413 mapCompose : : ( a) a = => Map . Map a a > a > maybe a id $ Map . lookup Ord a) 414 415 => a a p Map . Map a a > Map . Map a a > Map . Map a a 416 417 418 mapCompose p1 p2 = Map . foldWithKey f p1 p2 f atm image p = Map . atm ( mapImage p1 image ) p where insert 419 420 421 422 Ord mapSupp : : ( mapSupp p 423 424 b) = => Map . Map b b > [ b ] foldl (\ l a > if a == mapImage then l else a : l p a ) [ ] (Map . keys p ) 425 426 427 428 429 i n t e r p r e t P e r m P e r s i s t e n t : : PermCall (Map . Map Atm Atm) m r 430 431 432 i n t e r p r e t P e r m P e r s i s t e n t ( PermImage k ( mapImage p a ) p a k) = 433 434 435 436 437 438 i n t e r p r e t P e r m P e r s i s t e n t ( PermSwapRight _ p a b k ) = k ( pa = mapImage p a pb = mapImage p b Map . a pb (Map . b pa p ) ) let in insert insert 439 440 441 i n t e r p r e t P e r m P e r s i s t e n t ( PermCompose _ k $ mapCompose p1 p2 p1 p2 k ) = i n t e r p r e t P e r m P e r s i s t e n t ( PermSupp k $ mapSupp p p 442 443 444 k) = 445 446 447 i n t e r p r e t P e r m P e r s i s t e n t ( PermIdNative k Map . empty k) = 448 449 i n t e r p r e t P e r m P e r s i s t e n t ( PermUnshare 208 p k) = > r A.6 Sets and Permutation Layer 450 k p 451 452 453 454 455 456 Monad => runPermPersistent : : ( m) SContT ( PermCall (Map . Map Atm Atm) ) m a >m a 457 458 runPermPersistent = i n s t a n c i a t e N o T i n t e r p r e t P e r m P e r s i s t e n t 459 460 461 462 463 464 465 466 467 Monad => runNamingPersistent : : ( m) SContT ( PermCall (Map . Map Atm Atm) ) ( SContT ( S e t C a l l ( Set . Set Atm) ) m) a >m a runNamingPersistent = r u n S e t P e r s i s t e n t . runPermPersistent 209 A.7 Nominal Union-Find Algorithm A.7 Nominal Union-Find Algorithm 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 GeneralizedNewtypeDeriving # } 7 8 9 10 11 12 13 module UnionFind import import import import Call Naming Term Store import import Control . Control . where 14 15 16 17 Monad Monad . Trans 18 19 Abstraction 20 21 22 data 23 24 25 | | of No mi na l Datas NominalDataCall s e t perm dat (m : : > ) r NominalDataFresh ( Set s e t ) dat ( dat NominalDataPermute ( Perm s e t perm ) dat ( dat NominalDataMerge dat dat ( dat = > r) > r) > r) 26 27 28 29 nominalDataFresh s e t dat = c a l l T $ NominalDataFresh s e t dat nominalDataPermute perm dat = c a l l T $ NominalDataPermute perm dat nominalDataMerge dat1 dat2 = c a l l T $ NominalDataMerge dat1 dat2 30 31 32 33 data 34 35 36 Integer Ranked dat = Ranked { rank ' rank : : rank ' : : dat } ( , , ) data , deriving Eq Show Read 37 38 39 data UFCell s e t perm elem dat = I n d i r e c t ( Susp s e t perm 210 elem ) A.7 Nominal Union-Find Algorithm 40 41 | Direct deriving ( Eq , Show , Read ) dat 42 43 44 45 unRankUFCell ( D i r e c t ( Ranked r d ) ) = D i r e c t d unRankUFCell ( I n d i r e c t s ) = Indirect s 46 47 48 49 50 51 52 Calls type for the arr_ { u f } store elem elem elem elem UFStore s e t perm s t o r e dat m = SContT ( S t a t e C a l l ( s t o r e ( UFCell s e t perm ( Ranked dat ) ) ) ) ( SContT ( S t o r e C a l l s t o r e ) m) 53 54 55 56 elem u f D a t a I n i t : : dat > UFCell s e t perm u f D a t a I n i t dat = D i r e c t ( Ranked 0 dat ) ( Ranked dat ) 57 58 59 60 61 62 Monad => ufStoreInit : : ( m) dat > UFStore s e t perm s t o r e elem dat m ( ) 63 64 u f S t o r e I n i t dat = ( clv ' p1 $ s t o r e I n i t $ u f D a t a I n i t dat ) >>= s t a t e P u t 65 66 67 68 69 70 ufStoreGet : : ( elem > UFStore s e t perm s t o r e ( UFCell s e t perm 71 72 73 elem ufStoreGet stateGetsM (\ s t o r e 74 75 76 77 78 ufStorePut : : ( elem elem elem dat m ( Ranked dat ) ) = > clv ' p1 $ s t o r e G e t s t o r e Monad m) => elem > UFCell s e t perm ( Ranked dat ) > UFStore s e t perm s t o r e dat m ( ) 79 80 Monad m) => ufStorePut elem elem dat = 211 elem ) A.7 Nominal Union-Find Algorithm 81 stateModifyM (\ s t o r e > clv ' p1 $ s t o r e P u t s t o r e elem 82 83 84 85 86 87 88 89 ufStoreGets : : ( elem elem > ( UFCell s e t perm ( Ranked dat ) > a ) > UFStore s e t perm s t o r e dat m a 90 91 Monad m) => ufStoreGets elem f elem = ufStoreGet elem >>= return 92 93 94 95 96 97 98 99 100 ufStoreGetsM : : ( elem elem > ( UFCell s e t perm ( Ranked dat ) > UFStore s e t perm s t o r e > UFStore s e t perm s t o r e dat m a elem elem 101 102 Monad m) => ufStoreGetsM elem f = ufStoreGet dat m a ) elem >>= f 103 104 105 106 107 108 109 110 111 ufStoreModify : : ( elem 114 elem > ( UFCell s e t perm ( Ranked dat ) > UFCell s e t perm ( Ranked dat ) ) > UFStore s e t perm s t o r e dat m ( ) 112 113 Monad m) => ufStoreModify ufStoreGet elem f elem >>= elem elem = ( ufStorePut elem ) . f 115 116 117 118 119 120 121 ufStoreModifyM : : ( elem Monad m) => elem > ( UFCell s e t perm ( Ranked dat ) > UFStore s e t perm s t o r e dat m elem 212 . f dat ) A.7 Nominal Union-Find Algorithm elem ( Ranked elem dat m ( ) ( UFCell s e t perm > UFStore s e t perm s t o r e 122 123 124 125 126 elem f elem >>= ufStoreModifyM ufStoreGet = f >>= ( u f S t o r e P u t dat ) ) ) elem ) 127 128 129 130 131 132 133 134 Monad elem => ufStoreFoldM : : ( m) (a > ( , UFCell s e t perm dat ) > SContT ( S t o r e C a l l s t o r e ) m a) > a > UFStore s e t perm s t o r e dat m a elem elem elem 135 136 137 138 139 ufStoreFoldM f store < f' a clv ' p1 $ do let a = stateGet ( e , v ) = f a ( e , unRankUFCell v ) storeFoldM f ' a s t o r e 140 141 142 143 144 145 146 147 148 Monad => elem findRanked : : ( m) Susp s e t perm > UFStore s e t perm ( SContT ( PermCall ( SContT ( S e t C a l l ( Susp s e t perm elem store dat perm ) s e t ) m) ) ( , Ranked dat ) ) elem 149 150 151 152 153 154 findRanked ( Susp perm var ) = vc < u f S t o r e G e t var vc Direct d > $ Susp perm ( var , d ) do case of return 155 156 157 158 159 160 161 Indirect s > do Susp p ( r , d ) < findRanked s u f S t o r e P u t var $ I n d i r e c t ( Susp p r ) p2 < clv ' p2 $ permCompose MutateNone perm p $ Susp p2 ( r , d ) return 162 213 A.7 Nominal Union-Find Algorithm 163 164 165 166 find 167 168 169 170 171 172 173 find Monad => :: ( m) Susp s e t perm > UFStore s e t perm ( SContT ( PermCall ( SContT ( S e t C a l l ( Susp s e t perm s = do 174 elem elem store dat perm ) s e t ) m) ) ( , dat ) ) elem Susp p ( v , ( Ranked r d ) ) < $ Susp p ( v , d ) return findRanked s 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 Monad => ufDataModify : : ( m) ( dat > SContT ( NominalDataCall s e t perm dat ) ( UFStore s e t perm s t o r e dat ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) dat ) > Susp s e t perm > SContT ( NominalDataCall s e t perm dat ) ( UFStore s e t perm s t o r e dat ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) () elem elem elem 190 191 192 193 194 195 196 ufDataModify f s = Susp p ( r , Ranked rk d ) dp d2 d3 clv ' p1 $ u f S t o r e P u t r $ do < clv ' p1 $ findRanked s < nominalDataPermute p d < f d < nominalDataPermute ( permInverse p ) d2 D i r e c t ( Ranked rk d3 ) 197 198 199 200 { pr1 . r1 = pr2 . r2 201 202 203 A1 # r 1 = n1 => p3 ^ . r2 1(A1 ) # r 2 = p3^ 1 n1 , 214 p3 = p r 1 ^ 1 . pr2 A.7 Nominal Union-Find Algorithm 204 } 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 union Monad Eq elem => elem elem :: ( m, ) Susp s e t perm > Susp s e t perm > SContT ( NominalDataCall s e t perm dat ) ( UFStore s e t perm s t o r e dat ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) () elem union s1 s2 = do Susp p1 ( r1 , Ranked rk1 d1 ) < clv ' p1 $ findRanked s1 Susp p2 ( r2 , Ranked rk2 d2 ) < clv ' p1 $ findRanked s2 r1 == r2 ds < clv ' p3 $ permDS p1 p2 d3 < nominalDataFresh ds d1 clv ' p1 $ u f S t o r e P u t r1 ( D i r e c t $ Ranked rk1 d3 ) rk1 rk2 > clv ' p1 $ u f S t o r e P u t r2 ( D i r e c t $ Ranked ( rk2 + 1) d2 ) ( Susp p1 r1 ) ( Susp p2 r2 ) > ( Susp p2 r2 ) ( Susp p1 r1 ) > p3 < clv ' p3 $ permCompose MutateNone ( permInverse p1 ) p2 clv ' p1 $ u f S t o r e P u t r1 $ I n d i r e c t ( Susp p3 r2 ) ufDataModify (\ d > nominalDataMerge d d1 ) ( Susp PermId r1 ) if then do else case compare EQ do GT LT of union union do 234 235 236 237 238 239 240 241 242 243 Monad Eq elem => unions : : ( m, ) [ Susp s e t perm > SContT ( NominalDataCall s e t perm dat ) ( UFStore s e t perm s t o r e dat ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) ( Susp s e t perm ( , dat ) ) elem elem 244 215 elem ] A.7 Nominal Union-Find Algorithm 245 246 247 248 error unions [ ] = "Empty l i s t o f union f i n d s " unions [ x ] = clv ' p1 $ x unions ( x : ( y : l ) ) = x y unions ( y : l ) do union find 249 250 251 252 253 254 255 256 257 Monad Ord elem => runUFStoreP : : ( m, ) dat > SContT ( S t a t e C a l l ( StoreP ( UFCell s e t perm elem1 ( Ranked dat ) ) ) ) ( SContT ( S t o r e C a l l StoreP ) m) a >m a elem elem 258 259 260 261 runUFStoreP dat m = runStoreP $ s t o r e < s t o r e I n i t $ u f D a t a I n i t dat runStateCall store m do 216 A.8 Unication A.8 Unication 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 GeneralizedNewtypeDeriving , 7 ExistentialQuantification 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 module where Unification import import import import import import Call Naming Term UnionFind Store Error import import Control . Control . # } Monad Monad . I d e n t i t y import qualified import qualified Data . Set as Set Data . Map as Map 24 25 26 SPECIALIZED UNION FIND TO UNIFICATION 27 28 29 data 30 31 32 33 34 NominalDataUnif s e t perm = NominalDataUnif { ndu ' o c c u r s : : , ndu ' s e t : : Set s e t , ndu ' terms : : LazyTermSet s e t perm } ( , , ) Int deriving Eq Show Read 35 36 37 38 type UFStoreUnif s e t perm s t o r e m = UFStore s e t perm s t o r e Var ( NominalDataUnif s e t perm ) m 39 217 A.8 Unication 40 41 42 43 44 Monad => nominalDataUnifFresh : : ( m) Set s e t > NominalDataUnif s e t perm > SContT ( S e t C a l l s e t ) m ( NominalDataUnif s e t perm ) 45 46 47 48 nominalDataUnifFresh s ( NominalDataUnif o c c u r s s e t terms ) = s2 < setUnion MutateNone s s e t $ NominalDataUnif o c c u r s s2 terms do return 49 50 51 52 53 54 55 56 Monad => nominalDataUnifPermute : : ( m) Perm s e t perm > NominalDataUnif s e t perm > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( NominalDataUnif s e t perm ) 57 58 59 60 nominalDataUnifPermute perm ( NominalDataUnif o c c u r s s e t terms ) = s2 < setPermute perm s e t $ NominalDataUnif o c c u r s s2 ( LtsRemap perm terms ) do False return 61 62 63 64 65 66 nominalDataUnifMerge : : NominalDataUnif > NominalDataUnif > SContT ( S e t C a l l Monad => ( m) s e t perm s e t perm s e t ) m ( NominalDataUnif s e t perm ) 67 68 69 70 71 72 73 nominalDataUnifMerge do s3 < return ( NominalDataUnif o c c u r s 1 s e t 1 terms1 ) ( NominalDataUnif o c c u r s 2 s e t 2 terms2 ) = setUnion MutateNone s e t 1 s e t 2 $ NominalDataUnif ( o c c u r s 1 + o c c u r s 2 ) s3 ( l t s M e r g e terms1 terms2 ) 74 75 76 77 78 79 80 Monad Monad => interpretNominalDataUnif : : ( m1, m) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l t1 ) m1) ( NominalDataUnif t1 perm ) > m ( NominalDataUnif t1 perm ) ) 218 A.8 Unication 81 82 > NominalDataCall t1 perm ( NominalDataUnif t1 perm ) t (m b ) >m b 83 84 85 i n t e r p r e t N o m i n a l D a t a U n i f l v ( NominalDataFresh s e t dat k ) = ( l v $ clv ' p1 $ nominalDataUnifFresh s e t dat ) >>= k 86 87 88 i n t e r p r e t N o m i n a l D a t a U n i f l v ( NominalDataPermute perm dat k ) = ( lv $ nominalDataUnifPermute perm dat ) >>= k 89 90 91 i n t e r p r e t N o m i n a l D a t a U n i f l v ( NominalDataMerge dat1 dat2 k ) = ( l v $ clv ' p1 $ nominalDataUnifMerge dat1 dat2 ) >>= k 92 93 94 findUnif = find 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 Monad => ufDataModifyUnif : : ( m1) ( NominalDataUnif s e t perm > SContT ( NominalDataCall s e t perm ( NominalDataUnif s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m1 ) ) ) ( NominalDataUnif s e t perm ) ) > Susp s e t perm Var > UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m1) ) () 114 115 116 117 ufDataModifyUnif f s = i n s t a n c i a t e N o T ( i n t e r p r e t N o m i n a l D a t a U n i f clv ' p2 ) ( ufDataModify f s ) 118 119 120 121 Monad => unionUnif : : ( m) Susp s e t perm Var 219 A.8 Unication 122 123 124 125 > Susp s e t perm Var > UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ( ) 126 127 128 129 unionUnif s1 s2 = i n s t a n c i a t e N o T ( i n t e r p r e t N o m i n a l D a t a U n i f clv ' p2 ) ( s1 s2 ) union 130 131 132 133 nominalDataUnifNeutral : : NominalDataUnif s e t perm nominalDataUnifNeutral = NominalDataUnif 0 SetEmpty L t s N i l 134 135 136 137 138 139 140 141 Monad => unionsUnif : : ( m) [ Susp s e t perm Var ] > UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ( Susp s e t perm ( Var , NominalDataUnif s e t perm ) ) 142 143 unionsUnif l 144 = i n s t a n c i a t e N o T ( i n t e r p r e t N o m i n a l D a t a U n i f clv ' p2 ) ( unions l ) 145 146 147 148 149 150 151 Monad => nominalDataUnifOccurAdd : : ( m) Var > > UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ( ) Int 152 153 154 155 nominalDataUnifOccurAdd v n = ufDataModifyUnif f ( Susp PermId v ) where f d = nominalDataMerge d ( NominalDataUnif n SetEmpty L t s N i l ) 156 157 158 159 Monad => ufStoreInitUnif : : ( m) UFStoreUnif s e t perm s t o r e m ( ) 160 161 u f S t o r e I n i t U n i f = u f S t o r e I n i t nominalDataUnifNeutral 162 220 A.8 Unication 163 164 165 166 167 168 169 170 Monad Ord elem => elem runUFStoreUnifP : : ( m, ) SContT ( S t a t e C a l l ( StoreP ( UFCell s e t 1 perm1 elem1 ( Ranked ( NominalDataUnif s e t perm ) ) ) ) ) ( SContT ( S t o r e C a l l StoreP ) m) a >m a elem 171 172 runUFStoreUnifP = runUFStoreP nominalDataUnifNeutral 173 174 THE UNIFICATION ALGORITHM 175 176 177 data 178 179 180 181 Equation s e t { eqSet :: eqTerms : : } ( , perm = Equation Set s e t , [ Term s e t perm ] deriving Eq Show , Ord , Read ) 182 183 184 185 186 187 data OutputSolution s e t perm = OutputSubst Var (Term s e t perm ) | OutputFresh Var ( Set s e t ) ( , , , ) deriving Show Read Eq Ord 188 189 190 191 192 Monad iterM : : ( m) iterM f [ ] = iterM f ( x : l ) = 193 => ( t return do f x > m a) () > [t] > m () iterM f l 194 195 196 197 198 199 200 Monad 201 202 203 => splitEq : : ( m) [ Term s e t perm ] > SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ( [ Susp s e t perm Var ] , [ Term s e t perm ] ) splitEq [ ] = splitEq ( t : l ) = return ( [ ] , [ ] ) do Susp p u < termHeadPerm t 221 A.8 Unication ( vs , t s ) < s p l i t E q l u Leaf ( Var v ) > _ > 204 case 205 206 207 of return return ( ( ( Susp p v ) : vs ) , t s ) ( vs , ( perm p u ) : t s ) 208 209 210 211 212 213 214 215 216 217 Monad => decompose : : ( m) Equation s e t perm > SContT ( S t a c k C a l l s t a c k ( Equation s e t perm ) ) ( SContT ( OutputCall ( OutputSolution s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( SetCall set ) ( SContT ( ErrorCall [ ] ) m) ) ) ) ) ( ) Char 218 219 decompose ( Equation f s [ ] ) = return () 220 221 222 223 224 225 226 decompose ( Equation f s ( t : q ) ) = u < clv ' p4 $ permStepDown t l < ( clv ' p4 . permStepDown ) q u Leaf ( Var _) > clv ' p6 $ r a i s e E r r o r " Error : head v a r i a b l e s " do mapM case of 227 228 229 230 Leaf (Atm a ) val < do > clv ' p5 $ s e t I s I n f s a v a l ( clv ' p6 $ r a i s e E r r o r " Error : F r e s h n e s s Cst " ) iterM ( clv ' p6 . ( checkLeaf $ Atm a ) ) l when 231 232 233 234 Leaf ( Cst c ) > iterM ( clv ' p6 . ( checkLeaf $ Cst c ) ) l 235 236 237 Nominal (NPerm _) _ > clv ' p6 $ r a i s e E r r o r "permStepDown did Wrong ! " 238 239 240 241 242 243 244 Nominal ( Abs a) _ > ( atms , terms ) < clv ' p6 $ a l l A b s a ( u : l ) f s e t a = clv ' p5 $ s e t S e t a fs1 < f f s atms f s 2 < clv ' p5 $ s e t S e t a fs1 stackPush ( Equation f s 2 terms ) do let False foldM False 222 False True set A.8 Unication 245 Pair t1 t2 > ( l1 , l 2 ) < clv ' p6 $ a l l P a i r ( u : l ) stackPush ( Equation f s l 1 ) stackPush ( Equation f s l 2 ) 246 do 247 248 249 250 251 252 where checkLeaf : : ( Monad m) => 253 254 255 256 checkLeaf l 1 ( Leaf l 2 ) = Leaf > Term s e t perm > SContT ( E r r o r C a l l when 257 258 checkLeaf _ _ 259 260 allPair : : ( = Monad m) => 263 265 allPair = foldM 267 ( l 1 /= l 2 ) ( r a i s e E r r o r "Not the same l e a f ! " ) r a i s e E r r o r "Not the same l e a f ! " String 262 266 (\( ll , l r ) t > t Pair t1 t2 case of return 268 > ( ( t1 : l l ) , ( t2 : l r ) ) 269 _ 270 > r a i s e E r r o r "Not a Pair " 271 ) ([] ,[]) 272 273 274 allAbs : : ( Monad m) => 275 277 278 279 280 allAbs a l = mapM ( checkAbs 281 282 283 284 285 Atm > [ Term s e t perm ] > SContT ( E r r o r C a l l ) m ( [ Atm ] , [ Term s e t perm ] ) String 276 checkAbs : : ( m () [ Term t t1 ] > SContT ( E r r o r C a l l ) m ( [ Term t t1 ] , [ Term t t1 ] ) 261 264 String ) Monad m) => a ) l >>= return . unzip Atm > Term t t1 > SContT ( E r r o r C a l l m (Atm, Term t t1 ) String ) 223 A.8 Unication 286 checkAbs a ( Nominal ( Abs b ) u ) = ( b , ( swap a b u ) ) checkAbs a _ = r a i s e E r r o r "Not un a b s t r a c t i o n " 287 return 288 289 290 291 292 293 294 295 296 297 298 299 300 301 Monad => checkLastOccurence : : ( m) Var > SContT ( S t a c k C a l l s t a c k ( Equation s e t perm ) ) ( SContT ( OutputCall ( OutputSolution s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) ) ( ) 302 303 304 305 306 307 308 checkLastOccurence v = Susp p ( r , d ) < clv ' p2 $ f i n d U n i f $ Susp PermId v ( ndu ' o c c u r s d == 1) ( terms < clv ' p4 $ f l a t t e n L T S $ ndu ' terms d terms [] > clv ' p1 $ output ( OutputFresh v ( ndu ' s e t d ) ) do when do case of 309 t : l 310 > do 311 312 let 313 314 315 ) stackPush ( Equation ( ndu ' s e t d ) terms ) clv ' p1 $ output ( OutputSubst v t ) return f _= nominalDataUnifNeutral clv ' p2 $ ufDataModifyUnif f ( Susp PermId v ) 316 317 318 319 320 321 322 323 324 325 326 Monad => storeEquation : : ( m) Susp s e t perm Var > Equation s e t perm > SContT ( S t a c k C a l l s t a c k ( Equation s e t perm ) ) ( SContT ( OutputCall ( OutputSolution s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) ) ( ) 224 A.8 Unication 327 328 329 330 331 332 333 334 s t o r e E q u a t i o n ( Susp p v ) ( Equation f s terms ) = i = permInverse p ifs < clv ' p4 $ setPermute i fs d d i f f = NominalDataUnif 0 i f s ( LtsRemap i ( toLTS terms ) ) f d = nominalDataMerge d d d i f f clv ' p2 $ ufDataModifyUnif f ( Susp p v ) checkLastOccurence v do let False let let 335 336 337 338 339 340 341 occurCheck : : ( UFStore set perm store Monad m) => elem 342 ( NominalDataUnif s e t 1 perm1 ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) () 343 344 345 Char 346 347 348 349 350 351 352 occurCheck = ufStoreFoldM f ( ) where f ( ) (_ , I n d i r e c t _) = () return 353 354 355 356 f ( ) (_ , D i r e c t d ) = ( ndu ' o c c u r s d > 0) ( clv ' p3 $ r a i s e E r r o r " OccurCheck F a i l e d " ) when 357 358 359 360 361 362 363 364 365 Monad => solveLoop : : ( m) SContT ( S t a c k C a l l s t a c k ( Equation s e t perm ) ) ( SContT ( OutputCall ( OutputSolution s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) ) ) ( ) Char 366 367 solveLoop = 225 A.8 Unication 368 369 370 371 372 373 374 375 376 377 378 379 380 381 do meq < stackPop meq > clv ' p2 $ occurCheck eq > Equation f s l t = eq ( s u s p s , terms ) < clv ' p4 $ s p l i t E q l t susps decompose ( Equation f s terms ) Susp p ( v ,_) < clv ' p2 $ u n i o n s U n i f s u s p s clv ' p2 $ nominalDataUnifOccurAdd v (1 ( susps )) s t o r e E q u a t i o n ( Susp p v ) ( Equation f s terms ) solveLoop case of Nothing Just let do in if null then else do length 382 383 384 385 386 387 388 389 390 391 392 393 394 Monad => addOccurences : : ( m) Term s e t perm > UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ( ) addOccurences ( Nominal _ t ) = addOccurences t addOccurences ( Pair t1 t2 ) = addOccurences t1 addOccurences t2 addOccurences ( Leaf ( Var v ) ) = nominalDataUnifOccurAdd v 1 addOccurences _ = () do return 395 396 397 398 399 400 401 402 403 404 405 406 407 408 Monad => unifyGen : : ( m) Term s e t perm > Term s e t perm > SContT ( S t a c k C a l l s t a c k ( Equation s e t perm ) ) ( SContT ( OutputCall ( OutputSolution s e t perm ) ) ( UFStoreUnif s e t perm s t o r e ( SContT ( PermCall perm ) ( SContT ( S e t C a l l set ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) ) ) Char 226 A.8 Unication 409 () 410 411 unifyGen s t = do 412 413 414 415 clv ' p2 $ u f S t o r e I n i t U n i f clv ' p2 $ addOccurences s clv ' p2 $ addOccurences t stackPush ( Equation SetEmpty [ s , t ] ) solveLoop 416 417 418 419 420 421 422 unifyP : : Term ( Set . Set Atm) (Map . Map Atm Atm) > Term ( Set . Set Atm) (Map . Map Atm Atm) > [ ] [ OutputSolution ( Set . Set Atm) (Map . Map Atm Atm ) ] Either Char 423 424 425 426 427 428 429 430 431 432 unifyP s t = r u n I d e n t i t y $ runErrorCallErrorT $ runSetPersistent $ runPermPersistent $ runUFStoreUnifP $ (_, s o l ) < runOutputP $ runStackStateT $ unifyGen s t sol do return 227 A.9 Environment Layer A.9 Environment Layer 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 ExistentialQuantification 7 8 9 10 11 12 13 14 module Env import import Call Naming import import Control . Control . # } where Monad Monad . S t a t e 15 16 17 18 19 20 21 22 23 data OpenClose = Open deriving ( Eq , Ord ) data S i d e = S L e f t | deriving ( Eq , Ord ) data 24 | Close SRight EnvAction s e t perm = EnvCompose S i d e ( Perm s e t perm ) | EnvSetFresh Atm Bool 25 26 27 28 29 30 31 32 33 34 35 data EnvCall s e t perm (m : : > ) r EnvPerm | EnvImage Atm | EnvImageInv Atm | EnvIsFresh Atm | EnvSet | EnvLocal OpenClose | EnvAction ( EnvAction s e t perm ) = ( Perm s e t perm (Atm (Atm ( ( Set s e t (() (() 36 37 38 envPerm = c a l l T $ EnvPerm 39 228 Bool > > > > > > > r) r) r) r) r) r) r) A.9 Environment Layer 40 envSet = c a l l T $ EnvSet 41 42 envImage a = c a l l T $ EnvImage a envImageInv a = c a l l T $ EnvImageInv a envIsFresh a = c a l l T $ EnvIsFresh a envComposeLeft perm = c a l l T $ EnvAction 43 44 45 46 47 48 49 50 51 ( EnvCompose SLeft perm ) 52 53 envComposeRight perm = c a l l T $ EnvAction ( EnvCompose SRight perm ) envSetFresh a v ( EnvSetFresh a v ) envLocal l v m = do 54 55 56 57 58 = c a l l T $ EnvAction 59 60 61 62 63 l v $ c a l l T $ EnvLocal Open r < m l v $ c a l l T $ EnvLocal Close r return 64 65 66 67 68 69 70 THE INTERPRETATION FUNCTION type StateEnv s e t perm = ( ( Perm s e t perm ) , Set s e t , [ [ EnvAction s e t perm ] ] ) 71 72 73 74 Monad 75 76 mygets : : ( Monad m) => 77 78 mygets = gets 79 80 => myget : : ( m) StateT ( StateEnv s e t perm ) m ( StateEnv s e t perm ) myget = get myput : : ( ( StateEnv s e t perm > a ) > StateT ( StateEnv s e t perm ) m a Monad m) => StateEnv s e t perm 229 A.9 Environment Layer > StateT ( StateEnv s e t perm ) m ( ) 81 82 myput = put 83 84 mymodify : : ( Monad m) => ( StateEnv s e t perm > StateEnv s e t perm ) > StateT ( StateEnv s e t perm ) m ( ) 85 86 mymodify = modify 87 88 89 liftStateEnvT : : ( Monad m) => 90 91 liftStateEnvT = l i f t m a > StateT ( StateEnv s e t perm ) m a 92 93 94 95 96 97 98 99 getStatePerm getStateSet getStateUndo setStatePerm p setStateSet s setStateUndo u = = = = = = mygets ( \ ( p ,_,_) > mygets ( \ (_, s ,_) > mygets ( \ (_,_, u ) > mymodify ( \ (_, s , u ) mymodify ( \ ( p ,_, u ) mymodify ( \ ( p , s ,_) p) s) u) > (p , s , u )) > (p , s , u )) > (p , s , u )) 100 101 102 modifyStatePerm f = do modifyStateSet f = do modifyStateUndo f = do 103 104 105 106 107 108 109 110 111 112 ( p , s , u ) < myget p' < f p myput ( p ' , s , u ) ( p , s , u ) < myget s' < f s myput ( p , s ' , u ) ( p , s , u ) < myget u' < f u myput ( p , s , u ' ) 113 114 115 116 117 118 Monad => clv ' perm : : ( m) m a > StateT ( StateEnv s e t perm ) ( SContT t h e c l a s s m) a 119 120 clv ' perm = l i f t S t a t e E n v T . clv ' p1 121 230 A.9 Environment Layer 122 123 124 125 126 Monad => clv ' s e t : : ( m) m a > StateT ( StateEnv s e t perm ) ( SContT t h e c l a s s ( SContT t h e c l a s s 1 m) ) a 127 128 clv ' s e t = l i f t S t a t e E n v T . clv ' p2 129 130 131 132 133 134 135 136 137 Monad => runEnvAction : : ( m) EnvAction s e t perm > StateT ( StateEnv s e t perm ) ( SContT t h e c l a s s ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) () 138 139 140 141 142 runEnvAction ( EnvCompose S L e f t p ) = modifyStatePerm (\ perm > clv ' perm $ permCompose MutateRight p perm ) 143 144 145 146 147 148 149 150 151 152 153 runEnvAction ( EnvCompose SRight p ) = modifyStatePerm (\ perm > clv ' perm $ permCompose MutateLeft perm p ) m o d i f y S t a t e S e t (\ s e t > p ' = permInverse p s e t ' < clv ' perm $ setPermute set ' ) do do let return 154 155 156 157 158 runEnvAction ( EnvSetFresh a v) = m o d i f y S t a t e S e t (\ s e t > clv ' s e t $ setSet a v set ) True 159 160 161 162 envOppositeAction : : ( Monad m) => 231 True p ' set A.9 Environment Layer 163 164 165 166 167 168 EnvAction s e t 1 perm1 > StateT ( StateEnv s e t perm ) ( SContT t h e c l a s s ( SContT t h e c l a s s 1 ( SContT ( S e t C a l l s e t ) m) ) ) ( EnvAction s e t 1 perm1 ) 169 170 171 172 envOppositeAction ( EnvCompose s i d e p ' = permInverse p $ EnvCompose s i d e p ' do let return p ) = 173 174 175 176 177 envOppositeAction ( EnvSetFresh a _ set < getStateSet v < clv ' s e t $ s e t I s I n s e t a $ EnvSetFresh a v do ) = return 178 179 180 181 182 183 184 185 186 187 188 189 Monad => interpretEnvT : : ( m) EnvCall s e t perm t ( SContT t h e c l a s s ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) b ) > StateT ( StateEnv s e t perm ) ( SContT t h e c l a s s ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) m) ) ) b 190 191 192 193 interpretEnvT ( EnvPerm k) = perm < getStatePerm l i f t S t a t e E n v T $ k perm do 194 195 196 197 interpretEnvT ( EnvSet set < getStateSet liftStateEnvT $ k set do k) = 198 199 200 201 202 interpretEnvT ( EnvImage a k) = perm < getStatePerm a' < clv ' perm $ permImage liftStateEnvT $ k a ' do 203 232 perm a A.9 Environment Layer 204 205 206 207 interpretEnvT ( EnvImageInv a k ) = perm < getStatePerm a' < clv ' perm $ permImageInv liftStateEnvT $ k a ' do perm a 208 209 210 211 212 interpretEnvT ( EnvIsFresh a k ) = set < getStateSet v < clv ' s e t $ s e t I s I n liftStateEnvT $ k v do set a 213 214 215 216 interpretEnvT ( EnvLocal Open k ) = modifyStateUndo (\ l > liftStateEnvT $ k () do return ( [ ] : l )) 217 218 219 220 221 222 223 224 225 interpretEnvT ( EnvLocal Close k ) = undo < getStateUndo (x , l ) < undo [] > "Undo l i s t e r r o r " (x : l ) > $ (x , l ) setStateUndo l forM x runEnvAction liftStateEnvT $ k () do case of error return 226 227 228 229 230 231 232 233 234 235 interpretEnvT ( EnvAction a c t k ) = modifyStateUndo (\ l > l [] > "Undo l i s t e r r o r " (x : l2 ) > act ' < envOppositeAction a c t $ ( act ' : x ) : l 2 ) runEnvAction a c t liftStateEnvT $ k () do case of error do return 236 237 238 239 240 241 242 243 244 Monad m) => runEnv : : ( SContT ( SContT ( SContT ( SContT > SContT ( SContT ( EnvCall s e t perm ) theclass ( PermCall perm ) ( S e t C a l l s e t ) m) ) ) a theclass ( PermCall perm ) 233 A.9 Environment Layer 245 ( SContT ( S e t C a l l s e t ) m) ) a 246 247 248 249 runEnv c = do v < runStateT ( i n s t a n c i a t e T interpretEnvT c ) ( PermId , SetEmpty , [ ] ) $ v return fst 234 A.10 -equivalence and Matching Algorithms A.10 -equivalence and Matching Algorithms 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 ExistentialQuantification 7 8 9 10 11 12 13 14 15 16 17 18 19 20 module Matching import import import import import import import Base Naming Call Term Env Error Store import import import Control . Control . Control . # } where Monad Monad . I d e n t i t y Monad . S t a t e 21 22 23 24 import qualified import qualified Data . Map as Map Data . Set as Set 25 26 27 28 29 30 31 32 33 34 35 runBasePersistent : : SContT ( PermCall (Map . Map Atm Atm) ) ( SContT ( S e t C a l l ( Set . Set Atm) ) ( SContT ( E r r o r C a l l e ) I d e n t i t y ) ) a > e a runBasePersistent = runIdentity . runErrorCallErrorT . runSetPersistent . runPermPersistent Either 36 37 38 Freshness Context Calls 39 235 A.10 -equivalence and Matching Algorithms 40 41 42 43 44 data FreshContextCall s e t (m : : > ) r = F r e s h C o n s t r a i n t Var ( Set s e t ) ( ( ) | FreshContextRule Var ( Set s e t | FreshToList ( [ ( Var , Set s e t ) ] > r) > r) > r) 45 46 47 48 49 50 51 Monad 52 53 54 55 56 Monad 59 60 => freshContextRule : : ( m) Var > SContT ( FreshContextCall s e t ) m ( Set s e t ) f r e s h C o n t e x t R u l e v = c a l l T $ FreshContextRule v 57 58 => freshConstraint : : ( m) Var > Set s e t > SContT ( FreshContextCall s e t ) m ( ) freshConstraint v s = callT $ FreshConstraint v s Monad => freshToList : : ( m) SContT ( FreshContextCall s e t ) m [ ( Var , Set s e t ) ] freshToList = callT FreshToList 61 62 63 64 The algorithms 65 66 67 68 69 70 71 72 73 74 Monad m) => checkAtom : : ( Atm > SContT ( SContT ( SContT ( SContT ( SContT Atm 75 76 77 78 79 checkAtom a = ( EnvCall s e t perm ) ( FreshContextCall s e t ) ( PermCall perm ) ( SetCall set ) ( ErrorCall [ ] ) m) ) ) ) Char do f r s < envIsFresh a frs clv ' p4 $ r a i s e E r r o r " F r e s h n e s s e r r o r " envImage a if then else 80 236 A.10 -equivalence and Matching Algorithms 81 82 ENVIRONMENT ELIMINATION PHASE 83 84 85 86 87 88 89 90 91 Monad => unEnvVar : : ( m) Var > SContT ( EnvCall s e t perm ) ( SContT ( FreshContextCall s e t ) m) (Term s e t perm ) unEnvVar v = p < envPerm s < envSet clv ' p1 $ f r e s h C o n s t r a i n t v s $ perm p ( var v ) do return 92 93 94 95 96 CHECK TERM PHASE data StreamCall a b (m : : 97 98 99 Monad > ) r = StreamStep a ( b > r) => streamStep : : ( m) a > SContT ( StreamCall a b ) m b streamStep a = c a l l T $ StreamStep a 100 101 102 103 104 105 106 107 108 109 Monad => checkTerm : : ( m) Term s e t perm > SContT ( StreamCall Var (Term s e t perm ) ) ( SContT ( EnvCall s e t perm ) ( SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) ) ) (Term s e t perm ) Char 110 111 112 113 114 115 116 117 118 119 120 121 checkTerm ( Nominal (NPerm p ) t ) = envLocal clv ' p1 ( clv ' p1 $ envComposeRight p checkTerm t ) checkTerm ( Nominal ( Abs a) t ) = b < clv ' p1 $ envImage a u < envLocal clv ' p1 ( clv ' p1 $ envSetFresh a checkTerm t ) ( Nominal ( Abs b ) u ) checkTerm ( Pair s t ) = s ' < checkTerm s do do do return do 237 False A.10 -equivalence and Matching Algorithms t' < checkTerm t ( Pair s ' t ' ) = a2 < clv ' p1 $ checkAtom a $ atm a2 = streamStep v = t 122 123 124 checkTerm do ( Leaf (Atm a ) ) 125 126 127 checkTerm ( Leaf ( Var v ) ) checkTerm t@ ( Leaf ( Cst c ) ) return return return 128 129 130 131 DECOMPOSITION PHASE 132 133 134 135 136 137 138 139 140 141 Monad => decompose : : ( m) Term s e t perm > Term s e t perm > SContT ( StreamCall ( Var , Term s e t perm ) ( ) ) ( SContT ( EnvCall s e t perm ) ( SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) ) ) () Char 142 143 144 145 146 decompose ( Nominal (NPerm p ) t ) u = envLocal clv ' p1 ( clv ' p1 $ envComposeLeft ( permInverse p ) decompose t u ) do 147 148 149 decompose t envLocal clv ' p1 ( 150 ) 151 do ( Nominal (NPerm p ) u ) = clv ' p1 $ envComposeRight p decompose t u 152 153 154 155 156 157 158 159 160 decompose ( Nominal ( Abs a ) t ) ( Nominal ( Abs b ) u ) = a2 < clv ' p1 $ envImageInv a b2 < clv ' p1 $ envImage b envLocal clv ' p1 ( clv ' p1 $ envSetFresh a2 clv ' p1 $ envSetFresh b clv ' p1 $ envComposeLeft ( permSwapping a b2 ) decompose t u ) do do True False 161 162 decompose ( Pair s t ) ( Pair 238 u v) = A.10 -equivalence and Matching Algorithms 163 do 164 decompose s u decompose t v 165 166 167 168 169 170 171 decompose ( Leaf ( Cst f ) ) ( Leaf ( Cst g )) = ( f /= g ) ( clv ' p5 $ r a i s e E r r o r $ " Error : " ++ ( f) ++ " /= " ++ ( g) ) when show show 172 173 174 175 decompose ( Leaf (Atm a ) ) ( Leaf (Atm b)) = c < clv ' p1 $ checkAtom b ( a /= c ) ( clv ' p5 $ r a i s e E r r o r "Atoms not e q u a l s " ) do when 176 177 178 179 180 181 182 183 decompose ( Leaf ( Var v)) t = s < clv ' p2 $ f r e s h C o n t e x t R u l e v p < clv ' p1 envPerm sp < clv ' p3 $ setPermute ( permInverse p ) s l < clv ' p4 $ s e t T o L i s t sp (\ a > clv ' p1 $ envSetFresh a ) l streamStep ( v , t ) do False mapM True 184 185 186 decompose _ _ = clv ' p5 $ r a i s e E r r o r " I n c o m p a t i b l e c o n s t r u c t o r s " 187 188 189 190 191 192 193 194 195 196 197 Monad Char 198 199 200 201 202 203 => core : : ( m) Term s e t perm > Term s e t perm > SContT ( StreamCall ( Var , Term s e t perm ) ( ) ) ( SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l set ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) ) () core s t = do (_,m) < clv ' p1 $ runEnv $ runStateT ( instanciateT interpDec ( decompose s t ) ) ( () return 239 A.10 -equivalence and Matching Algorithms ) 204 205 206 207 208 209 m where streamCheckTerm u = i n s t a n c i a t e N o T ( \ ( StreamStep v k ) ( checkTerm u ) > unEnvVar v >>= k ) 210 211 212 213 214 i n t e r p D e c ( StreamStep ( v , t ) k ) = u < l i f t S t a t e T $ streamCheckTerm t modify (\m > m >> streamStep ( v , u ) ) liftStateT $ k () do 215 216 217 218 219 ALPHA 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 Monad => alpha : : ( m) Term s e t perm > Term s e t perm > SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ] ) m) ) ) () alpha s t = i n s t a n c i a t e N o T i n t e r p r e t A l p h a ( c o r e s t ) Char where i n t e r p r e t A l p h a ( StreamStep ( v , t ) k ) = Susp p u < clv ' p1 $ termHeadPerm t r < u Leaf ( Var w) > w == v s e t < clv ' p2 $ permSupp p freshConstraint v set clv ' p3 $ r a i s e E r r o r "No s u b t i t u t i o n s i n alpha " _ > clv ' p3 $ r a i s e E r r o r "No s u b t i t u t i o n s i n alpha " k r do case of if then do else 241 242 243 244 match : : ( Monad m) => 240 A.10 -equivalence and Matching Algorithms 245 246 247 248 249 250 251 252 Term s e t perm > Term s e t perm > SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ]) ( SContT ( S t o r e C a l l s t o r e Var ) m) ) ) ) ( Var > (Term s e t perm ) ) Char Maybe 253 254 255 256 257 258 259 260 261 262 263 264 265 match s t = i < clv ' p4 $ s t o r e I n i t runStateCall i $ instanciateT interpretMatch ( core s t ) s t a t e G e t >>= clv ' p5 . storeToFun do do Nothing where i n t e r p r e t M a t c h ( StreamStep ( v , t ) k ) = stateModifyM (\ s t o r e > mu < clv ' p5 $ s t o r e G e t s t o r e v mu > clv ' p5 $ s t o r e P u t s t o r e v ( do do 266 case of Nothing Just Just u > do clv ' p1 $ alpha u t if ( ( t e r m S i z e u ) > ( t e r m S i z e t ) ) then ( clv ' p5 $ s t o r e P u t s t o r e v ( Just else return s t o r e 267 268 269 270 271 ) clv ' p1 $ k ( ) 272 273 274 275 276 277 278 279 280 281 Solving And Checking Freshness 282 283 284 285 t) Monad m) => instanciateFreshContextSolve : : ( SContT ( FreshContextCall s e t ) 241 u)) A.10 -equivalence and Matching Algorithms 286 287 288 289 290 291 292 293 294 ( SContT ( SContT ( SContT ( SContT > SContT ( SContT ( SContT ( SContT ( SContT ( PermCall perm ) ( SetCall set ) ( ErrorCall [ ]) ( S t o r e C a l l s t o r e Var ) m) ) ) ) a ( S t a t e C a l l ( s t o r e Var ( Set s e t ) ) ) ( PermCall perm ) ( SetCall set ) ( ErrorCall [ ]) ( S t o r e C a l l s t o r e Var ) m) ) ) ) a Char Char 295 296 297 298 299 300 301 302 303 304 305 instanciateFreshContextSolve m = instanciateT interpretFreshContextSolve m where interpretFreshContextSolve ( FreshConstraint v s k) = stateModifyM (\ s t o r e > vs < clv ' p4 $ s t o r e G e t s t o r e v s ' < clv ' p2 $ setUnion MutateLeft vs s clv ' p4 $ s t o r e P u t s t o r e v s ' ) clv ' p1 $ k ( ) do do 306 307 308 i n t e r p r e t F r e s h C o n t e x t S o l v e ( FreshContextRule v clv ' p1 $ k SetEmpty k) = 309 310 311 312 i n t e r p r e t F r e s h C o n t e x t S o l v e ( FreshToList l < stateGetsM ( clv ' p4 . s t o r e T o L i s t ) clv ' p1 $ k l do 313 314 315 316 317 318 319 320 321 322 323 324 325 326 Monad => instanciateFreshContextCheck : : ( m) s t o r e Var ( Set s e t ) > SContT ( FreshContextCall s e t ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ]) ( SContT ( S t o r e C a l l s t o r e Var ) m) ) ) ) a > SContT ( S t a t e C a l l ( s t o r e Var ( Set s e t ) ) ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ]) Char Char 242 k) = A.10 -equivalence and Matching Algorithms ( SContT ( S t o r e C a l l s t o r e Var ) m) ) ) ) 327 a 328 329 330 331 332 333 334 335 336 337 instanciateFreshContextCheck storehypothesis m = instanciateT interpretFreshContextCheck m where interpretFreshContextCheck ( FreshConstraint v s k) = store < stateGet vs < clv ' p4 $ s t o r e G e t s t o r e v b < clv ' p2 $ s e t I s S u b S e t s vs ( b ) ( clv ' p3 $ r a i s e E r r o r " C o n s t r a i n t s not i n c o n t e x t " ) clv ' p1 $ k ( ) do when not 338 339 340 341 342 i n t e r p r e t F r e s h C o n t e x t C h e c k ( FreshContextRule v k) = s < clv ' p4 $ s t o r e G e t s t o r e h y p o t h e s i s v clv ' p4 $ s t o r e P u t s t o r e h y p o t h e s i s v SetEmpty clv ' p1 $ k s do 343 344 345 346 i n t e r p r e t F r e s h C o n t e x t C h e c k ( FreshToList l < stateGetsM ( clv ' p4 . s t o r e T o L i s t ) clv ' p1 $ k l do k) = 347 348 349 350 351 Alpha and Matching , Solving and checking 352 353 354 355 alphaSolve s t = instanciateFreshContextSolve $ alpha s t 356 357 358 alphaCheck h y p o t h e s i s s t = i n s t a n c i a t e F r e s h C o n t e x t C h e c k h y p o t h e s i s $ alpha s t 359 360 361 362 matchSolve s t = instanciateFreshContextSolve $ match s t 363 364 365 matchCheck h y p o t h e s i s s t = i n s t a n c i a t e F r e s h C o n t e x t C h e c k h y p o t h e s i s $ match s t 366 367 243 A.10 -equivalence and Matching Algorithms 368 We a s s u m e that the freshness context is already 369 370 371 data 372 373 374 375 376 Rule s e t perm = Rule { r u l e ' c o n t e x t : : [ ( Var , Set s e t ) ] , rule ' l e f t : : Term s e t perm , rule ' right : : Term s e t perm } ( , , , ) deriving Eq Ord Show Read 377 378 379 380 381 382 383 Rename the variable of a rule freshRule : : ( Var > Var ) > Rule s e t perm > Rule s e t perm 384 385 386 387 388 f r e s h R u l e renamevar ( Rule c l r ) = Rule ( ( \ ( v , s ) > ( renamevar v , s ) ) c ) ( substitute ( . var . renamevar ) l ) ( substitute ( . var . renamevar ) r ) map Just Just 389 390 391 392 393 394 395 396 397 398 399 Monad => rewriteNoFresh : : ( m) Rule s e t perm > Term s e t perm > SContT ( S t a t e C a l l ( s t o r e Var ( Set s e t ) ) ) ( SContT ( PermCall perm ) ( SContT ( S e t C a l l s e t ) ( SContT ( E r r o r C a l l [ ]) ( SContT ( S t o r e C a l l s t o r e Var ) m) ) ) ) (Term s e t perm ) Char 400 401 402 403 404 rewriteNoFresh ( Rule c l r ) t = s < clv ' p4 $ s t o r e F r o m L i s t SetEmpty c f < matchCheck s l t $ substitute f r do return 405 406 407 408 244 in the store A.10 -equivalence and Matching Algorithms 409 410 r e w r i t e F r e s h renamevar r u l e t = rewriteNoFresh ( f r e s h R u l e renamevar r u l e ) t 245 A.11 Zipper Layer A.11 Zipper Layer 1 { # LANGUAGE RankNTypes , 2 FlexibleInstances , 3 MultiParamTypeClasses , 4 NoMonomorphismRestriction , 5 KindSignatures , 6 ExistentialQuantification , 7 GeneralizedNewtypeDeriving # } 8 9 10 11 12 13 14 15 16 module Zipper where import import import Base Call Term import Control . Monad . S t a t e 17 18 19 type Hole term = term > term 20 21 22 23 24 25 26 27 f i l l H o l e : : ( Hole term ) fillHole = id type data { 28 29 30 } > term > term Context term = [ Hole term ] Zipper term zip ' term zip ' c o n t e x t = Zipper : : term , : : Context term deriving (Show) 31 32 33 zipMake : : term > Zipper term zipMake t = Zipper t [ ] 34 35 36 37 data Dir d i r = Up | Down d i r deriving ( Eq , Ord , Read , Show) 38 39 246 A.11 Zipper Layer 40 41 42 43 44 Eq => zipMove : : ( dir ) ( term > [ ( d i r , ( term , Hole term ) ) ] ) > Dir d i r > Zipper term > ( Zipper term ) Maybe 45 46 47 zipMove _ Up Nothing ( Zipper _ [] ) = 48 49 50 zipMove _ return Up ( Zipper t ( c : l ) ) = $ Zipper ( f i l l H o l e c t ) l 51 52 53 54 zipMove t d i r s (Down d ) ( Zipper t l (t ' , c) < d ( tdirs t) $ Zipper t ' ( c : l ) do return ) = lookup 55 56 57 58 59 60 zipUpdate : : term > Zipper term > Zipper term zipUpdate u ( Zipper _ l ) = Zipper u l 61 62 63 64 65 66 zipDirs : : ( term > [ ( d i r , ( term , Hole term ) ) ] ) > Zipper term > [ Dir d i r ] 67 68 69 z i p D i r s t d i r s ( Zipper t [ ] ) = z i p D i r s t d i r s ( Zipper t _ ) = Up : ( map map (Down . (Down . 70 71 72 73 First Class Monadic Signature 74 75 76 77 78 79 80 data Z i p C a l l term d i r (m ZipTerm | ZipUpdate term | ZipDirs | ZipMove ( Dir d i r ) : : > ) ( term (() ( [ Dir d i r ] ( Bool 247 r > > > > = r) r) r) r) fst ) fst ) ( tdirs t) ( tdirs t )) A.11 Zipper Layer 81 82 83 84 85 86 zipCallTerml = callT ZipTerm zipCallUpdate t = c a l l T $ ZipUpdate t zipCallDir = callT ZipDirs zipCallMove d = c a l l T $ ZipMove d 87 88 89 90 91 92 93 94 Monad Eq => interpretZip : : ( m, dir ) ( term > [ ( d i r , ( term , Hole term ) ) ] ) > Z i p C a l l term d i r n (m b ) > StateT ( Zipper term ) m b 95 96 97 98 99 100 i n t e r p r e t Z i p t d i r s = aux where aux ( ZipTerm k) = t < gets ' term liftStateT $ k t do zip 101 102 103 104 aux ( ZipUpdate u k ) = modify ( \ ( Zipper _ c ) liftStateT $ k () do > Zipper u c ) 105 106 107 108 aux ( Z i p D i r s k) = zipper < get liftStateT $ k ( zipDirs tdirs zipper ) do 109 110 111 112 aux ( ZipMove d k) = zipper < get r < do case zipMove t d i r s d z i p p e r of Nothing > return False Just z > do put z return True 113 114 115 116 liftStateT $ k r 117 118 119 120 121 Monad m, Eq runZip : : ( ( term => dir ) > [ ( d i r , ( term , Hole term ) ) ] ) 248 A.11 Zipper Layer 122 123 124 > term > SContT ( Z i p C a l l term d i r ) m a >m a 125 126 127 128 runZip t d i r s t m = runStateT ( i n s t a n c i a t e T ( i n t e r p r e t Z i p t d i r s ) m) ( zipMake t ) >>= . return fst 249 Appendix B Objective CAML Code B.1 Quadratic Nominal Unication 1 2 3 type type type 4 5 ( 6 ( atm = i n t cst = int var = i n t Helpers on Arrays ) 7 8 9 10 11 12 13 14 let 17 18 19 20 23 24 x > x) ) 1 arr2 let array ' d i f f a r r l = let a r r 2 = Array . copy ( L i s t . i t e r ( fun ( i , e ) 21 22 fun let permuteArr f a r r = let a r r 2 = Array . copy a r r in ( for i = 0 to ( Array . l e n g t h a r r 2 ) do a r r 2 . ( f i ) < a r r . ( i ) done ; 15 16 mkArr n = Array . i n i t n ( ) let arr2 in arr > arr2 . ( i ) < maxAtoms = 30 250 e) l ; ) B.1 Quadratic Nominal Unication 25 let allAtoms = mkArr maxAtoms 26 27 28 ( 29 ( 30 type perm = Perm of let unPerm ( Perm p ) 31 32 33 34 35 36 37 38 Perms let let let let 39 40 41 44 45 46 47 compose ( Perm p1 ) ( Perm p2 ) = Perm ( Array . map ( x > p1 . ( p2 . ( x ) ) ) allAtoms ) fun swap a b = | i when i = a | i when i = b | i 43 let let int array = p idPerm = Perm allAtoms image ( Perm p ) a = p . ( a ) inverse p = Perm ( permuteArr ( image p ) allAtoms ) let 42 ) ) function > b > a > i swapping a b = Perm ( Array . map ( swap a b ) allAtoms ) swapPermL a b p = compose ( swapping a b ) p 48 49 50 ( 51 ( 52 type f r s S e t = FrsSet of b o o l a r r a y let unFrsSet ( FrsSet f s ) = f s 53 54 55 56 57 let let 58 59 60 61 62 63 64 FrsSet ( let let ) emptyFrsSet = FrsSet ( Array . map ( i s I n F r s S e t ( FrsSet f s ) a = f s . ( a ) permute p fs = p( fs ) fun _ > f a l s e ) allAtoms ) ) permute p ( FrsSet f s ) = FrsSet ( permuteArr ( image p ) f s ) combineFrsSet op ( FrsSet f s 1 ) ( FrsSet f s 2 ) = FrsSet ( Array . map ( i > op f s 1 . ( i ) f s 2 . ( i ) ) allAtoms ) unionFrsSet = combineFrsSet ( ) let fun or 65 251 ) B.1 Quadratic Nominal Unication 66 67 let f r s S e t S e t b l ( FrsSet f s ) = FrsSet ( array ' d i f f f s ( L i s t . map ( 68 69 70 71 72 73 let let fun i > ( i ,b)) l )) frsSetAdd = f r s S e t S e t t r u e frsSetDel = frsSetSet false let dsPerm p1 p2 = FrsSet ( Array . map ( a > ( image p1 a ) <> ( image p2 a ) ) allAtoms ) fun 74 75 76 77 ( 78 ( DAG ) ) 79 80 81 type node = { 82 ( 84 ( 85 87 88 89 ( 90 91 } 92 and 95 96 97 98 99 100 101 and head = | | | | | not Data N om in al mutable Atm Cst Var Abs Pair Perm del_edge = { of of of of of of changed head fathers mutable mutable mutable 86 94 Data mutable 83 93 changed by mutable algorithm the algorithm ) ; ; ) : bool ; : ( ( perm node ) del_edge ) l i s t ; : ( perm node ) o p t i o n ; ( changed fresh_cond atm cst var atm node perm the : head : node l i s t deleted edges pointer data by also by : frsSet the alogorithm ) ) node node node edge_deleted : b o o l } 102 103 104 ( 105 ( Node management ) 106 252 ) B.1 Quadratic Nominal Unication 107 108 109 let is_same_node ( n1 : node ) ( n2 : node ) = n1 == n2 let list_node_iter f = List . i t e r ( function n > 110 ) 111 112 113 114 let let 115 addFrsCond r f r s addFrsAtms r b ( 117 ( 118 let rec Edge management 121 122 unionFrsSet r . fresh_cond f r s frsSetAdd b r . fresh_cond ) reduceHead r p i | ( Perm ( pi2 , r1 ) , _ ) | (_ , Perm ( pi2 , s2 ) ) | (_ , _ ) 120 () 116 119 = r . fresh_cond < = r . fresh_cond < if n . d e l e t e d then else f n s > > > match with = ( r . head , s . head ) reduceHead r1 ( compose ( i n v e r s e p i 2 ) p i ) s reduceHead r ( compose p i p i 2 ) s2 ( r , pi , s ) 123 124 125 126 127 128 129 let add_undirected_edge r p i s = let ( r1 , pi1 , s1 ) = reduceHead r p i s in let p i 1 i n v = i n v e r s e p i 1 and del_egde = { edge_deleted = f a l s e } in ( r1 . edges < ( ( p i 1 , s1 ) , del_egde ) 130 ) 131 s1 . edges < : : r1 . edges ; ( ( p i 1 i n v , r1 ) , del_egde ) : : s1 . edges 132 133 134 let 135 list_edges_iter f = List . iter ( ( ( pi , n ) , d ) function > 136 137 ) 138 139 140 let if n . d e l e t e d or d . edge_deleted then ( ) else f ( ( pi , n ) , d ) delete_edge (_,_, d ) = d . edge_deleted < true 141 142 143 144 ( 145 ( DAG ) 146 147 253 ) ) B.1 Quadratic Nominal Unication 148 149 type 150 151 152 dag = { nonvar variables suspended } : node l i s t ; : node l i s t ; : node l i s t 153 154 155 let reset_node n = ( n . deleted < n . edges < n . pointer < ) 156 157 158 159 160 161 false [] None ; ; let reset_dag d = ( L i s t . i t e r reset_node d . nonvar ; L i s t . i t e r reset_node d . v a r i a b l e s ; L i s t . i t e r reset_node d . suspended ) 162 163 164 165 166 167 168 ( 169 ( 170 let let 171 172 Substitution ) maxVars = 30 sigma = Array . c r e a t e maxVars None 173 174 175 176 177 ( 178 ( Unification Algorithm ) 179 180 181 182 183 184 185 let let match let check_clash r p i s = push s t a c k n = ( s t a c k := n : : ! s t a c k ) pop s t a c k = ! stack | [] > f a i l w i t h " Error : empty s t a c k " | ( n : : s t ) > ( s t a c k := s t ; n ) with 186 187 188 254 ) ) B.1 Quadratic Nominal Unication 189 190 191 192 193 194 195 let ( r2 , pi2 , s2 ) = reduceHead match ( r2 . head , s2 . head ) with | | | | | ( Var _ (_ ( Abs (_,_) ( Pair (_,_) (Atm a , , , , , _ Var _ Abs (_,_) Pair (_,_) Atm b 196 197 198 199 | ( Cst c1 | (_ , Cst c2 , _ r pi s ) ) ) ) ) when a && && ) when c1 ) not not in > () = image p i b ( i s I n F r s S e t r . fresh_cond a ) ( i s I n F r s S e t s . fresh_cond b ) > ( ) = c2 > () > f a i l w i t h "FAIL CLASH" 200 201 202 203 204 205 206 207 208 209 let rec p r o p a g a t e _ u n i f i c a t i o n _ e d g e s r p i let ( r2 , pi2 , s2 ) = reduceHead r p i s in match ( r2 . head , s2 . head ) with | | | | | ( Var _ (_ (Atm _ ( Cst _ ( Pair ( r1 , r2 ) , , , , , _ Var _ Atm _ Cst _ Pair ( s1 , s2 ) ) ) ) ) ) 210 211 212 | ( Abs ( a , r1 ) in , Abs ( b1 , s1 ) ) 214 215 216 | (_ > f a i l w i t h " p r o p a g a t i o n f a i l e d : var " > () > ( add_undirected_edge r1 p i s1 ; add_undirected_edge r2 p i s2 ; ) > b2 = image p i b1 let let 213 217 s = , _ ) p i 2 = compose ( swapping a b2 ) p i ( add_undirected_edge r1 p i 2 s1 ; addFrsAtms r [ b2 ] ) > assert false 218 219 220 221 222 223 let rec match 224 225 226 227 228 with propagate_freshness_perm f c r = r . head | Perm ( p , r1 ) > propagate_freshness_perm ( permute ( i n v e r s e p ) f c ) r1 | _ > addFrsCond r f c let p r o p a g a t e _ f r e s h n e s s r = let rec aux f c r = match r . head with 255 in B.1 Quadratic Nominal Unication | | | | 229 230 231 232 233 234 235 236 237 in Cst _ Atm _ > ( ) Var x > p r i n t _ s t r i n g " r . fresh_cond # x" Pair ( r1 , r2 ) > ( propagate_freshness_perm f c r1 ; propagate_freshness_perm f c r2 ) | Abs ( a , r1 ) > propagate_freshness_perm ( f r s S e t D e l [ a ] f c ) r1 | Perm ( p , r1 ) > aux ( permute ( i n v e r s e p ) f c ) r1 aux r . fresh_cond r 238 239 240 241 let rec do_fathers s = list_node_iter ( fun t > match t . head | Perm (_,_) | _ 242 243 ) s . fathers 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 and f i n i s h let s t a c k with > do_fathers t > finish t r = = ref [ ] ( push s t a c k ( idPerm , r ) ; ! s t a c k <> [ ] ( pi , s ) = pop s t a c k (( s . pointer | Some ( pi2 , r2 ) when is_same_node r2 r > ( ) | Some _ > f a i l w i t h "FAIL LOOP" | None > s . p o i n t e r < Some ( ( i n v e r s e p i ) , r ) ) ; do_fathers s ; check_clash r p i s ; ( s . head | Var x > sigma . ( x ) < Some ( ( i n v e r s e p i ) , r ) | _ > propagate_unification_edges r pi s ) ; list_edges_iter ( (e , d) > push s t a c k e ; d . edge_deleted < t r u e ) s . edges ; is_same_node r s addFrsCond r ( dsPerm idPerm p i ) ( addFrsCond r ( permute ( i n v e r s e p i ) s . fresh_cond ) ; while let match match in do with in with function if then else 256 B.1 Quadratic Nominal Unication s . deleted < 270 true ) ) ; propagate_freshness r ; r . deleted < true 271 272 273 274 275 ) 276 done 277 278 279 280 281 ( 282 ( Main function ) 283 284 285 286 287 288 289 290 291 ( let a dag where u = v ) u n i f i y dag s t = ( reset_dag dag ; add_undirected_edge s idPerm t ; l i s t _ n o d e _ i t e r f i n i s h dag . nonvar ; l i s t _ n o d e _ i t e r f i n i s h dag . v a r i a b l e s ) 257 )