EXTRACTING COST RECURRENCES FROM SEQUENTIAL AND PARALLEL FUNCTIONAL PROGRAMS

Similar documents
Notes from Yesterday s Discussion. Big Picture. CIS 500 Software Foundations Fall November 1. Some lessons.

Chapter 3 Deterministic planning

The L Machines are very high-level, in two senses:

Notes on Inductive Sets and Induction

CS 4110 Programming Languages & Logics. Lecture 16 Programming in the λ-calculus

/633 Introduction to Algorithms Lecturer: Michael Dinitz Topic: Dynamic Programming II Date: 10/12/17

MA008/MIIZ01 Design and Analysis of Algorithms Lecture Notes 3

Depending on equations

Lecture Notes on Inductive Definitions

Bicubical Directed Type Theory

Supplementary Notes on Inductive Definitions

Automated Reasoning Lecture 17: Inductive Proof (in Isabelle)

Analysis of Algorithms I: Asymptotic Notation, Induction, and MergeSort

INTEROPERATION FOR LAZY AND EAGER EVALUATION. A Thesis. Presented to. the Faculty of California Polytechnic State University.

COMPUTER SCIENCE TRIPOS

Lecture Notes on Inductive Definitions

Mechanizing Metatheory in a Logical Framework

Safety Analysis versus Type Inference

Isomorphism of Finitary Inductive Types

Programming Languages and Types

Subtyping and Intersection Types Revisited

1 Introduction. 2 Recap The Typed λ-calculus λ. 3 Simple Data Structures

data structures and algorithms lecture 2

Beyond First-Order Logic

Types and Programming Languages (15-814), Fall 2018 Assignment 4: Data Representation (Sample Solutions)

The syntactic guard condition of Coq

Minimally Strict Polymorphic Functions

Normalization by Evaluation

Finite Automata Theory and Formal Languages TMV027/DIT321 LP Recap: Logic, Sets, Relations, Functions

Static Program Analysis

CHAPTER 5. Interactive Turing Machines

Wolfgang Jeltsch. Seminar talk at the Institute of Cybernetics Tallinn, Estonia

On the Semantics of Parsing Actions

Decision procedure for Default Logic

Trustworthy, Useful Languages for. Probabilistic Modeling and Inference

CS522 - Programming Language Semantics

A strongly polynomial algorithm for linear systems having a binary solution

7 RC Simulates RA. Lemma: For every RA expression E(A 1... A k ) there exists a DRC formula F with F V (F ) = {A 1,..., A k } and

On improving matchings in trees, via bounded-length augmentations 1

Programming Language Concepts, CS2104 Lecture 3

A Denotational Approach to Measuring Complexity in Functional Programs

Enhancing Active Automata Learning by a User Log Based Metric

CS632 Notes on Relational Query Languages I

About the relationship between formal logic and complexity classes

Programming with classical quantum datatypes

Most General Unifiers in Generalized Nominal Unification

Programming Languages Fall 2013

Introduction. An Introduction to Algorithms and Data Structures

Model Theory MARIA MANZANO. University of Salamanca, Spain. Translated by RUY J. G. B. DE QUEIROZ

Undecidable Problems. Z. Sawa (TU Ostrava) Introd. to Theoretical Computer Science May 12, / 65

Injectivity of Composite Functions

Complexity Theory Part I

Laver Tables A Direct Approach

Warm-Up Problem. Is the following true or false? 1/35

On Lists and Other Abstract Data Types in the Calculus of Constructions

Extending the Lambda Calculus: An Eager Functional Language

Step-indexed models of call-by-name: a tutorial example

Discrete Mathematics Review

First-Order Logic. 1 Syntax. Domain of Discourse. FO Vocabulary. Terms

Safety Analysis versus Type Inference for Partial Types

Introduction to Divide and Conquer

The Locally Nameless Representation

Nested Epistemic Logic Programs

On the Complexity of the Reflected Logic of Proofs

CS243, Logic and Computation Nondeterministic finite automata

Intelligent Agents. Formal Characteristics of Planning. Ute Schmid. Cognitive Systems, Applied Computer Science, Bamberg University

Type Systems Winter Semester 2006

A Lower Bound for the Size of Syntactically Multilinear Arithmetic Circuits

Element x is R-minimal in X if y X. R(y, x).

Lecture 3: Semantics of Propositional Logic

Peano Arithmetic. CSC 438F/2404F Notes (S. Cook) Fall, Goals Now

Combined Satisfiability Modulo Parametric Theories

Lecture 11: Measuring the Complexity of Proofs

P, NP, NP-Complete, and NPhard

CS411 Notes 3 Induction and Recursion

Divide and Conquer. CSE21 Winter 2017, Day 9 (B00), Day 6 (A00) January 30,

Abstracting real-valued parameters in parameterised boolean equation systems

Event Operators: Formalization, Algorithms, and Implementation Using Interval- Based Semantics

Foundations of Artificial Intelligence

Foundations of Artificial Intelligence

Models of Computation,

Handbook of Logic and Proof Techniques for Computer Science

Automata Theory and Formal Grammars: Lecture 1

Consequence Relations and Natural Deduction

DRAFT. Algebraic computation models. Chapter 14

First-order resolution for CTL

17.1 Correctness of First-Order Tableaux

Lecture 2: Syntax. January 24, 2018

Limitations of OCAML records

Midterm Exam Types and Programming Languages Frank Pfenning. October 18, 2018

Denotational Semantics

Restricted truth predicates in first-order logic

INDUCTIVE DEFINITION

Robust Network Codes for Unicast Connections: A Case Study

Lecture Notes on Heyting Arithmetic

High-Level Small-Step Operational Semantics for Transactions (Technical Companion)

Asymptotic Algorithm Analysis & Sorting

Propositional and Predicate Logic - IV

Recent Developments in and Around Coaglgebraic Logics

Lambda Calculus! Gunnar Gotshalks! LC-1

Transcription:

Wesleyan University EXTRACTING COST RECURRENCES FROM SEQUENTIAL AND PARALLEL FUNCTIONAL PROGRAMS By Justin Raymond Faculty Advisor: Norman Danner A Dissertation submitted to the Faculty of Wesleyan University in partial fulfillment of the requirements for the degree of Master of Arts Middletown, Connecticut May 2016

Acknowledgements Thank you to my adviser Norman Danner for having the patience to put up with me the past year. Without him, this thesis would not have made it past the title page. He made time to meet with me twice a week for two semesters, despite having another thesis advisee and teaching an additional course. Thanks also to Jim Lipton, Dan Licata, and Danny Krizanc who, along with Norman Danner, taught me everything I know about Computer Science. Thank you to my readers: Norman Danner, Dan Licata, and Saleh Aliyari, for reading and giving feedback on this thesis. ii

Abstract Complexity analysis aims to predict the resources, most often time and space, which a program requires. We build on previous work by Danner et al. [2013] and Danner et al. [2015], which formalizes the extraction of recurrences for evaluation cost from higher order functional programs. Source language programs are translated into a complexity language. The translation of a program is a pair of a cost, a bound on the cost of evaluating the program to a value, and a potential, the cost of future use of the value. We use the formalization to analyze the time complexity of higher order functional programs. We also demonstrate the flexibility of the method by extending it to parallel cost semantics. In parallel cost semantics, costs are cost graphs, which express dependencies between subcomputations in the program. We prove by logical relations that the extracted recurrences are an upper bound on the evaluation cost of the original program. We also give examples of the analysis of higher order functional programs under the parallel evaluation semantics. We also prove the recurrence for the potential of a program does not depend on the cost of the program. iii

Contents Chapter 1. Introduction 1 1. Complexity Analysis 1 2. Previous Work 3 3. Contribution 5 Chapter 2. Higher Order Complexity Analysis 7 1. Source Language 7 2. Complexity Language 12 3. Denotational Semantics 15 Chapter 3. Sequential Recurrence Extraction Examples 18 1. Fast Reverse 18 2. Reverse 36 3. Parametric Insertion Sort 45 4. Sequential List Map 59 5. Sequential Tree Map 64 Chapter 4. Parallel Functional Program Analysis 68 1. Work and span 68 2. Bounding Relation 71 3. Parallel List Map 73 4. Parallel Tree Map 79 Chapter 5. Mutual Recurrence 85 1. Pure Potential Translation 85 2. Logical Relation 86 iv

3. Proof 87 Chapter 6. Conclusions and Future Work 94 1. Future Work 95 Bibliography 96 v

CHAPTER 1 Introduction 1. Complexity Analysis The efficiency of programs is categorized by how the resource usage of a program increases with the input size in the limit. This is often called the asymptotic efficiency or complexity of a program. Asymptotic efficiency abstracts away the details of efficiency, allowing programs to be compared without knowledge of specific hardware architecture or the size and shape of the programs input (Cormen et al. [2001]). However, traditional complexity analysis is first-order; the asymptotic efficiency of a program is only expressed in terms of its input. Consider the following function. l e t rec map f xs = match xs with [ ] > [ ] ( x : : xs ) > f x : : map f xs The function map applies a function to every element in a list. Traditional analysis assumes the cost of applying its first argument is constant. Traditional complexity analysis proceeds as follows. First we write a recurrence for the cost. T (n) = c + T (n 1) The variable n is the length of the list and the constant c is the cost of applying the function f to an element of the list and then applying the cons function ::. The result is the asymptotic efficiency of map is linear in the length of the list. The are two problems with this approach. The first is that the analysis assumes the cost of applying the function f to each element in the list has a constant cost. If 1

1. INTRODUCTION 2 the cost function has a constant cost, such as fixed width integer addition, then this first-order analysis is sufficient. The cost of mapping a constant cost function over a list will increase linearly in the size of the list. However, first-order complexity analysis will not accurately describe the cost of mapping a nontrivial function over a list. The cost of mapping a quadratic time function such as insertion sort over a list of lists depends not only on the length of the list, but also on the length of the sublists. A more accurate prediction of the cost of this function can be obtained by taking into account the cost of the mapped function. The second is that there is no formal connection between the implementation of map and the recurrence T (n). The consequence is extraction of the correct recurrence relies on the absence of human error, to which the author of this thesis can attest the difficulty of doing. A formalization of the connection between the source program and the recurrence would prevent this. The translation of the source program to the recurrence could be done by application of a series of rules. A mechanical process such as this could easily be automated. For an example such map, it is simple enough to change our analysis to reflect that applying the function f does not have constant cost c, but instead has cost f c (x), where x is some notion of size of the elements of the list. If the elements of the list are fixed width integers, then all x are equivalent, and f c (x) is constant. However, if the elements of the list are strings or lists than f c (x) depends on the sizes of the elements of the list. If we only interpret the size of lists to be their lengths, we have no information about the size of the elements we apply f to. So we interpret lists as a pair of their largest element and their length. The recurrence for the cost of map becomes T (n, i) = (f c (i) + c)n, where i is the size of the largest element of the list. Our analysis of the cost of map is now parameterized by the cost applying f to the elements of the list. However, this does not allow us to analyze the composition of functions. For example, to analyze the cost of g f, we need to have a notion of the size of the result of f, as well as the cost. We need to have a notion of the size of the result of f in order to analyze the cost of applying g to the result of applying f to some value.

1. INTRODUCTION 3 The term we will use for this notion of the size is potential. Potential represents the cost of future use of an expression. As mentioned above, potential is necessary to compose the analysis of functions. Consider this implementation of fromlist which creates a set from a list of items. fromlist xs = f o l d r i n s e r t empty xs The insert function takes an element and a set and adds the element to the set. empty is the empty set. The insert function is applied to increasing sized sets each step of the fold. To correctly analyze fromlist, our analysis of insert must include both a cost of inserting an element into a set, and a potential with which we can use to analyze the cost of the next application of insert by foldr. 2. Previous Work As we have just seen, traditional complexity analysis does not have a formal connection between the programs and the extracted recurrences. Traditional complexity analysis is also not compositional. Danner and Royer [2007], building on the work of others, introduced the idea that the complexity of an expression consists of a cost, representing an upper bound on the time it takes to evaluate the expression, and a potential, representing the cost of future uses of the expression. They developed ATR, a variant of System T with callby-value semantics and type system which restricted the evaluation time of programs. ATR programs are limited to second order programs, which take natural numbers and first-order programs as arguments. Programs written in ATR are guaranteed to run in polynomial time. ATR is at least powerful enough to compute each type-2 basic feasible functionals characterized by Kapron and Cook [1996]. In order to limit the size of higher-order programs, the type system of ATR limits both the size of the values of expressions and the time required to evaluate an expression. A type-2 function takes a function as an argument. In order to restrict the cost of evaluating the type-2 function, we need to restrict the size of the output of the argument. Danner and Royer [2009]

1. INTRODUCTION 4 extended the ATR formalism with more forms of recursion, in particular those required by insertion sort and selection sort. Instead of implicitly restricting the complexity of programs as in ATR, the work of Danner et al. [2013] focused on constructing recurrences that bound the complexity of a program. The programs are written in a version of System T with structural list recursion, referred to as the source language. Programs in the source language are limited to integers and integer lists, with structural recursion on lists the only recursion construct. A translation function maps source language programs to recurrences in a complexity language. It is not possible for the user to define their own datatypes. The result of the translation of a source language program is a complexity. The complexity consists of a cost and a potential. The cost is a bound on the execution cost of the program and the potential is the size of the result of evaluating the program. To understand why the complexity must have both a cost and a potential, consider the higher-order program foldr over a list. f o l d r f z xs = case xs of [ ] > z x : xs > f x ( f o l d f z xs ) To analyze the cost of applying f at each step of the fold, we must have a bound on the size of x and fold f z xs. In other words, the analysis must produce a bound on the cost of the recursive call and a bound on the size of the recursive call. Costs and potentials also enable compositional analysis. Consider the composition of two functions, map sort and permutations. map s o r t permutations To analyze the cost of map sort, we must have a bound on its input size. The size of the input to map sort is the size of the output of permutations. So our analysis of permutations must produce a cost bound and a size bound, which we can use to produce a cost bound for map sort.

1. INTRODUCTION 5 Danner et al. [2015] built on this work to formalize the extraction of recurrences from a higher-order functional language with structural recursion on arbitrary inductive data types. Programs are written in a higher order functional language, referred to as the source language. The programs are translated into a complexity language, which is essentially a language for recurrences. The result of the translation of an expression is a pair of a cost and a potential. The cost is a bound on the steps required to evaluate the expression to a value, the potential is a size which represents cost of future use of the value. A bounding relation is used to prove the translation and denotational semantics of the complexity language give an upper bound on the operational cost of running the source program. The paper also presents a syntactic bounding theorem, where the abstraction of values to sizes done syntactically instead of semantically. Arbitrary inductive data types are handled semantically using programmer specified sizes of data types. Sizes must be programmer specified because the structure of a data type does not always determine the interpretation of the size of a data type. There also exist different reasonable interpretations of size, and some may be preferable to others depending on what is being analyzed. This thesis comes in three parts. 3. Contribution Chapter 2 contains a catalog of examples of the extraction of recurrences from functional programs using the approach given by Danner et al. [2015]. These examples illustrate how to apply the method to nontrivial programs. They also serve to demonstrate common techniques for solving the extracted recurrences. The examples include reversing a list in quadratic time, reversing a list in linear time, insertion sort, parametric insertion sort, list map, and tree map. Linear time list reversal is an example of higher-order analysis. Slow list reversal is an example of a quadratic time function. Parametric insertion sort demonstrates the compositionality of the method as well as its ability to handle higher-order programs. We do list map and tree map to compare with the parallel list map and tree map in Chapter 3.

1. INTRODUCTION 6 Chapter 3 extends the analysis to parallel programs. The source language syntax remains unchanged, but the operational semantics change to allow binary fork-join parallelism, also called nested parallelism. The semantics are parallel in that the subexpressions to tuples and function application may be evaluated in parallel. Parallelism is nested because the subexpressions themselves may have subexpressions which may also be evaluated in parallel. We change costs from from natural numbers to the cost graphs described in Harper [2012]. A cost graph represents the dependencies between subcomputations in a program. The nodes of the graph are subcomputations of the program and an edge between two nodes indicates the result of one computation is an input to the other. The cost graph can be used to determine an optimal strategy for scheduling the computation on multiple processors. The cost graph has two properties that we are interested in, work and span. The work is the total steps required to run the program, which corresponds to the steps a single processor must execute to run the program. The span is the critical path; the longest number of steps from the start to the end of the cost graph. Chapter 4 defines a pure potential translation. The pure potential translation is a stripped down version of the complexity translation which drops all notions of cost. We prove by logical relations that for all well-typed source language terms, the potential of the translation of the program into the complexity language is related to the pure potential translation of the program. The result of this is that the potential of the complexity of the translation does not depend on the cost. This justifies the extraction of the potential recurrence from the complexity language recurrence. This is useful because it is often easier to solve the cost and potential recurrences independently than it is to solve the initial recurrence. We are also sometimes only interested in just the potential or just the cost of a recurrence.

CHAPTER 2 Higher Order Complexity Analysis Programs are written in the source language. Then the program is translated to a complexity language. The semantic interpretation of the complexity language program may be used to analyse the complexity of the original program. 1. Source Language The source language is the simply typed lambda calculus with Unit, products, suspensions, programmer-defined inductive datatypes and a recursion construct. Valid signatures, types, and constructor arguments are given in Figure 2. The types, expressions, and typing judgments of the source language are given in Figure 1. Evaluation is call-by-value and the rules for evaluation are given in Figure 3. We use big-step operational cost semantics. Small-step operational semantics provide an indirect notion of number of steps required to evaluate a program to a value. Big-step operational semantics do not allow this since intermediate evaluation steps are suppressed. Big-step operational semantics introduce a notion of cost by using evaluation judgements of the form e n v. For example the evaluation judgement for a tuple is e 0 n 0 v 0 e 1 n 1 v 1 e 0, e 1 n 0+n 1 v 0, v 1 This judgement reads if e 0 evaluates to a value v 0 in n 0 steps and e 1 evaluates to a value v 1 in n 1 steps, then the tuple e 0, v 0 evaluates to the value v 0, v 1 in n 0 + n 1 steps. A program using datatypes must have a top-level signature ψ consisting of datatype declarations of the form datatypeδ = C δ 0ofφ C0 [δ]... C δ n 1ofφ Cn 1 [δ] 7

2. HIGHER ORDER COMPLEXITY ANALYSIS 8 Types τ ::= unit τ τ τ τ susp τ δ φ ::= t τ φ φ τ φ datatype δ = C δ 0ofφ C0 [δ]... C δ n 1ofφ Cn 1 [δ] Expressions v ::= x v, v λx.e delay(e) C v e ::= x e, e split(e, x.x.e) λx.e e e delay(e) force(e) C δ e rec δ (e, C x.e C ) map φ (x.v, v) let(e, x.e) n ::= 0 1 n + n Typing Judgments γ, x : σ x : σ γ : unit γ e 0 : τ 0 γ e 1 : τ 1 γ e 0 : τ 0 τ 1 γ, x 0 : τ 0, x 1 : τ 1 e 1 : τ e 0, e 1 : τ 0 τ 1 γ split(e 0, x 0.x 1.e 1 ) : τ γ, x : σ e : τ γ λx.e : σ τ γ e : τ γ delay(e) : susp τ γ e 0 : σ τ γ e 1 : σ γ e 0 e 1 : τ γ e : susp τ γ force(e) : τ γ e : φ C [δ] γ C δ e : δ γ e : δ C.γ, x : φ C [δ susp τ] e C : τ γ rec δ (e, C x.e C ) : τ γ, x : τ 0 v 1 : τ 1 γ v 0 : φ[τ 0 ] γ e 0 : σ γ, x : σ e 1 : τ map φ (x.v 1, v 0 ) : φ[τ 1 ] let(e 0, x.e 1 ) : τ Figure 1: Source language syntax and types Each datatype may only refer to datatypes declared earlier in the signature. This prevents general recursive datatypes. The argument to each constructor is given by a strictly positive functor φ, which is one of t, τ, φ 0 φ 1, and τ φ. The identity functor t represents recursive occurrence of the datatype. The constant functor τ represents a non-recursive type. The product functor φ 0 φ 1 represents a pair of arguments.

2. HIGHER ORDER COMPLEXITY ANALYSIS 9 Signatures: ψ sig sig δ / C(ψ φ C ok) ψ, datatype δ = C of φ C [δ] sig Types : ψ τ type ψ unit type ψ τ 0 type ψ τ 1 type ψ τ 0 τ 1 type ψ τ 0 type ψ τ 1 type ψ τ 0 τ 1 type ψ τ type ψ susp τ type δ ψ ψ δ type ψ t Constructor arguments: ψ φ ok ok ψ φ 0 ok ψ φ 1 ok ψ φ 0 φ 1 ok ψ τ type ψ τ ok ψ τ type ψ φ ok ψ τ φ ok Figure 2: Source language valid signatures, types, and constructor arguments e 0 n 0 v 0 e 1 n 1 v 1 e 0, e 1 n 0+n 1 v 0, v 1 e 0 n 0 v 0, v 1 e 1 [v 0 /x 0, v 1 /x 1 ] n 1 v split(e 0, x 0.x 1.e 1 ) n 0+n 1 v e 0 n 0 λx.e 0 e 1 n 1 v 1 e 0 [v 1/x] n v e 0 e 1 1+n 0+n 1 +n v e n 0 delay(e 0 ) e 0 n 1 v force(e) n 0+n 1 v delay(e) 0 delay(e) e n v Ce n Cv e n 0 Cv 0 map φ C (y. y, delay(rec(y, C x.e C )), v 0 ) n 1 v 1 e C [v 1 /x] n 2 v rec(e, C x.e C ) 1+n 0+n 1 +n 2 v map t (x.v, v 0 ) 0 v[v 0 /x] map τ (x.v, v 0 ) 0 v 0 map φ 0 (x.v, v 0 ) n 0 v 0 map φ 1 (x.v, v 1 ) n 1 v 1 map φ 0 φ 1 (x.v, v 0, v 1 ) n 0+n 1 v 0, v 1 e 0 n 0 v 0 e 1 [v 0 /x] n 1 v map τ φ (x.v, λy.e) 0 λy.let(e, z.map φ (x.v, z)) let(e 0, x.e 1 ) n 0+n 1 v Figure 3: Source language operational semantics The constant exponential τ φ represents a function type. The introduction forms

2. HIGHER ORDER COMPLEXITY ANALYSIS 10 for datatypes are the constructors. The elimination form for a datatype is the rec construct. To give the reader a better understanding of the source language, we will implement a small program, explaining the syntax and semantics we need as we go. We define an list datatype in the source language below. datatype list = Nil of unit Cons of int list unit is a singleton type with only one inhabitant, the value, also called unit. The listmap function applies a function to each element in a list. listmap f xs = rec(xs, Nil z.nil, Cons z.cons π 0 z, force(π 1 π 1 z) ) This function uses the rec construct, which is how we do structural recursion on datatypes. γ e : δ C.γ, x : φ C [δ susp τ] e C : τ γ rec δ (e, C x.e C ) : τ The rec is a branch on an expression. The expression is evaluated to a value, and the branch of the rec matching the outermost constructor of the value is taken. Inside the each branch of the rec, the variable x is a value of type φ C [δ susp τ]. A suspension is an unevaluated computation. A suspension has type susp τ where τ is the type of the suspended computation. Suspensions are introduced using the delay(e) operator. Suspensions are eliminated using the force(e) operator, which evaluates the suspended computation. The rec construct makes available all recursive calls. Suspensions are necessary to avoid charging for recursive calls that are not actually used. The operational semantics for rec are e n 0 Cv 0 map φ C (y. y, delay(rec(y, C x.e C )), v 0 ) n 1 v 1 e C [v 1 /x] n 2 v rec(e, C x.e C ) 1+n 0+n 1 +n 2 v map is used to lift functions from σ τ to φ[σ] φ[τ]. To understand the role of map in rec, let us consider the two branches of rec in listmap. Let E = rec(y, Nil Nil, Cons z.cons π 0 z, force(π 1 π 1 z) )

2. HIGHER ORDER COMPLEXITY ANALYSIS 11 The first case, xs is Nil, so according to the operational semantics, e Nil in 0 steps. Next map φnil (y, y, delay(e), ) is evaluated to a value v 1. We substitute v 1 for z in the body of the Nil branch and evaluate the body to a value to get our result. So the map evaluates to. map τ (x.v, v 0 ) 0 v 0 In the second case, the outermost constructor of xs is Cons. Let the argument to this constructor be the tuple x, xs. So the map expression is map φcons (y, y, delay(e), (x, xs ) ) To evaluate the map expression, we use the rule for mapping over a pair. map φ 0 (x.v, v 0 ) n 0 v 0 map φ 1 (x.v, v 1 ) n 1 v 1 map φ 0 φ 1 (x.v, v 0, v 1 ) n 0+n 1 v 0, v 1 We apply the rule for mapping over a pair. map int (y, y, delay(e), x), map φsusp list (y, y, delay(e), xs ) The first map is over a non-recursive argument of a constructor. Recall the rule for evaluating this map. map τ (x.v, v 0 ) 0 v 0 So the first map evaluates to x. The second map is over a recursive argument of a constructor. The rule to evaluate this map is map t (x.v, v 0 ) 0 v[v 0 /x] The result of the map over the second element of the tuple is y, delay(e) [xs /y] = xs, delay(e[xs /y]). So the result of the map over the tuple is x, xs, delay(e[xs /y]). Recall the body of the Cons branch of the rec is Cons fπ 0 z, force(π 1 π 1 z). We have just shown how the map expression results in the term x, xs, delay(e[xs /z]). This is the term z is bound to inside the body of the Cons branch. So π 0 z is the head of the

2. HIGHER ORDER COMPLEXITY ANALYSIS 12 list and π 1 π 1 z is a suspended computation representing the recursive call on the tail of the list. Since it is suspended, we need to use the force function to evaluate it. The let(e 0, x.e 1 ) syntactic construct allows us to do function application in map without charging for cost. It also serves the purpose avoiding recomputation of values. If e 0 is an expensive computation that occurs more than once in e 1, we can use let to compute e 0 and use the result inside e 1 multiple times without paying cost multiple times. 2. Complexity Language The types, expressions, and typing judgments of the complexity language are given in Figure 5. The complexity language is similar to the source language with a few exceptions. Suspensions are no longer present in the complexity language. Recall suspensions served the purpose of avoiding charging costs in unused recursive calls during the translation into the complexity language. Since the complexity language program has already been translated, the complexity language does not need suspensions. Another difference is tuples are deconstructed using projection functions instead of split. In the source language, to add two elements of a tuple together we write λp.split(p, x 0.x 1.x 0 + x 1 ) In the complexity language we write λp.π 0 p + π 1 p The map function is treated as a macro map Φ in the complexity language. The macro is defined by Φ and the definition mirrors the semantics of map in the source language. The definition is given in Figure 4. The translation from the source language to the complexity language is given in Figure 6 and Figure 7. We denote the complexity translation of a source language expression e as e. We refer to complexity language expressions of type C as costs,

2. HIGHER ORDER COMPLEXITY ANALYSIS 13 map t (x.e, E 0 ) = E[E 0 /x] map T (x.e, E 0 ) = E 0 map Φ 0 Φ 1 (x.e, E 0 ) = map Φ 0 (x.e, π 0 E 0 ), map Φ 1 (x.e, φ 1 E 1 ) map T Φ (x.e, E 0 ) = λy.map Φ (x.e, E 0 y) Figure 4: Complexity language map macro Types T ::= C unit T T T T Φ ::= t T Φ Φ T Φ C ::= 0 1 2... datatype = C0 ofφ C0 [ ]... Cn 1ofΦ Cn 1 [ ] Expressions E ::= x 0 1 E + E E, E π 0 E π 1 E λx.e E E C δ E rec (E, C x.e C ) Typing Judgments Γ, x : T x : T Γ 0 : C Γ 1 : C Γ : unit Γ E 0 : C Γ E 1 : C Γ E 0 + E 1 : C Γ E 0 : T 0 Γ E 1 : T 1 Γ E 0, E 1 : T 0 T 1 Γ E : T 0 T 1 Γ π i E : T i Γ, x : T 0 E : T 1 Γ λx.e : T 0 T 1 Γ E 0 : T 0 T 1 Γ E 1 : T 0 Γ E 0 E 1 : T 1 Γ E : Γ E : Φ C [ ] Γ C E : C.Γ, x : Φ C [ T ] E C : T Γ rec (E, C x.e C ) : T Figure 5: Complexity language types, expressions, and typing judgments

2. HIGHER ORDER COMPLEXITY ANALYSIS 14 τ = C τ unit = unit σ τ = σ τ σ τ = σ τ susp τ = τ δ = δ φ = C φ t = t τ = τ φ 0 φ 1 = φ 0 phi 1 τ φ = φ φ ψ = for each δ ψ, δ = C δ 0 of φ C0 [δ],..., C δ n 1 of φ n 1 [δ] Figure 6: Translation from source language to complexity language types. complexity language expressions of type τ as potentials, and complexity language expressions of type C τ as complexities. Examining the translation of source language types to complexity language types in Figure 6, we see that the translation of a source language expression of type τ is C τ. The first element is the cost, a bound on the cost of evaluating the expression, and the second element is the potential, an expression for the size of the value. The potential translation of types unit and δ is the corresponding complexity language types unit

2. HIGHER ORDER COMPLEXITY ANALYSIS 15 x = 0, x = 0, e 0, e 1 = e 0 c + e 1 c, e 0 p, e 1 p split(e 0, x 0.x 1.e 1 ) = e 0 c + c e 1 [π 0 e 0 p /x 0, π 1 e 0 p /x 1 ] λx.e = 0, λx. e e 0 e 1 = (1 + e 0 c + e 1 c ) + c e 0 p e 1 p delay(e) = 0, e force(e) = e c + c e p C δ i e = e c, C δ i e p rec δ (e, C x.e C ) = e c + c rec δ ( e p, C x.1 + c e C ) map φ (x.v 0, v 1 ) = 0, map φ (x. v 0 p, v 1 p ) let(e 0, x.e 1 ) = e 0 c + c e 1 [ e 0 p /x] Figure 7: Translation from source language to complexity language expressions. and δ. The potential translation of a product type is the complexity language type of a product of the potential translations of the components of the product type. 3. Denotational Semantics The recurrences in the complexity language do not look like the recurrences one would expect in complexity analysis. This is because the complexity language recurrences contain as much information about the size of an expression as the source language does. In order to get recognizable recurrences, we must abstract values to sizes by interpreting the complexity language in a denotational semantics. The denotational interpretation of the complexity types are standard. We interpret numbers as elements of Z, tuples of type τ σ as elements of the cross-product of the set of values of type τ and the set of values of type σ, lambda expressions of type τ σ as mathematical functions from the set of values of type τ to the set of values of type σ, and

2. HIGHER ORDER COMPLEXITY ANALYSIS 16 application as mathematical function application. The nonstandard interpretations are those of datatype constructors and rec. Since datatypes are programmer-defined and there are multiple interpretations for a single datatype, the programmer must provide their own interpretation. For example we may decide to interpret list as their length. list = N Semantically, we need to distinguish between the constructors of a datatype, so we also define a semantic value D list. D list is a sum type of the arguments to the list constructors. list has two constructors, Nil, which has argument of type unit, and Cons, which has argument of type int list. D list = + Z N We will write C i for the i th injection into D list. C i takes us from the interpretation of the argument of a constructor to a value of type D list. We also need a function size list which takes us from D list back to list. size is the programmers notion of size for programmer-defined datatypes. In this case, we want the size of a list to be its length. So our size function is defined as follows. size list ( ) = 0 size list (i, n) = 1 + n There is a restriction on the definition of the size function. The size of a value must be strictly greater than the size of any of its substructures of the same type. In the case of list, the restriction means the size of a (1, n) must be strictly greater than the size of (j, n 1). In general, when we interpret a source language with programmer-defined datatypes, for each datatype we must define an interpretation and a function size : D. The interpretation of a datatype with constructor C under the environment ξ is C e ξ = size(c( e ξ))

2. HIGHER ORDER COMPLEXITY ANALYSIS 17 In the list case, if e is Nil, then the interpretation Nil is size list (C 0 ), since the argument to the Nil constructor is. The interpretation of unit is 0. So size list (C 0 = size list (C 0 0). C 0 is the 0 th injection from Φ[list to D list. So size list (C 0 0) = size list ( ). By our definition of size list, size list ( ) = 0. The interpretation of rec is also nonstandard. To interpret rec, we introduce a semantic case function. case δ : D δ Π C ( T ΦC[δ] T τ ) T τ The interpretation of a rec is rec δ (E δ, C x φc[δ τ].ec τ ) ξ = case(z, f C ) size(z) E ξ where for each constructor C, f C (x) = E C ξ{x map Φ C (a.(a, rec(w, C x.e C ) ξ{w a}), x)} Since we cannot predict which branch the rec will take, we must take the maximum over all possible branches to obtain an upper bound. Recall our restriction on the size function that the size of a value must be strictly greater than the size of any of its substructure of the same type. This ensures the recursion used to interpret the rec expressions is well-defined. Continuing with the list example, the interpretation of rec on a list is rec(e 0, Nil E Nil, Cons x.e Cons ) = case(z, f Nil, f Cons ) size(z) E 0 where f Nil ( ) = E Nil f Cons (i, n) = E Cons

CHAPTER 3 Sequential Recurrence Extraction Examples 1. Fast Reverse Fast reverse is an implementation reverse in linear time complexity. A naive implementation of reverse appends the head of the list to recursively reversed tail of the list. Fast reverse instead uses an abstraction to delay the consing. As this is the first example, we will walk through the translation and interpretation in gory detail. The definition of the list datatype holds no surprises. datatype list = Nil of unit Cons of int list The implementation of fast reverse is not obvious. We write a function rev that applies an auxiliary function to an empty list to produce the result. The specification of reverse is rev [x 0,..., x n 1 ] = [x n 1,..., x 0 ]. The specification of the auxiliary function rec(xs,... ) is rec([x 0,..., x n 1 ],... ) [y 0,..., y m 1 ] = [x n 1,..., x 0, y 0,..., y m 1 ]. rev xs = λxs. rec ( xs, Nil λa. a, Cons b. s p l i t (b, x. c. s p l i t ( c, xs. r. λa. f o r c e ( r ) Cons x, a ) ) ) Nil Notice that the implementation of rev would be much cleaner if we where able to pattern match on cases of the rec. Below is rev written with this syntactic sugar. rev = λxs. rec ( xs, Nil λa. a, Cons y, ys, r. λb. f o r c e ( r ) Cons x, b ) Nil Each recursive call creates an abstraction that applies the recursive call on the tail of the list to the list created by consing the head of the list onto the abstraction argument. 18

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 19 The recursive calls builds nested abstractions as deep as the length of the list which is collapsed by application of the outermost abstraction to Nil. Below we show the evaluation of rev applied to a small list of just two elements. rev ( Cons 0, Cons 1, Nil ) rec ( Cons 0, Cons 1, Nil, Nil λa. a Cons b. s p l i t (b, x. c. s p l i t ( c, xs. r. λa. f o r c e ( r ) Cons x, a ) ) ) Nil β (λa0. ( λa1. ( λa2. a2 ) Cons 1, a1 ) Cons 0, a0 ) Nil β (λa1. ( λa2. a2 ) Cons 1, a1 ) Cons 0, Nil β (λa2. a2 ) Cons 1, Cons 0, Nil β Cons 1, Cons 0, Nil This example is especially interesting because traditional complexity analysis will tell us the recursive function which builds the nested functions runs in linear time, but it is not able to tell us the cost of applying the nested functions to value. 1.1. Translation. We will walk through the translation from the source language to the complexity language. rev = λxs.rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r) Cons x,a ))) Nil First we apply the rule for translating an abstraction. The rule is λx.e = 0, λx. e. rev = λxs.rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r)cons x, a ))) Nil = 0, λxs. rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r)cons x, a ))) Nil

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 20 The next translation is an application. The rule for translating an application is e 0 e 1 = (1 + e 0 c + e 1 c ) + c ( e 0 p e 1 p ). In this case, rec(...) is e 0 and Nil is e 1. We translate Nil then rec(...) separately. The translation of a constructor applied to an expression is a tuple of the cost of the translated expression and the corresponding complexity language constructor applied to the potential of the translated expression. Since the expression inside Nil is, and = 0,, we have Nil = 0, c, Nil 0, p = 0, Nil The rule for translating a rec expression is rec(e, C x.e C ) = e c + c rec( e p, C x. e C ) rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a ))) = xs c + c rec( xs p, Nil 1 + c λa.a Cons b.1 + c split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a )) ) = 0, xs c + c rec( 0, xs p, Nil 1 + c λa.a Cons b.1 + c split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a )) ) The term xs is a variable and the rule for translating variables is xs = 0, xs. = rec(xs, Nil 1 + c λa.a Cons b.1 + c split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a )) ) The translation of the Nil branch is simple application of the λx.e = 0, λx. e and the variable translation rule. 1 + c λa.a = 1 + c 0, λa. a = 1, λa. 0, a

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 21 The translation of the Cons branch is a slightly more involved. The rule for translating split is split(e 0, x 0.x 1.e 1 ) = e 0 c + c e 1 [π 0 e 0 p /x 0, π 1 e 0 p /x 1 ] After applying the rule to the Cons branch we get 1 + c split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a )) = 1 + c b c + c split(c, xs.r.λa.force(r) Cons x, a ) [π 0 b p /x, π 1 b p /c] Remember that b is a variable and has type φ Cons [list susp (list list)]. The translation of this type is C φ Cons [list list C list ]. We can say that π 0 b p is the head of the list xs, π 0 π 1 b p is the tail of the list xs, and π 1 π 1 b p is the result of the recursive call. The translation of b is 0, b. 1 + c b c + c split(c, xs.r.λa.force(r) Cons x, a ) [π 0 b p /x, π 1 b p /c] = 1 + c split(c, xs.r.λa.force(r) Cons x, a ) [π 0 b p /x, π 1 b p /c] We apply the rule for split again. = 1 + c ( c c + c λa.force(r) Cons x, a [π 0 c p /xs, π 1 c p /r][π 0 b p /x, π 1 b p /c] c is a variable, so its translation is 0, c. = 1 + c λa.force(r) Cons x, a [π 0 c p /xs, π 1 c p /r][π 0 b p /x, π 1 b p /c] We apply the rule for abstraction. = 1 + c 0, λa. force(r) Cons x, a [π 0 c p /xs, π 1 c p /r][π 0 b p /x, π 1 b p /c] Recall C + c E is a macro for C + E c, E p. We use this to eliminate the + c. We also apply the translation rule for application. = 1, λa.(1 + force(r) c + Cons x, a c ) + c force(r) p Cons x, a p [π 0 c p /xs, π 1 c p /r][π 0 b p /x, π 1 b p /c] We will translate force(r) and Cons x, a individually. First we compose the two substitutions.

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 22 let Θ = [π 0 c p /xs, π 1 c p /r][π 0 b p /x, π 1 b p /c] = [π 0 π 1 b p /xs, π 1 π 1 b p /r, π 0 b p /x] Since b is a variable, the potential of its translation is b. Θ = [π 0 π 1 b/xs, π 1 π 1 b/r, π 0 b/x] In translation of force(r) we apply the rule force(e) = e c + c e p. force(r) Θ = r c Θ + c r p Θ We apply the variable translation rule to r, then apply the substitution Θ. = 0, r c Θ + c 0, r p Θ = rθ = π 1 π 1 b Next we do the translation of Cons x, a. Cons x, a = x, a c, Cons x, a p Notice the translation of x, a appears twice, so we will do this separately. x, a = x c + a c, x p, a p Θ Both x and a are variables, so they have 0 cost. = 0, x, a Θ We apply the substitution Θ. = 0, π 1 b, π 1 π 1 b = 0, π 1 b, π 1 π 1 b We complete the translation of Cons x, a using x, a. Cons x, a = x, a c, Cons x, a p = 0, Cons π 1 b, π 1 π 1 b We use substitute in the translations of force(r) and Cons x, a.

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 23 force(r) has cost (π 1 π 1 b) c and Cons x, a has cost 0. 1, λa.(1 + force(r) c + Cons x, a c ) + c force(r) p Cons x, a p Θ = 1, λa.(1 + (π 1 π 1 b) c ) + c (π 1 π 1 b) p Cons π 1 b, a We can now complete the translation of the rec expression. rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a ))) = rec(xs, Nil 1 + c λa.a Cons b.1 + c split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a )) ) = rec(xs, Nil 1, λa. 0, a Cons b. 1, λa.(1 + (π 1 π 1 b) c ) + c (π 1 π 1 b) p Cons π 1 b, a ) We substitute the translation of rec and Nil into the translation of the application. Let R = rec(xs, Nil 1, λa. 0, a Cons b. 1, λa.(1 + (π 1 π 1 b) c ) + c (π 1 π 1 b) p Cons π 1 b, a ) rec(xs, Nil λa.a, Cons b.split(b, x.c.split(c, xs.r.λa.force(r) Cons x, a ))) Nil Substituting R for the translation of rec and 0, Nil for the translation of Nil. = (1 + R c ) + c R p Nil Recall C + c E = C + E c, E p, so (1 + E c ) + c E p = 1 + c E = 1 + c rec(xs, Nil 1, λa. 0, a Cons b. 1, λa.(1 + (π 1 π 1 b) c ) + c (π 1 π 1 b) p Cons π 1 b, a ) Nil Finally, we substitute this into the translation of rev.

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 24 rev = (λxs.rec(xs, Nil λa.a, Cons b.split(b,x.c.split(c,xs.r.λa.force(r) Cons x,a )))) Nil = 0, λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons b. 1, λa.(1 + (π 1 π 1 b) c ) + c (π 1 π 1 b) p Cons π 1 b, a ) Nil Observe that rev admits the same syntactic sugar as rev. In the complexity language, instead of taking projections of b, we can use the same pattern matching syntactic sugar as in the source language. rev = 0, λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons π 1 x, a ) Nil 1.2. Syntactic Sugar Translation. We walk through the same translation of fast reverse, but we use the syntactic sugar for matching introduced earlier. Recall the implementation of fast using syntactic sugar. The translation is almost identical to the translation of rev written without syntactic sugar until we translate the Cons branch of the rec. rev = λxs.rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) Nil First we apply the rule for translating an abstraction. The rule is λx.e = 0, λx. e. rev = 0, λxs. rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) Nil Next we apply the rule for translating an application. The rule is e 0 e 1 = (1+ e 0 c + e 1 c ) + c ( e 0 p e 1 p ). In this case, rec(...) is e 0 and Nil is e 1. We translate Nil then rec(...) separately. The translation of a constructor applied to an expression is a tuple of the cost of the translated expression and the corresponding complexity

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 25 language constructor applied to the potential of the translated expression. Since the expression inside Nil is, and = 0,, we have Nil = 0, c, Nil 0, p = 0, Nil The rule for translating a rec expression is rec(e, C x.e C ) = e c + c rec( e p, C x. e C ) rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) = xs c + c rec( xs p, Nil 1 + c λa.a Cons x, xs, r.1 + c λa.force(r) Cons x, a ) = 0, xs c + c rec( 0, xs p, Nil 1 + c λa.a Cons x, xs, r.1 + c λa.force(r) Cons x, a ) The term xs is a variable and the rule for translating variables is xs = 0, xs. = rec(xs, Nil 1 + c λa.a Cons x, xs, r.1 + c λa.force(r) Cons x, a ) The translation of the Nil branch is the same as before. 1 + c λa.a = 1, λa. 0, a The translation of the Cons branch is much simpler without the two splits. 1 + c λa.force(r) Cons x, a = 1 + c 0, λa. force(r) Cons x, a = 1, λa.(1 + force(r) c + Cons x, a ) c ) + c force(r) p Cons x, a p

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 26 The translation of force(r) and Cons x, a are the same as before, except we do not have a substitution to apply. force(r) = r c + c r p = 0, r c + c 0, r p = 0 + c r = r Cons x, a = 0, Cons x, a So the complete translation of the Cons branch is 1 + c λa.force(r) Cons x, a = 1 + c 0, λa. force(r) Cons x, a = 1, λa.(1 + force(r) c + Cons x, a ) c ) + c force(r) p Cons x, a p = 1, λa.(1 + r c + 0) + c r p Cons x, a = 1, λa.(1 + r c ) + c r p Cons x, a The complete translation of the rec becomes rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) = rec(xs, Nil 1 + c λa.a Cons x, xs, r.1 + c λa.force(r) Cons x, a ) = rec(xs, Nil 0, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a We substitute the translations of rec(..) and Nil into the application. Let R = rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a rec(xs, Nil λa.a,

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 27 Cons x, xs, r.λa.force(r) Cons x, a ) Nil Substituting R for the translation of rec and 0, Nil for the translation of Nil. = (1 + R c ) + c R p Nil = 1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil And our complete translation of rev is rev = λxs.rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) Nil = 0, λxs. rec(xs, Nil λa.a, Cons x, xs, r.λa.force(r) Cons x, a ) Nil = 0, λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil This is the same as the translation of rev without the syntactic sugar. We will use the syntactic sugar for the rest of this thesis. 1.3. Interpretation. Instead of interpreting rev, we will interpret rev applied to a list xs. Below is the translation of rev xs. rev xs = (1 + rev c + xs c ) + c rev p xs p The cost of rev is 0, and we will let xs be a variable, which has 0 cost. = (1 + 0 + 0) + c rev p xs = 1 + c (λxs.rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil) xs The cost of rev is driven by the auxiliary function rec(...). The cost of rev will be determined by the cost of the auxiliary function rec(...) applied to Nil plus some

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 28 constant factor. We will interpret the auxiliary function in the following denotational semantics. Since list is a user defined datatype, we must provide an interpretation. We interpret the size of an list to be the number of list constructors. list = N D list = { } + {1} N size list (Nil) = 1 size list (Cons(1,n)) = 1 + n N is the set of the natural numbers extended with infinity. We define the macro R(xs) as the translation of the auxiliary function rec(...) to avoid repeated coping of the translation. Let R(xs) = rec(xs, Nil 1, λa. 0, a Cons b. 1, λa.(1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a The recurrence g(n) is the interpretation of the auxiliary function R(xs), where n is the interpretation of xs. g(n) = R(xs) {xs n} = case(z, f C, f N ) size z xs {xs n} where f Nil (x) = 1, λa. 0, a {xs n} = (1, λa.(0, a)) f Cons (b) = 1, λa.(1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a {xs n, b map Φ Cons (d.(d, R(w) {w d, xs n}), b)}

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 29 Let us take a moment to analyze the semantic map. The definition mirrors the definition of the map macro in the complexity language. Since b is a tuple, map over a tuple is defined as the tuple of the map over the projections of the tuple. map Φ Cons (d.(d, R(w) {w d}), b) = (map int (d.(d, R(w) {w d}), π 0 b), map list (d.(d, R(w) {w d}), π 1 b)) The definition of map over int is map int (λx.v 0, V 1 ) = V 1. = (π 0 b, map list (d.(d, R(w) {w d}), π 1 b)) The definition of map over a recursive occurrence of a a datatype is map T (x.v 0, V 1 ) = V 0 [V 1 /x]. = (π 0 b, (π 1 b, R(w) {w π 1 b})) Observe that we can substitute g(π 1 b) for R(w) {w π 1 b}. = (π 0 b, (π 1 b, g(π 1 b))) Let us resume our interpretation of rec(...). f Cons (b) = 1, λa.(1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a {xs n, b map Φ Cons (λd.(d, R(w) {w d}), b)} = 1, λa.(1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a {xs n, b (π 0 b, (π 1 b, g(π 1 b)))} = (1, λa.(1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a {xs n, b (π 0 b, (π 1 b, g(π 1 b)))}) = (1, λa. (1 + π 1 π 1 b c ) + c π 1 π 1 b p Cons π 0 b, a {xs n, b (π 0 b, (π 1 b, g(π 1 b))), a a}) = (1, λa.(1 + g c (π 1 b)) + c g p (π 1 b) (1 + a))

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 30 So the initial extracted recurrence from rec is g(n) = case(z, f C, f N ) size z n where f Nil (x) = (1, λa.(0, a)) f Cons (b) = (1, λa.(1 + g c (π 1 b)) + c g p (π 1 b) (a + 1)) To obtain a closed form solution for the recurrence, we must eliminate the big maximum operator. To do so we break the definition of g into two cases. case n = 0: For n = 0, g(0) = (1, λa.(0, a)). case n > 0: g(n + 1) = case(ys, f Nil, f Cons ) size ys n+1 = case(ys, f Nil, f Cons ) size ys n = g(n) size ys=n+1 case(ys, f Nil, f Cons ) size ys=n+1 case(ys, λ().(1, λa.(0, a)), λ(1, m).(1, λa.(1 + g c (m)) + c g p (m)(a + 1))) = g(n) 1, λa.(1 + g c (n)) + c g p (n)(a + 1))) In order to eliminate the remaining max operator, we want to show that g is monotonically increasing; n.g(n) g(n + 1). By definition of, g(n) g(n + 1) g c (n) g c (n + 1) g p (n) g p (n + 1). First we will show lemma 3.1, which states the cost of g(n) is always one. Lemma 3.1. n.g c (n) = 1. Proof. We prove this by induction on n.

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 31 Base case: n = 0: By definition, g c (0) = (1, λa.(0, a)) = 1. Induction step: n > 0: By definition g c (n + 1) = (g(n) (1, λa.(1 + g c (n)) + c g p (n) (a + 1))) c. We distribute the projection over the max: g c (n + 1) = g c (n) 1. By the induction hypothesis, g c (n) = 1, so g c (n + 1) = 1. The immediate corollary of this is g c (n) is monotonically increasing. Corollary 3.1.1. n.g c (n) g c (n + 1). First we prove the lemma stating the potential of g(n) a is monotonically increasing. Lemma 3.2. n.g p (n) a g p (n) (a + 1) Proof. We prove this by induction on n. n = 0: g p (0) a = (λa.(0, a)) a = (0, a) g p (0) (a + 1) = (λa.(0.a)) (a + 1) = (0, a + 1) (0, a) (0, a + 1). n > 0: We assume g p (n) a g p (n) (a + 1). g p (n) a g p (n) (a + 1) g p (n) a (1 + g c (n)) + c g p (n) a g p (n) (a + 1) (1 + g c (n)) + c g p (n) (a + 1) g p (n + 1) a g p (n + 1) (a + 1) Now we show g p (n) g p (n + 1). Proof. By reflexivity, g p (n) g p (n). By the lemma we just proved: g p (n) a g p (n) (a + 1)

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 32 g p (n) a (1 + g c (n)) + c g p (n) (a + 1) λa.g p (n) a λa.(1 + g c (n)) + c g p (n) (a + 1) So since for all n, g c (n) = 1 and g p (n) λa.(1 + g c (n)) + c g p (n) (a + 1), we conclude g(n) 1, λa.(1 + g c (n)) + c g p (n) (a + 1) ) So g(n + 1) = 1, λa.(1 + g c (n)) + c g p (n) (a + 1) To extract a recurrence from g, we apply g to the interpretation of a list a. Let h(n, a) = g p (n) a. For n = 0 h(0, a) = g p (0)a = (λa.(0, a))a = (0, a) For n > 0 h(n, a) = g p (n)a = (λa.(1 + g c (n 1)) + c g p (n 1) (a + 1)) a = (1 + g c (n 1)) + c g p (n 1)(a + 1) = (1 + 1) + c h(n 1, a + 1) = (2 + h c (n 1, a + 1), h p (n 1, a + 1)) From this recurrence, we can extract a recurrence for the cost. For n = 0 h c (0, a) = (0, a) c = 0 For n > 0 h c (n, a) = (2 + h c (n 1, a + 1), h p (n 1, a + 1)) c = 2 + h c (n 1, a + 1)

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 33 We now have a recurrence for the cost of the auxiliary function rec(xs,... ) when applied to some list: 0 n = 0 (1) h c (n, a) = 2 + h c (n 1, a + 1) n > 0 We state the solution to the recurrence h c is 2n. Theorem 3.3. h c (n, a) = 2n Proof. We prove this by induction on n. : case n = 0 h c (0, a) = 0 = 2 0 : case n > 0 We assume h c (n, a + 1) = 2n. h c (n + 1, a) = 2 + h c (n, a + 1) = 2 + 2n = 2(n + 1) So we have proved the interpretation of applying the auxiliary function of rev xs to a list is linear in the length of xs. We can also extract a recurrence for the potential. For n = 0 h p (0, a) = h p (0, a) = (0, a) p = a For n > 0 h p (n, a) = (2 + h c (n 1, a + 1), h p (n 1, a + 1)) p = h p (n 1, a + 1)

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 34 We now have a recurrence for the potential of the auxiliary function in rev xs when applied to some list a. a n = 0 (2) h p (n, a) = h p (n 1, a + 1) n > 0 Theorem 3.4. h p (n, a) = n + a Proof. We prove this by induction on n. : case n = 0 : case n > 0 h p (0, a) = a h p (n, a) = h p (n 1, a + 1) = n 1 + a + 1 by the induction hypothesis = n + a Now that we have obtained a closed form solution for the recurrence describing the cost and potential of the auxiliary function that drives the cost of rev, we can obtain the interpretations for the cost and potential of rev xs. Recall the translation of rev xs. rev xs = 1 + c (λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil) xs We can obtain an interpretation of rev xs by substituting our interpretation of the auxiliary function. Let n = xs. rev xs = 1 + c (λxs.1 + c rec(xs, Nil 1, λa. 0, a

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 35 Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil) xs {xs n} = 1 + c λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil {xs n} n = 1 + c (λxs. 1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) Nil {xs n}) n = 1 + c (λxs.1 + c rec(xs, Nil 1, λa. 0, a Cons x, xs, r. 1, λa.(1 + r c ) + c r p Cons x, a ) {xs n} 0) n = 1 + c (λxs.1 + c h(xs, 0)) n = 1 + c (1 + c h(n, 0)) = 1 + c (1 + c (2n, n)) = (2 + 2n, n) So we see that the cost of rev xs is linear in the length of the list, and that the potential of the result is equal to the potential of the input.

3. SEQUENTIAL RECURRENCE EXTRACTION EXAMPLES 36 2. Reverse Here we present the naive implementation of list reverse. The naive implementation reverses a list in quadratic time as opposed to linear time. datatype list = Nil of unit Cons of int list The implementation walks down a list, appending the head of the list to the end of the result of recursively calling itself on the tail of the list. We use the syntactic sugar introduced earlier. rev uses the auxiliary function snoc. snoc appends an item to the end of a list. snoc = λxs.λx.rec(xs, Nil Cons x, Nil, Cons y, ys, r.cons y, force(r) ) The quadratic time implementation of reverse recurses on the list, appending the head of the list to the recursively reversed tail of the list. rev = λxs.rec(xs, Nil Nil, Cons x, xs, r.snoc force(r) x) 2.1. Translation. 2.1.1. snoc Translation. First we translate the function snoc. To do so we apply the rule for translating an abstraction two times. Recall the rule is λx.e = 0, λx. e. snoc = λxs.λx.rec(xs, Nil Cons x, Nil, Cons y, ys, r.cons y, force(r) ) = 0, λxs. λx.rec(xs, Nil Cons x, Nil, Cons y, ys, r.cons y, force(r) ) = 0, λxs. 0, λx. rec(xs, Nil Cons x, Nil, Cons y, ys, r.cons y, force(r) ) Next we apply the rule for translating a rec. = 0, λxs. 0, λx. xs c + c rec( xs p, Nil 1 + c Cons x, Nil,