<<

Universiteit Utrecht Faculty of Science Dept. of Information and Computing Sciences

MMH: High-level programming with the Mu-Mu--calculus

Author Supervisors Rik van Toor Dr. Wouter Swierstra ICA-4239776 Dr. Alejandro Serrano Mena

March 17, 2020 2 Contents

Acknowledgements iii

Abstract v

1 Introduction 1 1.1 Contributions ...... 1 1.2 Structure ...... 2

2 The 휇-calculus ̃휇 3 2.1 Introduction to Gentzen’s sequent calculus ...... 3 2.1.1 Logical rules ...... 5 2.2 The 휇-calculus ̃휇 ...... 8 2.2.1 The 휆-calculus ...... 8 2.2.2 The 휇-calculus ̃휇 ...... 11 2.2.2.1 Typing ...... 11 2.2.2.2 Evaluation ...... 12 2.2.3 Adding constructors and types ...... 13 2.2.4 User-defined (co-)datatypes ...... 14 2.2.4.1 Evaluation ...... 18 2.2.4.2 Nested evaluation ...... 18 2.2.5 Functions in the 휇-calculus ̃휇 ...... 19

3 Programming with the 휇-calculus ̃휇 25 3.1 Converting 휆-terms to 휇-terms ̃휇 ...... 25 3.1.1 Typechecking ...... 29 3.1.2 Evaluation ...... 33 3.2 Nested (co-)patterns ...... 34 3.2.1 Expanding (co-)patterns ...... 36 3.2.2 Adding join points ...... 39 3.2.2.1 Adding join points to C휇 ...... 41 3.3 Mu-Mu-tilde-Haskell ...... 42 3.3.1 Conclusion ...... 46

4 Adding polymorphism 47 4.1 Downen and Ariola’s polymorphism ...... 47 4.1.1 Type inference ...... 49 4.2 Hindley-Milner polymorphism ...... 50 4.2.1 Hindley-Milner in the 휇-calculus ̃휇 ...... 51 4.2.2 Conclusion ...... 53 ii CONTENTS

5 Closing remarks 55 5.1 Conclusions ...... 55 5.2 Future work ...... 55 5.2.1 Formalisation ...... 55 5.2.2 Adding modern features ...... 55 5.2.3 Code generation ...... 56 5.2.4 Adding dependent types ...... 56 5.2.5 Converting 휇-calculus ̃휇 programs to the 휆-calculus ...... 56 Acknowledgements

I would like to thank both my supervisors, Dr. Wouter Swierstra and Dr. Alejandro Serrano Mena, for their support and invaluable advice they have provided throughout this project. I especially want to acknowledge Dr. Serrano Mena’s contributions to this work, given that he has done all of it voluntarily in his spare time, purely out of interest in the subject, for which I am very grateful. iv Abstract

The 휇-calculus ̃휇 is a small core programming language, for which the separation between data and co- data is essential. To make the power of this separation more accessible, we introduce MMH, a high-level functional programming language that compiles to the 휇-calculus. ̃휇 We show how 휆-calculus programs can be converted to 휇-calculus ̃휇 programs, and extend the calculus with programmer-friendly features, such as nested (co-)pattern matching. We introduce a polymorphic typing system to the 휇-calculus ̃휇 for which type inferencing is decidable, and allow MMH to reap the benefits. vi 1 Introduction

Since its inception, the 휇-calculus, ̃휇 pronounced “mu-mu-tilde-calculus”, has long been an interest of study. The ideas and concepts that over time have been introduced to the calculus, have inspired practical implementations, such as Sequent Core, an alternative core language to GHC by Downen et al. [Dow+16]. Unfortunately, these implementations often fail at doing either of two things: fully exposing the power of the 휇-calculus, ̃휇 or providing a comfortable programming surface. The Sequent Core compiler plu- gin allows Haskell programs to be compiled to the 휇-calculus. ̃휇 However, as its programs are written in Haskell, not all features of the calculus are exposed to the user. Co-datatypes, for example, are not supported. Similarly, experimental evaluators and compilers for the 휇-calculus ̃휇 expose all features of the calculus, but writing programs for them is not done in a modern programming language, but in raw 휇-calculus ̃휇 (co-)terms.

1.1 Contributions

This thesis contains detailed descriptions of solutions to numerous shortcomings of the 휇-calculus. ̃휇 The following solutions are offered and explained:

• A formal way to convert 휆-calculus terms to 휇-calculus ̃휇 terms

• Adding support for nested (co-)patterns in the 휇-calculus ̃휇 by adapting Augustsson’s algorithm to include co-data and commands

• Defining syntactical structures to allow 휇-terms, co-terms and commands to be written. These structures, in addition to a large part of Haskell, form a new programming language based on the 휇-calculus, ̃휇 called MMH.

• Adding type polymorphism to the 휇-calculus ̃휇 by implementing a Hindley-Milner typing system. 2 STRUCTURE

1.2 Structure

First, an extensive background section will give an introduction to the 휇-calculus ̃휇 by explaining the and the sequent calculus, and their relations to the 휆-calculus and the 휇-calculus. ̃휇 This thesis then introduces a high-level programming language that uses the 휇-calculus ̃휇 at its core, and supports advanced datatypes and co-datatypes to be defined and processed in a straightforward way. To do so, first, a method to convert programs written for the 휆-calculus to the 휇-calculus ̃휇 is proposed, therefore allowing Haskell programs to be transformed to 휇-calculus ̃휇 programs. Next, Augustsson’s pattern expansion is adapted for use in the 휇-calculus ̃휇 to allow nested pattern matching. Finally, new syntax is introduced to the language, which is used to define co-terms and commands. After defining this programming language, this thesis proposes a polymorphic type system forthe 휇-calculus. ̃휇 An existing system for polymorphism is explored, and its advantages and disadvantages are laid out. Then, a new polymorphic type system that is heavily based on the Hindley-Milner typing system is formulated. This thesis ends by listing the conclusions that can be taken from the earlier sections, and suggesting multiple related questions and topics that can be researched in the future. 2 The 휇-calculus ̃휇

The 휇-calculus, ̃휇 as formalised by Herbelin, is a logical system within which computation can be mod- eled [Her05]. The calculus can be seen as a minimalistic theoretical programming language, based on the sequent calculus. In this chapter, the ins and outs of the 휇-calculus ̃휇 are explored. First, the sequent calculus and its origins are explained. Next, the 휇-calculus’ ̃휇 relation to the sequent calculus is described. Finally, the differences between the sequent calculus and natural deduction, as well as the differences between the 휇-calculus ̃휇 and the 휆-calculus are explained.

2.1 Introduction to Gentzen’s sequent calculus

In 1935, Gerhard Gentzen invented two separate formal systems of logical reasoning [Gen35]: natural de- duction and the sequent calculus. Both are very similar in the sense that both are systems for second-order propositional . The two systems work with logical propositions that either consist of an atomic vari- able (푋, 푌 , 푍), or a proposition constructor. The supported constructors are truth (⊤), falsehood (⊥), disjunction (∨), conjunction (∧), implication (⊃), universal quantification (∀), and existential quantifi- cation (∃). The syntax of the propositions that will be used is defined in Figure 2.1. Both systems support proofs of these propositions using judgements. However, the two systems dif- fer in the way these judgements are formed. In natural deduction, a judgement simply consists of a proposition. In the sequent calculus, however, a judgement consists of two sets of propositions. One represents the judgement’s hypotheses, while the other set represents its consequences. Figure 2.2 shows the syntactical definitions of judgements in both natural deduction and the sequent calculus.

푋, 푌 , 푍 ∈ 푃 푟표푝푉 푎푟푖푎푏푙푒 ∶∶= … 퐴, 퐵, 퐶 ∈ 푃 푟표푝표푠푖푡푖표푛 ∶∶= 푋 ∣ ⊤ ∣ ⊥ ∣ 퐴 ∧ 퐵 ∣ 퐴 ∨ 퐵 ∣ 퐴 ⊂ 퐴 ∣ ∀푋.퐴 ∣ ∃푋.퐴

Figure 2.1: Propositions in the sequent calculus and natural deduction 4 INTRODUCTION TO GENTZEN’S SEQUENT CALCULUS

Γ ∈ 퐻푦푝표푡ℎ푒푠푖푠 ∶∶= 퐴, 퐵, 퐶 Δ ∈ 퐶표푛푠푒푞푢푒푛푐푒 ∶∶= 퐴, 퐵, 퐶 퐻, 퐽 ∈ 퐽푢푑푔푒푚푒푛푡 ∶∶= ⊢ 퐴 퐻, 퐽 ∈ 퐽푢푑푔푒푚푒푛푡 ∶∶= Γ ⊢ Δ

Natural deduction Sequent calculus

Figure 2.2: Judgements in natural deduction and the sequent calculus

The semantics of both systems are defined by inference rules, which state that zero or more specific premises lead to a certain conclusion. Premises, as well as conclusions, usually consist of judgements, and inference rules are denoted as Premise 1 … Premise 푛 Conclusion In these inference rules, we differentiate between several types of rules over two dimensions. If arulein- troduces a constructor in the conclusion that was not present in the premises, we categorise this rule as an introduction rule. When a rule does the exact oposite - leaving a constructor that is a part of the premises out of the conclusion - we call this rule an elimination rule. Inference rules often specify introduction or elimination on either the right side, or the left side of the (⊢) in judgements. Inference rules like these are called left-hand rules and right-hand rules accordingly. As Figure 2.2 shows, judgements in natural deduction only contain propositions on the right-hand side of the turnstile. Natural deduction therefore consists of only right-hand rules. Judgements in the sequent calculus do contain propositions on both sides. For this reason, the sequent calculus does contain both left-hand and right-hand rules. However, unlike natural deduction, the sequent calculus has no need for elimination rules, but instead solely consists of introduction rules. In the sequent calculus, judgements consist of two sets of propositions: hypotheses and consequences, denoted as Γ ⊢ Δ. Semantically, Γ ⊢ Δ means that when every proposition in Γ is true, then at least one proposition in Δ must be true as well. The absence of hypotheses in natural deduction causes the key difference between the sequent calculus and natural deduction: the sequent calculus is conditional, while natural deduction is not. The distinction between hypotheses and consequences can be used to model truth and falsehood into the sequent calculus. “퐴 is true” can be encoded as ⊢ 퐴. There are no hypotheses, which means that, by default, all hypotheses are true. Since this is the case, at least one proposition in the set of consequences must be true. Since the set of consequences is a singleton of 퐴, 퐴 must always be true. Similarly, “퐴 is ” can be encoded as 퐴 ⊢. In this case, the judgement is conditional; the set of hypotheses is a singleton of 퐴. Whenever 퐴 is true, the judgement claims that at least one proposition in the of consequences must be true. This is, of course, not possible. Therefore, whenever 퐴 is true, we end up in a . Ergo, 퐴 can never be true, and it must be false. The sequent calculus consists of two core rules that form the basis of any logical proof in the cal- culus. Both are defined in Figure 2.3. The first rule, the axiom rule, or 퐴푥, allows any proposition to be introduced on both sides of the turnstile. The second and more interesting one, the cut rule, allows intermediate results to be cut out of a proof. For example, if we know a proposition 퐴 is both true, and false, we know a contradiction has taken place. We can use the cut rule to prove this:

퐴 ⊢ ⊢ 퐴 퐶푢푡 ⊢ In addition to the core rules, the calculus specifies six structural rules. The structural rules - defined in Figure 2.4 are split into three different kinds that have been implemented for both the left-hand side, as well as the right-hand side. THE 휇-CALCULUS ̃휇 5

Γ ⊢ 퐴, Δ Γ′, 퐴 ⊢ Δ′ 퐴푥 퐶푢푡 퐴 ⊢ 퐴 Γ′, Γ ⊢ Δ′,Δ

Figure 2.3: The core rules of the sequent calculus

Γ ⊢ Δ Γ ⊢ Δ 푊 푅 푊 퐿 Γ ⊢ 퐴, Δ Γ, 퐴 ⊢ Δ

Γ ⊢ 퐴, 퐴, Δ Γ, 퐴, 퐴 ⊢ Δ 퐶푅 퐶퐿 Γ ⊢ 퐴, Δ Γ, 퐴 ⊢ Δ

Γ ⊢ Δ, 퐴, 퐵, Δ′ Γ′, 퐵, 퐴, Γ ⊢ Δ 푋푅 푋퐿 Γ ⊢ Δ, 퐵, 퐴, Δ′ Γ′, 퐴, 퐵, Γ ⊢ Δ

Figure 2.4: The structural rules of the sequent calculus

First, there are the weakening rules: 푊 푅 and 푊 퐿. The weakening rules allow extra propositions to be introduced into a judgement. This is allowed, since adding an extra proposition to either side of a judgement does not compromise its validity. Assume valid judgement 퐴, 퐵 ⊢ 퐶, 퐷. This judgement means that at least one proposition out of 퐶 and 퐷 must be true whenever 퐴 and 퐵 are both true. If we add proposition 퐸 to the left-hand side, we get 퐴, 퐵, 퐸 ⊢ 퐶, 퐷. This is still valid, since we now claim that 퐶 or 퐷 must be true when 퐴, 퐵, and 퐸 are true. When this is the case, 퐴 and 퐵 are still true. If we add 퐸 to the right-hand side instead, we get the judgement 퐴, 퐵 ⊢ 퐶, 퐷, 퐸. This, too, is a valid transformation. When 퐴 and 퐵 are both true, at least one out of 퐶 and 퐷 must be true. Adding 퐸 does not change the validity of the judgement. The contraction rules (퐶푅, 퐶퐿) and exchange rules (푋푅, 푋퐿) are more trivial. The contraction rules specify that duplicated propositions in both the hypotheses and the consequences can be safely ignored. Γ, 퐴, 퐴 is functionally the exact same set of hypotheses as Γ, 퐴. The exchange rules formalise how the order of propositions are irrelevant on both the left-hand and the right-hand sides.

2.1.1 Logical rules

The rules that dictate the behaviour of the different kinds of proposition constructors are called logical rules. In this section the logical rules for the sequent calculus will be explained, by comparing them with their natural deduction counterparts. The full definition of all logical rules in this version of the sequent calculus is found in Figure 2.5. Figure 2.6 shows the logical rules for the same proposition constructors for natural deduction. Let us begin with the very simplest case: truth and falsehood. In the sequent calculus, truth can always be introduced on the right-hand side of a judgement, while falsehood can always be introduced on the left-hand side. Introducing truth or falsehood on the opposite ends is not possible, for this would mean ”true is false”, or ”false is true”. In natural deduction, the introduction rule for truth is effectively the same: ⊤퐼. There is no left-hand introduction rule for truth in the sequent calculus. Similarly, ⊢ ⊤ there is no elimination rule for truth in natural deduction. If there had been one, it would imply any proposition could always be reduced to truth, even false propositions. Falsehood is the dual of truth, and its rules reflect this relation. In natural deduction, falsehood can ⊢ ⊥ be eliminated using the follwing rule: ⊥퐸. This falsehood elimination rule is effectively a crash- ⊢ 퐶 and-burn tactic. Any proposition can be reduced to falsehood, but doing so halts the proof. Since there is no introduction rule for ⊥, the ⊥ can never be removed from the judgement. Disjunction is another example where the implementations of the sequent calculus and natural de- duction are quite similar. The sequent calculus’ ∧푅 rule is virtually identical to natural deduction’s ∧퐼 6 INTRODUCTION TO GENTZEN’S SEQUENT CALCULUS

⊤푅 ⊥퐿 Γ ⊢ ⊤, Δ No ⊤퐿 rule No ⊥푅 rule Γ, ⊥ ⊢ Δ

Γ ⊢ 퐴, Δ Γ ⊢ 퐵, Δ Γ, 퐴 ⊢ Δ Γ, 퐵 ⊢ Δ ∧푅 ∧퐿 ∧퐿 Γ ⊢ 퐴 ∧ 퐵, Δ Γ, 퐴 ∧ 퐵 ⊢ Δ 1 Γ, 퐴 ∧ 퐵 ⊢ Δ 2

Γ ⊢ 퐴, Δ Γ ⊢ 퐵, Δ Γ, 퐴 ⊢ Δ Γ, 퐵 ⊢ Δ ∨푅 ∨푅 ∨퐿 Γ ⊢ 퐴 ∨ 퐵, Δ 1 Γ ⊢ 퐴 ∨ 퐵, Δ 2 Γ, 퐴 ∨ 퐵 ⊢ Δ

Γ, 퐴 ⊢ Δ Γ ⊢ 퐴, Δ ¬푅 ¬퐿 Γ ⊢ ¬퐴, Δ Γ, ¬퐴 ⊢ Δ

Γ, 퐴 ⊢ 퐵, Δ Γ ⊢ 퐴, Δ Γ′, 퐵 ⊢ Δ′ ⊃ 푅 ⊃ 퐿 Γ ⊢ 퐴 ⊃ 퐵, Δ Γ′, Γ, 퐴 ⊃ 퐵 ⊢ Δ′,Δ

Γ ⊢ 퐴, Δ 푋 ∉ 퐹 푉 (Γ ⊢ Δ) Γ, 퐴{퐵/푋} ⊢ Δ ∀푅 ∀퐿 Γ ⊢ ∀푋.퐴, Δ Γ, ∀푋.퐴 ⊢ Δ

Γ ⊢ 퐴{퐵/푋}, Δ Γ, 퐴 ⊢ Δ 푋 ∉ 퐹 푉 (Γ ⊢ Δ) ∃푅 ∃퐿 Γ ⊢ ∃푋.퐴, Δ Γ, ∃푋.퐴 ⊢ Δ

Figure 2.5: Logical rules for truth (⊤), falsehood (⊥), conjunction (∧), disjunction (∨), (¬), implication (⊃), universal quantification (∀) and existential quantification (∃) in the sequent calculus.

⊢ ⊥ ⊤퐼 No ⊤퐸 rule No ⊥퐼 rule ⊥퐸 ⊢ ⊤ ⊢ 퐶 ⊢ 퐴 ⊢ 퐵 ⊢ 퐴 ∧ 퐵 ⊢ 퐴 ∧ 퐵 ∧퐼 ∧퐸 ∧퐸 ⊢ 퐴 ∧ 퐵 ⊢ 퐴 1 ⊢ 퐵 2 푥 푦 ⊢ 퐴 ⊢ 퐵 ⊢ 퐴 ∨퐼 ⊢ 퐵 ∨퐼 ⋮ ⋮ ⊢ 퐴 ∨ 퐵 1 ⊢ 퐴 ∨ 퐵 2 ⊢ 퐴 ∨ 퐵 ⊢ 퐶 ⊢ 퐶 ∨퐸 ⊢ 퐶 푥,푦 푥 ⊢ 퐴 ⊢ 퐴 ⊃ 퐵 ⊢ 퐴 ⋮ ⊃ 퐸 ⊢ 퐵 ⊢ 퐵 ⊃ 퐼 ⊢ 퐴 ⊃ 퐵 푥

⋮ (푋 ∉ 퐹 푉 (∗)) ⊢ ∀푋.퐴 ∀퐸 ⊢ 퐴 ∀퐼 ⊢ 퐴{퐵/푋} ⊢ ∀푋.퐴 푋 푥 ⊢ 퐴 ⊢ 퐴{퐵/푋} ⋮ (푋 ∉ 퐹 푉 (∗)) ∃퐼 ⊢ ∃푋.퐴 ⊢ ∃푋.퐴 ⊢ 퐶 (푋 ∉ 퐹 푉 (퐶)) ∃퐸 퐶 푋,푥

Figure 2.6: Logical rules for truth (⊤), falsehood (⊥), conjunction (∧), disjunction (∨), implication (⊃), universal quantification (∀) and existential quantification (∃) in natural deduction. THE 휇-CALCULUS ̃휇 7

푥 ⊢ 퐴 Γ, 퐴 ⊢ 퐵, Δ ⋮ ⊃ 푅 Γ ⊢ 퐴 ⊃ 퐵, Δ ⊢ 퐵 ⊃ 퐼 ⊢ 퐴 ⊃ 퐵 푥 Sequent calculus Natural deduction

Figure 2.7: Implication in natural deduction and the sequent calculus

푥 ⊢ (퐴 ∧ 퐵) ∧ 퐶 ∧퐸 푥 1 ⊢ (퐴 ∧ 퐵) ∧ 퐶 ⊢ 퐴 ∧ 퐵 ∧퐸 ∧퐸 ⊢ 퐴 1 ⊢ 퐶 2 ∧퐼 ⊢ 퐴 ∧ 퐶 ⊃ 퐼 ⊢ (퐴 ∧ 퐵) ∧ 퐶 ⊃ 퐴 ∧ 퐶 푥

Natural deduction

퐴푥 퐴 ⊢ 퐴 ∧퐿1 퐴푥 퐴 ∧ 퐵 ⊢ 퐴 ∧퐿 퐶 ⊢ 퐶 ∧퐿 (퐴 ∧ 퐵) ∧ 퐶 ⊢ 퐴 1 (퐴 ∧ 퐵) ∧ 퐶 ⊢ 퐶 2 ∧푅 (퐴 ∧ 퐵) ∧ 퐶 ⊢ 퐴 ∧ 퐶 ⊃ 푅 ⊢ (퐴 ∧ 퐵) ∧ 퐶 ⊃ 퐴 ∧ 퐶

Sequent calculus

Figure 2.8: An example proof of the proposition (퐴 ∧ 퐵) ∧ 퐶 ⊃ 퐴 ∧ 퐶 in the two logical systems. rule: 퐴 ∧ 퐵 can be introduced when we know that both 퐴 and 퐵 are true. The elimination rules are straight forward too. If we know that 퐴 ∧ 퐵 holds, we can safely eliminate either proposition to end up with 퐴 or 퐵. The sequent calculus does not have elimination rules, of course, but instead defines behaviour for the left-hand side. Take the special case of ∧퐿1 where the starting judgement is 퐴 ⊢ 퐶. This judgement claims that 퐶 must be true if 퐴 is true. If we were to change the hypothesis 퐴 to 퐴 ∧ 퐵, 퐴 ⊢ 퐶 퐶 would still be true, as 퐴 ∧ 퐵 dictates that 퐴 must be true. We thus end up with . This step 퐴 ∧ 퐵 ⊢ 퐶 can be generalised to rule ∧퐿1. Since ∧ is symmetric, we can use the same steps to gain rule ∧퐿2. The rules for implication display the true power of the sequent calculus. Natural deduction’s ⊃ 퐼푥 rule handles implication by creating a new verification rule 푥 and allowing this rule to be used some- where on above the right-hand side of the implication. Since the sequent calculus is conditional, it is not necessary to create new rules. The proposition on the left-hand side of the implication simlpy gets moved to the hypotheses, while the right-hand side remains in the consequences. This allows for a much simpler and more straight forward rule, and by proxy, proofs. Figure 2.7 contains the right introduction rules for implication in both systems. Figure 2.8 shows example proofs for the proposition (퐴∧퐵)∧퐶 ⊃ 퐴∧퐶 in both natural deduction and the sequent calculus. The two proof trees display the implications of the different approaches taken by the two systems. In both systems, the implication is first reduced to the right-hand side 퐴∧퐶, after which the conjunction is split into 퐴 and 퐶. In natural deduction, the two propositions need to be expanded to (퐴 ∧ 퐵) ∧ 퐶 using elimination rules, before the generated verification rule 푥 can be utilised to finish the proof. In the sequent calculus, the system ’remembers’ (퐴 ∧ 퐵) ∧ 퐶 as a hypothesis, and this can be reduced to either 퐴 or 퐶. Another advantage the sequent calculus has over natural deduction is its neutrality. The sequent 8 THE 휇-CALCULUS ̃휇 calculus can be used to prove both true and false judgements, whereas natural deduction favours truth over falsehood. This neutrality is used to implement negation (¬) into the sequent calculus. In natural deduction, this is not possible. Instead, a negation of 퐴 can be implied by proving the judgement 퐴 ⊃ ⊥ as follows:

푥 ⊢ 퐴 ⋮ ⊢ ⊥ ⊃ 퐼 ⊢ 퐴 ⊃ ⊥ 푥

Using this encoding, it is possible to prove ¬⊥, or ”false is false”.

푥 ⊢ ⊥ ⊃ 퐼 ⊢ ⊥ ⊃ ⊥ 푥

A final remark on the logical rules for both the sequent calculus and natural deduction isaboutthe quantification rules. The rules for both existential and universal quantification in both systems contain side conditions. Let us look at the right rule and the introduction rule for universal quantification. ⋮ (푋 ∉ 퐹 푉 (∗)) Γ ⊢ 퐴, Δ (푋 ∉ 퐹 푉 (Γ ⊢ Δ)) ⊃ 푅 ⊢ 퐴 ∀퐼 Γ ⊢ ∀푋.퐴, Δ ⊢ ∀푋.퐴 푋

Both rules contain a side condition: (푋 ∉ 퐹 푉 (Γ ⊢ Δ)) and (푋 ∉ 퐹 푉 (∗)) respectively. Here, 퐹 푉 refers to a function that returns all the free variables in its argument. A free variable is a variable that is not bound by any quantifiers. For example, the result of 퐹 푉 (∀푋.(푋 ∨ 푌 ∨ 푍)) would be the set of 푍 and 푌 . Another reoccurring concept in the logical rules is substitution. The substitution of 퐵 for 푋 in propo- sition 퐴 is denoted as 퐴{퐵/푋}. This means that every free occurrence of 푋 within 퐴 is replaced by 퐵. Substitution is used in - among others - the ∃푅 rule. Γ ⊢ 퐴{퐵/푋}, Δ ∃푅 Γ ⊢ ∃푋.퐴, Δ

This rule permits the introduction of existential quantifieres over any free variable. For example, this rule can be used to transform the judgement Γ ⊢ 퐴 ∧ 퐵, Δ to Γ ⊢ ∃푋.푋 ∧ 퐵, Δ, as 퐴 ∧ 퐵 is equivalent to 푋 ∧ 퐵{퐴/푋}.

2.2 The 휇-calculus ̃휇

In this section the 휇-calculus ̃휇 is introduced and fully specified. First, we recall some notions about the 휆-calculus. Then, the simplest version of the 휇-calculus ̃휇 is defined, and compared to the 휆-calculus. Next, the 휇-calculus ̃휇 is extended with advanced types and type constructors, and the effects of this on typing and evaluation rules are explained. Finally, function types are introduced using these concepts, and examples of programming with the 휇-calculus ̃휇 are given.

2.2.1 The 휆-calculus

Alonzo Church invented the 휆-calculus [Chu36], a minimalistic core programming language based on natural deduction, in 1936. The 휆-calculus in itself consists of just three components: variables, functions and application. THE 휇-CALCULUS ̃휇 9

푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 푀, 푁 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휆푥.푀 ∣ 푀 푁

The dynamic behaviour of these terms is defined by three simple rules:

훼-equivalence: 휆푥.푀 =훼 휆푦.푀

훽-reduction: (휆푥.푀) 푁 ≻훽 푀{푁/푥} 휂-conversion: (휆푥.푀 푥) ≻ 휂푀 (푥 ∉ 퐹 푉 (푀))

Once we start adding types according to Church’s simply typed 휆-calculus [Chu40], the similarities between the calculus and natural deduction become striking. First, we add a function type (→) and im- plement typing rules for functions and function applications. Functions can be compared to implication in natural deduction. Where 퐴 ⊃ 퐵 means “if 퐴 is true, 퐵 is true,” function 휆푥.푀 with type 퐴 → 퐵 can be seen as “if 푥 has type 퐴, 푀 has type 퐵.” This way, a 휆-function reflects the introduction rule for implications, as defined in Figure 2.6. Implication’s elimination rule is useful too. Where 퐴 ⊃ 퐵 and 퐴 are true, 퐵 must be true as well. Similarly, where 푀 has type 퐴 → 퐵, and 푁 has type 퐴, 푀 푁 must have type 퐵. Using the connections between functions and implication, we are able to deduce the introduction and elimination rules for functions: the → 퐼푥 and the → 퐸 rules in Figure 2.9. Next, we add product types to the calculus. To do so, we extend our definition of terms by pairs and projections, and add a product type:

푀, 푁 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ (푀, 푁) ∣ 휋1(푀) ∣ 휋2(푀) 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐴 → 퐵 ∣ 퐴 × 퐵

Dynamically, 휋1(푀, 푁) evaluates to 푀, while 휋2(푀, 푁) evaluates to 푁. In other words, 휋1 and 휋2 eliminate terms of product types. Like functions, product types bear a strong resemblance to a proposi- tional constructor in natural deduction: conjunction. Where 푀 has type 퐴, and 푁 has type 퐵, (푀, 푁) must have type 퐴 × 퐵. This reflects the behaviour of conjunction: if 퐴 is true and 퐵 is true, 퐴 ∧ 퐵 must be true. Once again, we can infer the typing rules from natural deduction. The ×퐼, ×퐸1 and ×퐸2 rules are added to the calculus in Figure 2.9. Finally, we add sum types (+), and extend the definition of terms once more:

푀, 푁 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ 휄1(푀) ∣ 휄2(푀)

∣ (푐푎푠푒 푀 표푓 휄1(푥) ⇒ 푁1 ∣ 휄2(푦) ⇒ 푁2) 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐴 → 퐵 ∣ 퐴 × 퐵 ∣ 퐴 + 퐵

With these new terms come more evaluation rules. 푐푎푠푒 휄1(푀) 표푓 휄1(푥) ⇒ 푁1 ∣ 휄2(푦) ⇒ 푁2 evaluates to 푁1{푀/푥}, while 푐푎푠푒 휄2(푀) 표푓 휄1(푥) ⇒ 푁1 ∣ 휄2(푦) ⇒ 푁2 evaluates to 푁2{푀/푥}. In a way, sum types are the dual of product types. While products consist of one constructor that takes two terms, and two destructors, sums consist of two constructors that take one term, and one destructor. It may come as no surprise that product types heavily resemble disjunction. As seen before, we derive the product typing rules from natural deduction’s disjunction rules, and define the +퐼1, +퐼2 and +퐸푥,푦 in Figure 2.9. The complete definition of this version of the 휆-calculus is found in Figure 2.9. 10 THE 휇-CALCULUS ̃휇

푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐴 → 퐵 ∣ 퐴 × 퐵 ∣ 퐴 + 퐵 푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 푀, 푁 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휆푥.푀 ∣ 푀 푁 ∣ (푀, 푁)

∣ 휋1(푀) ∣ 휋2(푀) ∣ 휄1(푀) ∣ 휄2(푀)

∣ (푐푎푠푒 푀 표푓 휄1(푥) ⇒ 푁1 ∣ 휄2(푦) ⇒ 푁2) 퐻, 퐽 ∈ 퐽푢푑푔푒푚푒푛푡 ∶∶= 푀 ∶ 퐴

푀 ∶ 퐴 푁 ∶ 퐵 푀 ∶ 퐴 × 퐵 푀 ∶ 퐴 × 퐵 ×퐼 ×퐸1 ×퐸2 (푀, 푁) ∶ 퐴 × 퐵 휋1(푀) ∶ 퐴 휋2(푀) ∶ 퐵 푀 ∶ 퐴 푀 ∶ 퐵 +퐼1 +퐼2 휄1(푀) ∶ 퐴 + 퐵 휄2(푀) ∶ 퐴 + 퐵 푥 푦 푥 ∶ 퐴 푦 ∶ 퐵 ⋮ ⋮

푀 ∶ 퐴 + 퐵 푁1 ∶ 퐶 푁2 ∶ 퐶 +퐸푥,푦 case 푀 of 휄1(푥) ⇒ 푁1 ∣ 휄2(푦) ⇒ 푁2 ∶ 퐶 푥 푥 ∶ 퐴 푀 ∶ 퐴 → 퐵 푁 ∶ 퐴 ⋮ → 퐸 푀 푁 ∶ 퐵 푀 ∶ 퐵 → 퐼 휆푥.푀 ∶ 퐴 → 퐵 푥

Figure 2.9: The simply typed 휆-calculus with function (→), sum (+) and product (×) types. THE 휇-CALCULUS ̃휇 11

2.2.2 The 휇-calculus ̃휇 We have seen how the 휆-calculus relates to natural deduction. In this section we will explore the results of creating a programming language based on the sequent calculus: the 휇-calculus. ̃휇 The 휇-calculus ̃휇 originally stems from Herbelin [Her05]. Since the sequent calculus is ambidextrous, meaning that it has both right and left-hand rules, so is the 휇-calculus. ̃휇 In the context of programming, this translates to the calculus differentiating between terms and co-terms: Terms on the left side of the turnstile, co-terms on the right side. Terms are comparable to the terms in the 휆-calculus. They consist of variables and various construc- tors, which together represent pieces of data. Co-terms are the dual to terms. They are not formed by con- structors, but rather destructors, observers, or consumers. Co-terms represent co-data, or co-inductively defined data. Co-data is often used to represent infinite structures, such as infinite streams. Termsand co-terms do not have separate type systems; a term can have the same type as a co-term. In the 휇-calculus, ̃휇 computation happens in commands: structures where a term and a co-term of the same type come together.

푐 ∈ 퐶표푚푚푎푛푑 ∶∶= ⟨푇 푒푟푚 ∥ 퐶표푇 푒푟푚⟩

This may seem complicated, especially when compared to the 휆-calculus, where computation simply happens in function applications. However, recall that terms are producers of a type where co-terms are consumers. Commands allow producers and consumers to interact with each other, which is the essence of computation. After all, producing data which cannot be consumed is not very useful, nor is a consumer of data that cannot be produced. Terms and co-terms can interact using 휇 and -constructions.̃휇

푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 훼, 훽, 훾 ∈ 퐶표푉 푎푟푖푎푏푙푒 ∶∶= … 푣 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휇훼.푐 푒 ∈ 퐶표푇 푒푟푚 ∶∶= 훼 ∣ ̃휇푥.푐 푐 ∈ 퐶표푚푚푎푛푑 ∶∶= ⟨푣 ∥ 푒⟩

At first glance, 휇 and ̃휇 look to be the 휇-calculus ̃휇 equivalent of the 휆-calculus’ 휆. This is, however, not the case. 휆-constructions are actual functions. They take an argument and produce a term that may use this argument. 휇 and -constructions̃휇 take an argument, a co-term and a term respectively, and execute a command that may use this argument. They do not return any value by themselves, and are therefore not functions. They are perhaps more accurately compared to void methods, as found in many imperative programming languages, such as C.

2.2.2.1 Typing

Judgements in the sequent calculus consist of a set of hypotheses and a set of consequences. Judgements in the 휇-calculus ̃휇 are similar. Hypotheses are replaced by an input environment, which stores variables and their types. Consequences are replace by an output environment, which does exactly the same for co- variables. The judgements come in different shapes: passive and active. Passive judgements, which are denoted by 푐 ∶ (Γ ⊢ Δ), simply bind an input environment and an output environment to a command. Then there are active judgements: (Γ ⊢ 푣 ∶ 퐴 ∣ Δ) and (Γ ∣ 푒 ∶ 퐴 ⊢ Δ). Active judgements highlight one proposition, which is called the active proposition. The active proposition is the proposition that we currently want to prove. It can therefore not be removed by using weakening rules in a proof tree. The full definition of the core 휇-calculus ̃휇 is given in Figure 2.10. Since judgements have been extended from the sequent calculus, the 퐴푥 and 퐶푢푡 rule are not suffi- cient anymore for the 휇-calculus. ̃휇 Instead, we now split the 퐴푥 rule into two verification rules: 푉 푅 and 푉 퐿. These rules complete a proof of a right active judgement and a left active judgement respectively. 푉 푅 푉 퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐴 ∣ ∣ 훼 ∶ 퐴 ⊢ 훼 ∶ 퐴 12 THE 휇-CALCULUS ̃휇

퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … 푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 훼, 훽, 훾 ∈ 퐶표푉 푎푟푖푎푏푙푒 ∶∶= … 푣 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휇훼.푐 푒 ∈ 퐶표푇 푒푟푚 ∶∶= 훼 ∣ ̃휇푥.푐 푐 ∈ 퐶표푚푚푎푛푑 ∶∶= ⟨푣 ∥ 푒⟩

Γ ∈ 퐼푛푝푢푡퐸푛푣 ∶∶= 푥1 ∶ 퐴1, … , 푥푛 ∶ 퐴푛

Δ ∈ 푂푢푡푝푢푡퐸푛푣 ∶∶= 훼1 ∶ 퐴1, … , 훼푛 ∶ 퐴푛 퐽푢푑푔푒푚푒푛푡 ∶∶= 푐 ∶ (Γ ⊢ Δ) ∣ (Γ ⊢ 푣 ∶ 퐴 ∣ Δ) ∣ (Γ ∣ 푒 ∶ 퐴 ⊢ Δ)

Core rules: 푉 푅 푉 퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐴 ∣ ∣ 훼 ∶ 퐴 ⊢ 훼 ∶ 퐴

푐 ∶ (Γ ⊢ 훼 ∶ 퐴, Δ) 푐 ∶ (Γ, 푥 ∶ 퐴 ⊢ Δ) 퐴푅 퐴퐿 Γ ⊢ 휇훼.푐 ∶ 퐴 ∣ Δ Γ ∣ ̃휇푥.푐∶ 퐴 ⊢ Δ

Γ ⊢ 푣 ∶ 퐴 ∣ Δ Γ′ ∣ 푒 ∶ 퐴 ⊢ Δ′ 퐶푢푡 ⟨푣 ∥ 푒⟩ ∶ (Γ′, Γ ⊢ Δ′, Δ)

Figure 2.10: The core 휇-calculus ̃휇

Next, we alter the cut rule to adopt the new judgement definitions. Whereas before it cut a proposition out of the hypotheses and the consequences, it now cuts out an active left judgement and an active right judgement, and combines them into a command. In practice, this rule forces the term and the co-term in a command to be of the same type.

Γ ⊢ 푣 ∶ 퐴 ∣ Δ Γ′ ∣ 푒 ∶ 퐴 ⊢ Δ′ 퐶푢푡 ⟨푣 ∥ 푒⟩ ∶ (Γ′, Γ ⊢ Δ′, Δ)

Finally, we add activation rules, which allow a passive judgement to be turned into an active one by creating either a 휇 or a -construction.̃휇 푐 ∶ (Γ ⊢ 훼 ∶ 퐴, Δ) 푐 ∶ (Γ, 푥 ∶ 퐴 ⊢ Δ) 퐴푅 퐴퐿 Γ ⊢ 휇훼.푐 ∶ 퐴 ∣ Δ Γ ∣ ̃휇푥.푐∶ 퐴 ⊢ Δ

2.2.2.2 Evaluation

Like the 휆-calculus, the core 휇-calculus ̃휇 evaluates according to serveral simple rules. First, the 휂휇 and 휂 ̃휇 reduction rules. These allow 휇 and -constructions̃휇 to be removed when their argument is directly applied in its command. As the name suggests, the 휂휇 and 휂 ̃휇 rules are similar to the 휆-calculus’ 휂 rule. 휇훼.⟨푣 ∥ 훼⟩ ≻ 푣 ̃휇푥.⟨푥∥ 푒⟩ ≻ 푒 휂휇 휂 ̃휇

Next, we have the 휇 and -rewriting̃휇 rules. These specify how commands can be evaluated, similar to 훽-reduction in the 휆-calculus.

⟨휇훼.푐 ∥ 푒⟩ ≻휇 푐{푒/훼} ⟨푣 ∥ ̃휇푥.푐⟩ ≻ ̃휇 푐{푣/푥}

The attentive reader may have noticed that the 휇 and -rewriting̃휇 rules get into conflict in specific situations. What happens with a command that has a 휇-construction on the left side, and a -constructioñ휇 THE 휇-CALCULUS ̃휇 13

on the right side? With the current rules, we could rewrite ⟨휇훼.푐1 ∥ ̃휇푥.푐2⟩ to both 푐1{ ̃휇푥.푐2/훼} and 푐2{휇훼.푐1/푥}. Which result is the correct one? Actually, both results are correct, it is simply a matter of preference. As Curien and Herbelin for- malised, the two evaluation options represent different evaluation strategies: call-by-name and call-by- value [CH00]. Call-by-value prioritises 휇-rewriting over -rewriting,̃휇 while call-by-name prioritises -̃휇 rewriting over 휇-rewriting.

2.2.3 Adding constructors and types So far, we have seen the 휇-calculus ̃휇 where all types are atomic type variables. The next step is to add basic types connectors and associated constructors, as we have done for the 휆-calculus in Figure 2.9. First, we will add product types (×). Once again, we define tuples as a constructor for product types. However, where the 휆-calculus has 휋1 and 휋2 as terms, they are co-terms in the 휇-calculus. ̃휇

푣 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ (푣, 푣) 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ⋯ ∣ 휋1[푒] ∣ 휋2[푒] Just like for the 휆-calculus, we can use the logical foundations of the 휇-calculus ̃휇 in the definitions of the typing rules for products. We have previously established the relationship between product types and conjunction. We use the logical rules for conjunction to derive typing rules. Γ ⊢ 퐴, Δ Γ ⊢ 퐵, Δ Γ ⊢ 푣 ∶ 퐴 ∣ Δ Γ ⊢ 푣′ ∶ 퐵 ∣ Δ ∧푅 ×푅 Γ ⊢ 퐴 ∧ 퐵, Δ Γ ⊢ (푣, 푣′) ∶ 퐴 × 퐵 ∣ Δ

Γ, 퐴 ⊢ Δ Γ ∣ 푒 ∶ 퐴 ⊢ Δ ∧퐿1 ×퐿1 Γ, 퐴 ∧ 퐵 ⊢ Δ Γ ∣ 휋1[푒] ∶ 퐴 × 퐵 ⊢ Δ Γ, 퐵 ⊢ Δ Γ ∣ 푒 ∶ 퐵 ⊢ Δ ∧퐿2 ×퐿2 Γ, 퐴 ∧ 퐵 ⊢ Δ Γ ∣ 휋2[푒] ∶ 퐴 × 퐵 ⊢ Δ

Note that 휋1 and 휋2 take co-terms as arguments, instead of terms. Unlike in the 휆-calculus, it is not ′ possible to insert a term (푣, 푣 ) into 휋1 and get 푣 as a result. Instead, a consumer of 푣 is passed to 휋1 This allows for the following reduction rules:

⟨(푣, 푣′) ∥ 휋 [푒]⟩ ≻ ⟨푣 ∥ 푒⟩ 1 휋1 ⟨(푣, 푣′) ∥ 휋 [푒]⟩ ≻ ⟨푣′ ∥ 푒⟩ 2 휋2

It may come as no surprise that we add sum types to the calculus next. Sum types consist of two constructors, 휄1 and 휄2, and one consumer. In the 휆-calculus, this consumer was modeled as a 푐푎푠푒 표푓 expression. In the 휇-calculus, ̃휇 the syntax [푒, 푒′] is used, meaning a pair of two co-terms. For intuition’s sake, this can be read as

′ 푐푎푠푒 푣 표푓 휄1(푥) ⇒ ⟨푥 ∥ 푒⟩ ∥ 휄2(푦) ⇒ ⟨푦 ∥ 푒 ⟩

In words, whenever [푒, 푒′] encounters a term 푣 within a command, this resolves to a computation of the ′ argument to 휄1 and 푒, or the argument to 휄2 and 푒 , according to the shape of 푣. Formally:

⟨휄 (푥) ∥ [푒, 푒′]⟩ ≻ ⟨푥 ∥ 푒⟩ 1 휄1 ⟨휄 (푦) ∥ [푒, 푒′]⟩ ≻ ⟨푦 ∥ 푒′⟩ 2 휄2

The typechecking rules can be derived from the logical rules for disjunction in the sequent calculus once more. 14 THE 휇-CALCULUS ̃휇

Γ ⊢ 퐴, Δ Γ ⊢ 푣 ∶ 퐴 ∣ Δ ∨푅1 +푅1 Γ ⊢ 퐴 ∨ 퐵, Δ Γ ⊢ 휄1(푣) ∶ 퐴 + 퐵 ∣ Δ

Γ ⊢ 퐵, Δ Γ ⊢ 푣 ∶ 퐵 ∣ Δ ∨푅2 +푅2 Γ ⊢ 퐴 ∨ 퐵, Δ Γ ⊢ 휄2(푣) ∶ 퐴 + 퐵 ∣ Δ

Γ, 퐴 ⊢ Δ Γ, 퐵 ⊢ Δ Γ ∣ 푒 ∶ 퐴 ⊢ Δ Γ ∣ 푒′ ∶ 퐵 ⊢ Δ ∨퐿 +퐿 Γ, 퐴 ∨ 퐵 ⊢ Δ Γ ∣ [푒, 푒′] ∶ 퐴 + 퐵 ⊢ Δ

2.2.4 User-defined (co-)datatypes

So far we have extended the calculus with sum and product types. Using these together allow complex data structures to be modeled. However, this is not intuitive, since the structures cannot be named. As an alternative, Downen added user-defined (co-)datatypes to the 휇-calculus ̃휇 in his PhD thesis [Dow17]. The calculus is extended with declarations of both datatypes and co-datatypes. Datatypes consist of any number of constructors. Co-datatypes are defined by any number of observers instead. Both con- structors and observers can take any number of arguments, which can be both terms and co-terms. The (co-)datatype declarations specify of which types the arguments to constructors and observers should be. The constructors and observers can be used as terms and co-terms respectively. #» 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) 퐹 , 퐺 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= … 퐾 ∈ 퐶표푛푠푡푟푢푐푡표푟 ∶∶= … 푂 ∈ 푂푏푠푒푟푣푒푟 ∶∶= … #» # #» #» #» » 푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 ∶ 퐴 ⊢ 퐹 (푋) ∣ 퐵 #» # #» #» #» » ∣ 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂 ∶ 퐴 ∣ 퐹 (푋) ⊢ 퐵

In this notation of declarations, each#» data#» declaration#» can define any#» number of constructors. Each constructor has its own type system 퐴 ⊢ 퐹 (푋) ∣ 퐵.#» Here, the vector 퐴 contains all the types of the term arguments that the constructor takes. Similarly, 퐵 represents the collection of the types of the co- term arguments that the constructor takes. 퐹 (푋) #»is the type#» that#» the constructor will have with all of its arguments. The#» type constructor declaration 퐾 ∶ 퐴 ⊢ 퐹 (푋) ∣ 퐵 essentially#» means that constructor 퐾 has type 퐹 (푋),#» given that it is supplied with term arguments with the types 퐴, and co-term arguments with the types 퐵. This syntax works the exact same way for co-datatype declarations and observers, of course. As an example, product and sum types can be implemented this way:

푑푎푡푎 퐴 × 퐵 푤ℎ푒푟푒 (_, _) ∶ 퐴, 퐵 ⊢ 퐴 × 퐵 ∣

푑푎푡푎 퐴 + 퐵 푤ℎ푒푟푒 휄1(_) ∶ 퐴 ⊢ 퐴 + 퐵 ∣

∣ 휄2(_) ∶ 퐵 ⊢ 퐴 + 퐵 ∣

Underscores (_) are used as placeholders for (co-)terms in the constructors of both declarations, meaning that for the constructor (푥, 푦) with type 퐴 × 퐵, 푥 must have type 퐴, and 푦 must have type 퐵. Using these user-defined declarations of sum and product types has one drawback in comparison to the hard-coded variant that we have seen in Section 2.2.3: the declarations do not offer ways to consume the created data. To be able to do so anyway, the 휇 and ̃휇 constructions are altered. Whereas before, ̃휇 took a variable as its argument, it will now take a pattern. Likewise, 휇 will no longer take a co-variable, but a co-pattern instead. Patterns consist of either a single variable, or a constructor with any number of sub(co-)variables. Of course, co-patterns are the exact same thing, but using co-variables and observers THE 휇-CALCULUS ̃휇 15 instead of variables and constructors. In addition to the introduction of patterns, 휇 and ̃휇 can now take multiple pairs of patterns and commands. #» #» 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾(훼, 푥 ) #» #» ̃푝∈ 퐶표푃 푎푡푡푒푟푛 ∶∶= 훼 ∣ 푂[ 푥 , 훼] # » #» #» 푣 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휇( ̃푝.푐) ∣ 퐾( 푒 , 푣 ) # » #» #» 푒 ∈ 퐶표푇 푒푟푚 ∶∶= 훼 ∣ ̃휇[푝.푐] ∣ 푂[ 푣 , 푒 ]

Formal typing rules for these generalised (co-)data declarations are needed. Because of the nested structure of the constructors and observers, these rules are very complicated. for simplicity’s sake, let us first look at sum types, and generalise from there. As we have just seen, sum types consist oftwo constructors: 휄1 and 휄2. Both take one term argument. When we pass a term 푣 of type 퐴 to 휄1, the new term has type 퐴 + 퐵. If we instead pass 푣 to 휄2, the result’s type is 퐵 + 퐴. So, given that we have

푑푎푡푎 퐴 + 퐵 푤ℎ푒푟푒 휄1(_) ∶ 퐴 ⊢ 퐴 + 퐵 ∣

∣ 휄2(_) ∶ 퐵 ⊢ 퐴 + 퐵 ∣ we know both constructors turn an argument into a sum type as follows: Γ ⊢ 푣 ∶ 퐴 ∣ Δ Γ ⊢ 푣 ∶ 퐵 ∣ Δ 휄1 휄2 Γ ⊢ 휄1(푣) ∶ 퐴 + 퐵 ∣ Δ Γ ⊢ 휄2(푣) ∶ 퐴 + 퐵 ∣ Δ # »푖 #» # »푗 #» # »푗 For a generic data declaration 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 ∶ 퐴푖푗 ⊢ 퐹 (푋) ∣ 퐵푖푗 , this strategy gives us the following typing rule: # # » »푗 # # » »푗 Γ ∣ 푒 ∶ 퐵 {퐶/푋} ⊢ Δ Γ ⊢ 푣 ∶ 퐴 {퐶/푋} ∣ Δ 푖푗 푖푗 퐹 푅 #» #» #» 퐾푖 Γ ⊢ 퐾푖( 푒 , 푣 ) ∶ 퐹 (퐶) ∣ Δ In words, this rule tells us that for each constructor in the declaration, we are able to construct type 퐹 , substituting 푋 for whatever types we decide to use, as long as they match the declaration. In the context of sum types, 퐹 would be +, 푋 would be 퐴 and 퐵, 퐾1 would be 휄1, 퐾2 would be 휄2, 퐴1 would be a single 퐴, 퐴2 a single 퐵, and both 퐵1 and 퐵2 would be empty. As an example, assuming a variable 푥 with type 퐶, this rule can be used to prove that 휄1(푥) has type 퐶 + 퐵. 푉 푅 Γ ⊢ 푥 ∶ 퐶 ∣ Δ 퐹 푅 휄1 Γ ⊢ 휄1(푥) ∶ 퐶 + 퐵 ∣ Δ The same logic can be used to define typing rules for co-datatypes and observers. For a given decla- # »푖 #» # »푗 #» # »푗 ration 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂푖 ∶ 퐴푖푗 ∣ 퐺(푋) ⊢ 퐵푖푗 , we have the rule # # » »푗 # # » »푗 Γ ⊢ 푣 ∶ 퐴 {퐶/푋} ∣ Δ Γ ∣ 푒 ∶ 퐵 {퐶/푋} ⊢ Δ 푖푗 푖푗 퐺퐿 #» #» #» 푂푖 Γ ∣ 푂푖[ 푣 , 푒 ] ∶ 퐺(퐶) ⊢ Δ The 퐺퐿 rule is the exact same as the 퐹 푅 rule, except it introduces an observer instead of a constructor. Observers and constructors are not the only extensions to the calculus we have made. (Co-)patterns have been added as well, which have caused 휇 and ̃휇 definitions to be altered. Before, activation rules 퐴푅 and 퐴퐿 could be used to typecheck 휇 and ̃휇 (co-)terms. With the renewed definitions, those rules are not going to be sufficient anymore. Let us analyse the 퐴푅 rule to find its essence, which can be used for a new rule that includes patterns. 16 THE 휇-CALCULUS ̃휇

푐 ∶ (Γ ⊢ 훼 ∶ 퐴, Δ) 퐴푅 Γ ⊢ 휇훼.푐 ∶ 퐴 ∣ Δ

The 퐴푅 rule allows us to turn a command into a 휇-term, using a co-variable that exists in the output environment as the parameter. The new rule should do the same thing, but for a co-pattern. In practice, this means that every (co-)variable in the co-pattern must be known, and that the observer in the co- pattern must be well-typed. We introduce two functions. One that takes a pattern or co-pattern and a type, and matches every variable that occurs in the (co-)pattern with the correct type, to finally return an input environment containing all the variables in the original (co-)pattern. The other function we introduce is one that does the exact same, but with co-variables, which will therefore return an output environment.

푉 푎푟푠푇 푦푝푒푠(푥, 퐶) = 푥 ∶ 퐶 푉 푎푟푠푇 푦푝푒푠(훼, 퐶) = ∅ # »푖 #» # »푗 #» # »푗 Given 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 ∶ 퐴푖푗 ⊢ 퐹 (푋) ∣ 퐵푖푗 #» #» #» # »푗 푉 푎푟푠푇 푦푝푒푠(퐾푖(훼푖, 푥푖), 퐹 (퐶)) = 푥푖푗 ∶ 퐴푖푗{퐶/푋} # »푖 #» # »푗 #» # »푗 Given 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂푖 ∶ 퐴푖푗 ∣ 퐺(푋) ⊢ 퐵푖푗 #» #» #» # »푗 푉 푎푟푠푇 푦푝푒푠(푂푖[푥푖, 훼푖], 퐺(퐶)) = 푥푖푗 ∶ 퐴푖푗{퐶/푋}

퐶표푉 푎푟푠푇 푦푝푒푠(훼, 퐶) = 훼 ∶ 퐶 퐶표푉 푎푟푠푇 푦푝푒푠(푥, 퐶) = ∅ # »푖 #» # »푗 #» # »푗 Given 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂푖 ∶ 퐴푖푗 ∣ 퐺(푋) ⊢ 퐵푖푗 #» #» #» # »푗 퐶표푉 푎푟푠푇 푦푝푒푠(푂푖[푥푖, 훼푖], 퐺(퐶)) = 훼푖푗 ∶ 퐵푖푗{퐶/푋} # »푖 #» # »푗 #» # »푗 Given 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 ∶ 퐴푖푗 ⊢ 퐹 (푋) ∣ 퐵푖푗 #» #» #» # »푗 퐶표푉 푎푟푠푇 푦푝푒푠(퐾푖(훼푖, 푥푖), 퐹 (퐶)) = 훼푖푗 ∶ 퐵푖푗{퐶/푋}

As an example, the result of 푉 푎푟푠푇 푦푝푒푠((푥, 푦), 퐴×(퐵×퐶)) would be the environment 푥 ∶ 퐴, 푦 ∶ 퐵 × 퐶. Now that we are able to process entire patterns and co-patterns, we can define new rules for 휇 and ̃휇 constructions.

# »푖 푐푖 ∶ (Γ, 푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴) ⊢ 퐶표푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴), Δ) # »푖 퐹 퐿 Γ ∣ ̃휇[푝푖.푐푖 ] ∶ 퐴 ⊢ Δ

# »푖 푐푖 ∶ (Γ, 푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴) ⊢ 퐶표푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴), Δ) # »푖 퐺푅 Γ ⊢ 휇( 푖̃푝 .푐푖 ) ∶ 퐴 ∣ Δ

These rules can be used to typecheck 휇 and ̃휇 constructions, such as the judgement Γ ∣ ̃휇[휄1(푥).푐1 ∣ 휄2(푦).푐2] ∶ 푋 + 푌 ⊢ Δ: 푐 ∶ (Γ, 푥 ∶ 푋 ⊢ Δ) 푐 ∶ (Γ, 푦 ∶ 푌 ⊢ Δ) 1 2 퐹 퐿 Γ ∣ ̃휇[휄1(푥).푐1 ∣ 휄2(푦).푐2] ∶ 푋 + 푌 ⊢ Δ Figure 2.11 shows the full definition of the 휇-calculus ̃휇 with user-defined (co-)datatypes and its typing rules. THE 휇-CALCULUS ̃휇 17

#» 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) 푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … 퐹 , 퐺 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= … 퐾 ∈ 퐶표푛푠푡푟푢푐푡표푟 ∶∶= … 푂 ∈ 푂푏푠푒푟푣푒푟 ∶∶= … 푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 훼, 훽, 훾 ∈ 퐶표푉 푎푟푖푎푏푙푒 ∶∶= … #» #» #» #» 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾( ̃푝, 푝 ) ̃푝∈ 퐶표푃 푎푡푡푒푟푛 ∶∶= 훼 ∣ 푂[ 푝 , ̃푝] # » #» #» # » #» #» 푣 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휇( ̃푝.푐) ∣ 퐾( 푒 , 푣 ) 푒 ∈ 퐶표푇 푒푟푚 ∶∶= 훼 ∣ ̃휇[푝.푐] ∣ 푂[ 푣 , 푒 ] #» # #» #» #» » 푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 ∶ 퐴 ⊢ 퐹 (푋) ∣ 퐵 #» # #» #» #» » ∣ 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂 ∶ 퐴 ∣ 퐹 (푋) ⊢ 퐵 푐 ∈ 퐶표푚푚푎푛푑 ∶∶= ⟨푣 ∥ 푒⟩

Γ ∈ 퐼푛푝푢푡퐸푛푣 ∶∶= 푥1 ∶ 퐴1, … , 푥푛 ∶ 퐴푛

Δ ∈ 푂푢푡푝푢푡퐸푛푣 ∶∶= 훼1 ∶ 퐴1, … , 훼푛 ∶ 퐴푛 퐽푢푑푔푒푚푒푛푡 ∶∶= 푐 ∶ (Γ ⊢ Δ) ∣ (Γ ⊢ 푣 ∶ 퐴 ∣ Δ) ∣ (Γ ∣ 푒 ∶ 퐴 ⊢ Δ)

Core rules: 푉 푅 푉 퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐴 ∣ ∣ 훼 ∶ 퐴 ⊢ 훼 ∶ 퐴

Γ ⊢ 푣 ∶ 퐴 ∣ Δ Γ′ ∣ 푒 ∶ 퐴 ⊢ Δ′ 퐶푢푡 ⟨푣 ∥ 푒⟩ ∶ (Γ′, Γ ⊢ Δ′, Δ)

Logical rules: # »푖 #» # »푗 #» # »푗 Given 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 ∶ 퐴푖푗 ⊢ 퐹 (푋) ∣ 퐵푖푗 : # # » »푗 # # » »푗 Γ ∣ 푒 ∶ 퐵 {퐶/푋} ⊢ Δ Γ ⊢ 푣 ∶ 퐴 {퐶/푋} ∣ Δ 푖푗 푖푗 퐹 푅 #» #» #» 퐾푖 Γ ⊢ 퐾푖( 푒 , 푣 ) ∶ 퐹 (퐶) ∣ Δ

# »푖 푐푖 ∶ (Γ, 푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴) ⊢ 퐶표푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴), Δ) # »푖 퐹 퐿 Γ ∣ ̃휇[푝푖.푐푖 ] ∶ 퐴 ⊢ Δ

# »푖 #» # »푗 #» # »푗 Given 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂푖 ∶ 퐴푖푗 ∣ 퐺(푋) ⊢ 퐵푖푗 :

# »푖 푐푖 ∶ (Γ, 푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴) ⊢ 퐶표푉 푎푟푠푇 푦푝푒푠(푝푖, 퐴), Δ) # »푖 퐺푅 Γ ⊢ 휇( 푖̃푝 .푐푖 ) ∶ 퐴 ∣ Δ

# # » »푗 # # » »푗 Γ ⊢ 푣 ∶ 퐴 {퐶/푋} ∣ Δ Γ ∣ 푒 ∶ 퐵 {퐶/푋} ⊢ Δ 푖푗 푖푗 퐺퐿 #» #» #» 푂푖 Γ ∣ 푂푖[ 푣 , 푒 ] ∶ 퐺(퐶) ⊢ Δ

Figure 2.11: The 휇-calculus ̃휇 with user-defined (co-)datatypes. 18 THE 휇-CALCULUS ̃휇

2.2.4.1 Evaluation

In addition to new typing rules, we also need new reduction rules to define evaluation for the new user- defined (co-)datatypes. We already have the 휇 and ̃휇 rewriting rules. Those only work on 휇 and ̃휇 (co- )terms where there is only a single (co-)pattern, and it consists of a one simple (co-)variable. Fortunately, we are able to rewrite more complicated (co-)patterns to a series of simple (co-)patterns. We could for example rewrite the command ⟨(푣, 푣′) ∥ ̃휇[(푥, 푦).푐]⟩ to ⟨푣 ∥ ̃휇푥.⟨푣′ ∥ ̃휇푦.푐⟩⟩. This way both 푣 and 푣′ get matched to 푥 and 푦 respectively, and every ̃휇 co-term consist of single variables as patterns. After rewriting the 휇 and ̃휇 rewriting rules can be used to further evaluate the command.

′ ′ ′ ⟨푣 ∥ ̃휇푥.⟨푣 ∥ ̃휇푦.푐⟩⟩ ≻ ̃휇 ⟨푣 ∥ ̃휇푦.푐⟩{푣/푥} ≻ ̃휇 푐{푣/푥, 푣 /푦}

What actually happens during the rewriting of patterns, is that every sub-pattern and sub-co-pattern is taken from the input (co-)pattern and moved to a separate command. Of course, this only happens if the constructor or observer in the (co-)pattern matches the constructor or observer that is supplied to the pattern in the original command. We can formalise these rewriting rules, called 훽퐹 and 훽퐺, as follows: #» #» #» #» #» #» #» #» ⟨퐾 ( 푒 , 푣 ) ∥ ̃휇[⋯ ∣ 퐾 ( ̃푝, 푝 ).푐 ∣ … ]⟩ ≻ ⟨휇 ̃푝.⟨ 푣 ∥ ̃휇 푝 .푐 ⟩ ∥ 푒 ⟩ 푖 푖 푖 훽퐹 푖 #» #» #» #» #» #» #» #» ⟨휇(⋯ ∣ 푂 [ 푝 , ̃푝].푐 ∣ … ) ∥ 푂 [ 푣 , 푒 ]⟩ ≻ ⟨ 푣 ∥ ̃휇 푝 .⟨휇 ̃푝.푐 ∥ 푒 ⟩⟩ 푖 푖 푖 훽퐺 푖

The 훽퐹 and 훽퐺 rules also work for (co-)patterns that have sub-(co-)patterns that do not consist of a single (co-)variable. The new rules can, for instance, be used to evaluate the command ⟨((푣1, 푣2), 푣3) ∥ ̃휇[((푥, 푦), 푧).푐]⟩ as follows:

⟨((푣1, 푣2), 푣3) ∥ ̃휇[((푥, 푦), 푧).푐]⟩ ≻ 훽퐹

⟨(푣1, 푣2) ∥ ̃휇[(푥, 푦).⟨푣3 ∥ ̃휇푧.푐⟩]⟩ ≻ 훽퐹

⟨푣1 ∥ ̃휇푥.⟨푣2 ∥ ̃휇푦.⟨푣3 ∥ ̃휇푧.푐⟩⟩⟩

≻ ̃휇

⟨푣2 ∥ ̃휇푦.⟨푣3 ∥ ̃휇푧.푐⟩⟩{푣1/푥}

≻ ̃휇

⟨푣3 ∥ ̃휇푧.푐⟩{푣1/푥, 푣2/푦}

≻ ̃휇

푐{푣1/푥, 푣2/푦, 푣3/푧}

2.2.4.2 Nested evaluation

Despite the new rules, there are still situations where evaluation gets stuck, even though it should not be. This occurs whenever a 휇-term or -co-term̃휇 exists within a constructor or observer. For example, we are currently not able to further evaluate the command ⟨(푥, 휇훽.⟨푦 ∥ 훽⟩) ∥ 훼⟩, even though we know that 휇훽.⟨푦 ∥ 훽⟩ could be simplified to just 푦 by the 휂휇 reduction rule. To allow those inner (co-)terms to be lifted, we first need to define values and co-values. A (co-)value is a (co-)term that cannot be evaluated any further. Whether a (co-)term is or is not a (co-)value, depends on the evaluation context. Some (co-)terms can be evaluated further under call-by-value(풱), while they have already reached their final form under call-by-name(풩) and vice versa. Under call-by-value, every co-term is a co-value, but only some terms are co-terms: variables, constructors where every sub-term is a value, and 휇-terms for which at least one of the co-patterns contains an observer. Under call-by-name, this is almost exactly reversed: every term THE 휇-CALCULUS ̃휇 19 is a value, but only co-variables, observers where every sub-co-term is a co-value, and -co-terms̃휇 with at least one constructor in any of its patterns are co-values. # » #» #» #» #» 푉 ∈ 푉 푎푙푢푒풱 ∶∶= 푥 ∣ 퐾( 푒 , 푉 ) ∣ 휇(푂[ 푝 , ̃푝].푐)

퐸 ∈ 퐶표푉 푎푙푢푒풱 ∶∶= 푒

푉 ∈ 푉 푎푙푢푒풩 ∶∶= 푣 # » #» #» #» #» 퐸 ∈ 퐶표푉 푎푙푢푒풩 ∶∶= 훼 ∣ 푂[ 푣 , 퐸] ∣ ̃휇[퐾( ̃푝, 푝 ).푐]

This means that under call-by-value, constructors that have sub-terms that are not values and 휇- terms that have just a single co-pattern, which is a single co-variable, are non-values. Similarly, under call-by-name, observers that have sub-co-terms and -co-terms̃휇 that have a single pattern, consisting of a variable, are non-co-values. Now that we know which (co-)terms should be liftable, we can define lifting rules 휍: #» #» ′ #» #» #» #» ′ 퐾(퐸, 푒 , 푒 , 푣 ) ≻휍 휇훼.⟨휇훽.⟨퐾(퐸, 훽, 푒 , 푣 ) ∥ 훼⟩ ∥ 푒 ⟩ #» #» #» ′ #» ′ #» #» 퐾(퐸, 푉 , 푣 , 푣 ) ≻휍 휇훼.⟨푣 ∥ ̃휇푦.⟨퐾( 푒 , 푉 , 푦, 푣 ) ∥ 훼⟩⟩ #» #» ′ #» #» ′ #» #» 푂[푉 , 푣 , 푣 , 푒 ] ≻휍 ̃휇푥.⟨푣 ∥ ̃휇푦.⟨푥 ∥ 푂[푉 , 푦, 푣 , 푒 ]⟩⟩ #» #» #» ′ #» #» #» ′ 푂[푉 , 퐸, 푒 , 푒 ] ≻휍 ̃휇푥.⟨휇훽.⟨푥 ∥ 푂[ 푣 , 퐸, 훽, 푒 ]⟩ ∥ 푒 ⟩ where 푥, 푦, 훼 and 훽 are free (co-)variables, and 푣′ and 푒′ are non-values and non-co-values, respectively. Now, we can use the new lifting rules to further evaluate our example, but only under call-by-value. Under call-by-name, ⟨(푥, 휇훽.⟨푦 ∥ 훽⟩) ∥ 훼⟩ cannot be evaluated anyfurther, because 휇훽.⟨푦 ∥ 훽⟩ is seen as a value in this strategy.

⟨(푥, 휇훽.⟨푦 ∥ 훽⟩) ∥ 훼⟩

≻휍 ⟨휇훾.⟨휇훽.⟨푦 ∥ 훽⟩ ∥ ̃휇푧.⟨(푥, 푧) ∥ 훾⟩⟩ ∥ 훼⟩

≻휇 ⟨휇훽.⟨푦 ∥ 훽⟩ ∥ ̃휇푧.⟨(푥, 푧) ∥ 훼⟩⟩

≻휇 ⟨푦 ∥ ̃휇푧.⟨(푥, 푧) ∥ 훼⟩⟩

≻ ̃휇 ⟨(푥, 푦) ∥ 훼⟩

The complete definition of all evaluation rules can be found in Figure 2.12. What we have not seen implemented in the 휇-calculus ̃휇 with user-defined (co-)datatypes are the pre- viously built-in consumers to sum and product types. Before, we could consume them using the co-term ′ [푒, 푒 ] for sum types, and 휋1 and 휋2 for product types. However, since datatypes cannot have observers, and co-datatypes cannot have constructors, these have disappeared from the new calculus. To re-integrate them, we need to define them as functions.

2.2.5 Functions in the 휇-calculus ̃휇 While functions are an integral built-in piece of the 휆-calculus, they fulfill no special role in the 휇- ̃휇 calculus, and are therefore not built into the calculus. However, that does not mean that it is not possible to define functions in the 휇-calculus; ̃휇 we just need to declare the function co-datatype and its observer first. 20 THE 휇-CALCULUS ̃휇

Evaluation strategies ∶ call-by-value 풱 ∣ call-by-name 풩

# » #» #» #» #» 푉 ∈ 푉 푎푙푢푒풱 ∶∶= 푥 ∣ 퐾( 푒 , 푉 ) ∣ 휇(푂[ 푝 , ̃푝].푐)

퐸 ∈ 퐶표푉 푎푙푢푒풱 ∶∶= 푒

푉 ∈ 푉 푎푙푢푒풩 ∶∶= 푣 # » #» #» #» #» 퐸 ∈ 퐶표푉 푎푙푢푒풩 ∶∶= 훼 ∣ 푂[ 푣 , 퐸] ∣ ̃휇[퐾( ̃푝, 푝 ).푐]

Evaluation rules

⟨휇훼.푐 ∥ 푒⟩ ≻휇 푐{푒/훼}

⟨푣 ∥ ̃휇푥.푐⟩ ≻ ̃휇 푐{푣/푥} 휇훼.⟨푣 ∥ 훼⟩ ≻ 푣 휂휇 ̃휇푥.⟨푥∥ 푒⟩ ≻ 푒 휂 ̃휇 #» #» #» #» #» #» #» #» ⟨퐾 ( 푒 , 푣 ) ∥ ̃휇[⋯ ∣ 퐾 ( ̃푝, 푝 ).푐 ∣ … ]⟩ ≻ ⟨휇 ̃푝.⟨ 푣 ∥ ̃휇 푝 .푐 ⟩ ∥ 푒 ⟩ 푖 푖 푖 훽퐹 푖 #» #» #» #» #» #» #» #» ⟨휇(⋯ ∣ 푂푖[ 푝 , ̃푝].푐푖 ∣ … ) ∥ 푂푖[ 푣 , 푒 ]⟩ ≻훽 ⟨ 푣 ∥ ̃휇 푝 .⟨휇 ̃푝.푐푖 ∥ 푒 ⟩⟩ #» 퐺 #» ′ #» #» #» #» ′ 퐾(퐸, 푒 , 푒 , 푣 ) ≻휍 휇훼.⟨휇훽.⟨퐾(퐸, 훽, 푒 , 푣 ) ∥ 훼⟩ ∥ 푒 ⟩ #» #» #» ′ #» ′ #» #» 퐾(퐸, 푉 , 푣 , 푣 ) ≻휍 휇훼.⟨푣 ∥ ̃휇푦.⟨퐾( 푒 , 푉 , 푦, 푣 ) ∥ 훼⟩⟩ #» #» ′ #» #» ′ #» #» 푂[푉 , 푣 , 푣 , 푒 ] ≻휍 ̃휇푥.⟨푣 ∥ ̃휇푦.⟨푥 ∥ 푂[푉 , 푦, 푣 , 푒 ]⟩⟩ #» #» #» ′ #» #» #» ′ 푂[푉 , 퐸, 푒 , 푒 ] ≻휍 ̃휇푥.⟨휇훽.⟨푥 ∥ 푂[ 푣 , 퐸, 훽, 푒 ]⟩ ∥ 푒 ⟩

Figure 2.12: Evaluation rules and strategies in the 휇-calculus ̃휇 with user-defined (co-)datatypes. THE 휇-CALCULUS ̃휇 21

A function is something that takes a term of one type, and produces a new term of a second type. Of course, in the 휇-calculus, ̃휇 producing a new term and returning it is not as trivial as it is in the 휆-calculus. Producing and returning a term requires it to be part of a command, which in turn requires a co-term of the same type. The observer for the function type will therefore take a term of the input type, and a co-term of the output type.

푐표푑푎푡푎 퐴 → 퐵 푤ℎ푒푟푒 _ ⋅ _ ∶ 퐴 ∣ 퐴 → 퐵 ⊢ 퐵

The observer 푣 ⋅ 푒 represents a call-stack. Using this new definition, we can re-implement 휋1 and 휋2, the product type’s consumers.

휋1 = 휇((푥, 푦) ⋅ 훼.⟨푥 ∥ 훼⟩) ∶ 퐴 × 퐵 → 퐴

휋2 = 휇((푥, 푦) ⋅ 훼.⟨푦 ∥ 훼⟩) ∶ 퐴 × 퐵 → 퐵

While 휋1 and 휋2 were co-terms before, they are terms now. As such, 휋1 and 휋2 are strictly no longer consumers, but rather producers of functions. This changes the way they are used. Before, we could ′ ′ extract 푣 out of (푣, 푣 ) using the command ⟨(푣, 푣 ) ∥ 휋1[푒]⟩, which would evaluate to ⟨푣 ∥ 푒⟩. With ′ the new version 휋1, we build a call-stack containing (푣, 푣 ) and the output co-term 푒 instead: ⟨휋1 ∥ (푣, 푣′) ⋅ 푒⟩, or the fully expanded version ⟨휇((푥, 푦) ⋅ 훼.⟨푥 ∥ 훼⟩) ∥ (푣, 푣′) ⋅ 푒⟩. This command eventually evaluates to the same result ⟨푣 ∥ 푒⟩, according to the new evaluation rules: ⟨휇((푥, 푦) ⋅ 훼.⟨푥 ∥ 훼⟩) ∥ (푣, 푣′) ⋅ 푒⟩ ≻ 훽퐺 ⟨(푣, 푣′) ∥ ̃휇((푥,푦).⟨휇훼.⟨푥 ∥ 훼⟩ ∥ 푒⟩)⟩ ≻ 훽퐹 ⟨푣 ∥ ̃휇푥.⟨푣′ ∥ ̃휇푦.⟨휇훼.⟨푥 ∥ 훼⟩ ∥ 푒⟩⟩⟩

≻ ̃휇 ⟨푣′ ∥ ̃휇푦.⟨휇훼.⟨푣 ∥ 훼⟩ ∥ 푒⟩⟩

≻ ̃휇 ⟨휇훼.⟨푣 ∥ 훼⟩ ∥ 푒⟩

≻휇 ⟨푣 ∥ 푒⟩

With our new definitions, 휋1 and 휋2 are the 휇-calculus ̃휇 equivalents of the Haskell functions 푓푠푡 and 푠푛푑. The consumer for sum types [푒, 푒′] is different. Since this co-term requires two separate sub-co- terms, and call-stacks only contain one sub-co-term, we cannot implement [푒, 푒′] without first declaring

a wrapper co-datatype 퐴 & 퐵: &

푐표푑푎푡푎 퐴 & 퐵 푤ℎ푒푟푒 [_, _] ∶∣ 퐴 퐵 ⊢ 퐴, 퐵

With this new co-datatype, we are able to define a function which takes a term of a sum-type, andcon- sumes it, as seen below.

ℎ푎푛푑푙푒푆푢푚 ∶ 퐴 + 퐵 → 퐴 퐵&

ℎ푎푛푑푙푒푆푢푚 = 휇(휄1(푥) ⋅ [훼1, 훼2].⟨푥 ∥ 훼1⟩

∣ 휄2(푦) ⋅ [훼1, 훼2].⟨푦 ∥ 훼2⟩)

Like with product types and 휋1 and 휋2, ℎ푎푛푑푙푒푆푢푚 is a term, instead of a co-term. Once again, this changes the way ℎ푎푛푑푙푒푆푢푚 is used, compared to [푒, 푒′] in the 휇-calculus ̃휇 without user-defined 22 THE 휇-CALCULUS ̃휇

(co-)datatypes. We are still able to evaluate the commands we write using this new system to the same command that we expect to get in the old system.

′ ⟨ℎ푎푛푑푙푒푆푢푚 ∥ 휄1(푣) ⋅ [푒, 푒 ]⟩ ≻ 훽퐺 ′ ⟨휄1(푣) ∥ ̃휇(휄1(푥).⟨휇[[훼1, 훼2].⟨푥 ∥ 훼1⟩] ∥ [푒, 푒 ]⟩)⟩ ≻ 훽퐹 ′ ⟨푣 ∥ ̃휇푥.⟨휇[[훼1, 훼2].⟨푥 ∥ 훼1⟩] ∥ [푒, 푒 ]⟩⟩

≻ ̃휇 ′ ⟨휇[[훼1, 훼2].⟨푣 ∥ 훼1⟩] ∥ [푒, 푒 ]⟩ ≻ 훽퐺 ′ ⟨휇훼1.⟨휇훼2.⟨푣 ∥ 훼1⟩ ∥ 푒 ⟩ ∥ 푒⟩

≻휇 ′ ⟨휇훼2.⟨푣 ∥ 푒⟩ ∥ 푒 ⟩

≻휇 ⟨푣 ∥ 푒⟩

′ ′ ⟨ℎ푎푛푑푙푒푆푢푚 ∥ 휄2(푣 ) ⋅ [푒, 푒 ]⟩ ≻ 훽퐺 ′ ′ ⟨휄2(푣 ) ∥ ̃휇(휄2(푦).⟨휇[[훼1, 훼2].⟨푦 ∥ 훼2⟩] ∥ [푒, 푒 ]⟩)⟩ ≻ 훽퐹 ′ ′ ⟨푣 ∥ ̃휇푦.⟨휇[[훼1, 훼2].⟨푦 ∥ 훼2⟩] ∥ [푒, 푒 ]⟩⟩

≻ ̃휇 ′ ′ ⟨휇[[훼1, 훼2].⟨푣 ∥ 훼2⟩] ∥ [푒, 푒 ]⟩ ≻ 훽퐺 ′ ′ ⟨휇훼1.⟨휇훼2.⟨푣 ∥ 훼2⟩ ∥ 푒 ⟩ ∥ 푒⟩

≻휇 ′ ′ ⟨휇훼2.⟨푣 ∥ 훼2⟩ ∥ 푒 ⟩

≻휇 ⟨푣′ ∥ 푒′⟩

In addition to re-implementing lost consumers, functions and user-defined (co-)datatypes allow us to define much more powerful (co-)terms than we could before. We are now able to declare recursive (co-)datatypes, such as lists and streams.

푑푎푡푎 퐿푖푠푡 퐴 푤ℎ푒푟푒 푐표푑푎푡푎 푆푡푟푒푎푚 퐴 푁푖푙 ∶ ⊢ 퐿푖푠푡 퐴 ∣ 퐻푒푎푑 ∶∣ 푆푡푟푒푎푚 퐴 ⊢ 퐴 퐶표푛푠 ∶ 퐴, 퐿푖푠푡 퐴 ⊢ 퐿푖푠푡 퐴 ∣ 푇 푎푖푙 ∶∣ 푆푡푟푒푎푚 퐴 ⊢ 푆푡푟푒푎푚 퐴 Next, we are able to define functions that alter the structure of these (co-)datatypes. For example, we THE 휇-CALCULUS ̃휇 23 are able to implement map and foldr for lists:

map ∶ (퐴 → 퐵) → 퐿푖푠푡 퐴 → 퐿푖푠푡 퐵

map = 휇(푓 ⋅ 훼.⟨휇(푦 ⋅ 훽.⟨푦 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푓 ∥ 푥 ⋅ ̃휇푧.⟨푚푎푝 ∥ 푓 ⋅ 푥푠 ⋅ ̃휇푧푠. ⟨퐶표푛푠 푧 푧푠 ∥ 훽⟩⟩⟩ ∣ 푁푖푙.⟨푁푖푙 ∥ 훽⟩]⟩) ∥ 훼⟩)

foldr ∶ (퐴 → 퐵 → 퐵) → 퐵 → 퐿푖푠푡 퐴 → 퐵

foldr = 휇(푓 ⋅ 푏 ⋅ 푁푖푙 ⋅ 훼.⟨푁푖푙 ∥ 훼⟩

∣ 푓 ⋅ 푏 ⋅ 퐶표푛푠 푥 푥푠 ⋅ 훼.⟨푓표푙푑푟 ∥ 푓 ⋅ 푏 ⋅ 푥푠 ⋅ ̃휇푏′.⟨푓 ∥ 푥 ⋅ 푏′ ⋅ 훼⟩⟩)

These definitions display the power of the call-stack: We can see exactly which computations happen, and in what order. 24 THE 휇-CALCULUS ̃휇 3 Programming with the 휇-calculus ̃휇

In this chapter we propose a high-level programming language MMH(Mu-Mu-tilde-Haskell), which uses the 휇-calculus ̃휇 as an intermediate language. This means that programs written in MMH are compiled to 휇-calculus ̃휇 programs. In this form, the programs can be typechecked and evaluated using the 휇- ̃휇 calculus’ rules. The 휇-calculus ̃휇 will fulfill the same role for MMH as the 휆-calculus does for several languages, such as Haskell and ML. A good starting point would be transpiling 휆-calculus programs to 휇-calculus ̃휇 programs. With such a transpiler, we are able to evaluate Haskell programs in the 휇-calculus. ̃휇 Next, we can extend Haskell’s syntax to allow the power of the 휇-calculus ̃휇 - the separation of data and co-data - to be exploited.

3.1 Converting 휆-terms to 휇-terms ̃휇

Before we can start translating 휆-programs to 휇-programs, ̃휇 we first need a renewed definition ofthe 휆- calculus; one that supports user-defined datatypes and constructors. This definition is given in Figure 3.1. We define an algorithm Conv, which converts 휆-structures to 휇-structures. ̃휇 There are many sim- ilarities between the 휆-calculus and the 휇-calculus. ̃휇 For example, the type system is nearly the same. In both calculi, types can consist of either a type variable, or a connector with a collection of subtypes. These types are therefore valid in both calculi, meaning that conversion is not necessary. Additionally, the 휆-calculus also has built-in support for function types, while the 휇-calculus ̃휇 does not. For this reason, we require an implementation of function types in the 휇-system. ̃휇 We will hence- forth assume the definition that we have seen before:

푐표푑푎푡푎 퐴 → 퐵 푤ℎ푒푟푒 _ ⋅ _ ∶ 퐴 ∣ 퐴 → 퐵 ⊢ 퐵

Using this definition, we can copy function types from the 휆-calculus as well, meaning that all 휆-calculus types can be safely copied to the 휇-calculus, ̃휇 without any modifications. Datatypes and constructors are similar between the two calculi as well. The only difference is that constructors in the 휇-calculus ̃휇 may contain co-terms in addition to terms, while 휆-calculus constructors solely contain terms. This effectively means that the collection of 휇-constructors ̃휇 is a superset of the collection of 휆-constructors; every 휆-constructor can be a 휇-constructor, ̃휇 but not every 휇-constructor ̃휇 26 CONVERTING 휆-TERMS TO 휇-TERMS ̃휇

#» 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) ∣ 퐴 → 퐵 푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … 퐹 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= … 퐾 ∈ 퐶표푛푠푡푟푢푐푡표푟 ∶∶= … #» 푥, 푦, 푧 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾( 푥 ) # » # » 푀, 푁 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 휆[푝.푀] ∣ 푀 푁 ∣ 퐾(푀) #» # #» #» » 푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 퐴 ∶ 퐹 (푋) 퐻, 퐽 ∈ 퐽푢푑푔푒푚푒푛푡 ∶∶= 푀 ∶ 퐴

Typing rules: 푥 푥 ∶ 퐴 푀 ∶ 퐴 → 퐵 푁 ∶ 퐴 ⋮ → 퐸 푀 푁 ∶ 퐵 푀 ∶ 퐵 → 퐼 휆푥.푀 ∶ 퐴 → 퐵 푥 # »푖 #» # »푗 #» Given 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 퐴푖푗 ∶ 퐹 (푋) :

# »푖 # »푗 # # » 푥푖푗 # # » »푗 푥푖푗 ∶ 퐴푖푗{퐶/푋} 푀푗 ∶ 퐴푖푗{퐵/푋} # » #» 퐹 퐼 ⋮ 퐾 (푀) ∶ 퐹 (퐵) 푀 ∶ 퐵 푖 푖 → 퐼 # #» »푖 #» 퐾푖 휆[퐾푖(푥푖).푀푖 ] ∶ 퐹 (퐶) → 퐵 Evaluation:

휆푥.푀 푁 ≻ 푀{푁/푥} 훽푥 #» #» # » 휆[⋯ ∣ 퐾 ( 푥 ).푀 ∣ … ] 퐾 (푁) ≻ 푀 {푁/푥} 푖 푖 푖 훽퐹 푖

Figure 3.1: The 휆-calculus with datatypes and constructors. PROGRAMMING WITH THE 휇-CALCULUS ̃휇 27 can be a 휆-constructor. The case for declarations in Conv is therefore a simple one:

# »푖 #» # »푗 #» Conv(푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 퐴푖푗 ∶ 퐹 (푋) ) = # »푖 #» # »푗 #» 푑푎푡푎 Conv(퐹 (푋)) 푤ℎ푒푟푒 퐾푖 ∶ Conv(퐴푖푗) ⊢ Conv(퐹 (푋)) ∣

With types and constructors done, we are able to define Conv’s behaviour for variables and patterns, before we can finally start converting terms.

#»푖 # »푖 Conv(푥) = 푥 Conv(퐾(푥푖 )) = 퐾(∅, Conv(푥푖) ) In the 휆-calculus, terms come in four different shapes: variables, 휆-functions, function applications, and constructors. We have just shown how to convert variables and constructors, which leaves us with 휆-functions and function applications. In the 휇-calculus, ̃휇 functions need to have both an input term and an output co-term. For the simplest kind of functions, the ones only have variables as patterns, and no constructors, conversion is as simple as adding a fresh co-variable in the function’s pattern and returning the function’s term to this co-variable.

Conv(휆푥.푀) = 휇(Conv(푥) ⋅ 훼.⟨Conv(푀) ∥ 훼⟩)

휆-functions that have a constructor as at least one of its patterns require more work. In the 휇- ̃휇 calculus, constructors and observers in (co-)patterns can only contain variables. This holds for the call- #» stack observer (⋅) as well. For this reason, we cannot convert a 휆-function 휆[퐾( 푥 ).푀] to a 휇-term #» #» 휇(퐾( 푥 )⋅훼.⟨Conv(푀) ∥ 훼⟩), as 퐾( 푥 )⋅훼 is a nested pattern. This problem can be easily circumvented by splitting the pattern into two separate parts. We first generate a 휇-term that expects a call-stack. This call-stack contains a fresh variable 푦 and a fresh co-variable 훼. We then generate a -co-term̃휇 that contains the converted patterns and the converted terms of each branch in the 휆-function. The term from the call- stack, 푦, is directly applied to the generated -co-term.̃휇 The command in the -co-term̃휇 connects the converted 휆-term to the output of the call-stack, 훼. # #» »푖 # #» »푖 Conv(휆[퐾푖(푥푖).푀푖 ]) = 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[Conv(퐾푖(푥푖)).⟨Conv(푀푖) ∥ 훼⟩ ]⟩)

Finally, we define the behaviour of Conv for function applications. In the 휆-calculus, computation happens in function applications. In the 휇-calculus, ̃휇 this happens in commands. This complicates the transformation algorithm, since function applications are terms, but commands are not. The generated term will therefore be a 휇-term which takes a single fresh co-variable as its pattern. Its command uses the left-hand side of the function application as the term, while it uses the right-hand side in a call-stack as the co-term. Conv(푀 푁) = 휇훼.⟨Conv(푀) ∥ Conv(푁) ⋅ 훼⟩ This approach of converting function applications to the 휇-calculus ̃휇 can be problematic if there exists a function application within the right-hand side of a function application. Let us for example define the datatype 퐿푖푠푡, and the function 푡푎푖푙, which takes a list and returns the same list, but without its first : 푑푎푡푎 퐿푖푠푡 퐴 푤ℎ푒푟푒 푁푖푙 ∶ 퐿푖푠푡 퐴 ∣ 퐶표푛푠 퐴 (퐿푖푠푡 퐴) ∶ 퐿푖푠푡 퐴 푡푎푖푙 = 휆[(퐶표푛푠 푥 푥푠).푥푠]

If we now apply 푡푎푖푙 twice in a row to a list 퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙), we expect to see 푁푖푙 as the result. In the 휆-calculus, this result is achieved as follows: 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))) ≻ 푡푎푖푙 (퐶표푛푠 푦 푁푖푙) ≻ 푁푖푙 훽퐹 훽퐹 28 CONVERTING 휆-TERMS TO 휇-TERMS ̃휇

푑푎푡푎 퐿푖푠푡 퐴 푤ℎ푒푟푒 푁푖푙 ∶ (⊢ 퐿푖푠푡 퐴 ∣) ∣ 퐶표푛푠 ∶ (퐴, 퐿푖푠푡 퐴 ⊢ 퐿푖푠푡 퐴 ∣) 푡푎푖푙 = 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩) 푐푎푙푙 = Conv(푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)))) = 휇훼.⟨푡푎푖푙 ∥ (휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩) ⋅ 훼⟩

⟨푐푎푙푙 ∥ 훼⟩

≻휇 ⟨푡푎푖푙 ∥ (휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩) ⋅ 훼⟩

≻휇 ⟨휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩ ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩

≻휇 ⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩

≻휇 ⟨퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙) ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩]⟩

≻ ̃휇 ⟨퐶표푛푠 푦 푁푖푙 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩

≻ ̃휇 ⟨푁푖푙 ∥ 훼⟩

Figure 3.2: Evaluating 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))) using a call-by-value strategy in the 휇- ̃휇 calculus.

In the 휇-calculus, ̃휇 however, this is a much more complicated process. To see the evaluation steps, we first need to convert the definitions tothe 휇-calculus ̃휇 using Conv.

푑푎푡푎 퐿푖푠푡 퐴 푤ℎ푒푟푒 푁푖푙 ∶ (⊢ 퐿푖푠푡 퐴 ∣) ∣ 퐶표푛푠 ∶ (퐴, 퐿푖푠푡 퐴 ⊢ 퐿푖푠푡 퐴 ∣) 푡푎푖푙 = 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩) 푐푎푙푙 = Conv(푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)))) = 휇훼.⟨푡푎푖푙 ∥ (휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩) ⋅ 훼⟩

Of course, we cannot directly evaluate 푐푎푙푙, since it is a term, and not a command. Instead, we will evaluate the command ⟨푐푎푙푙 ∥ 훼⟩, for some arbitrary 훼. We expect the result to be ⟨푁푖푙 ∥ 훼⟩. There are two evaluation strategies, meaning there are two different reduction paths from ⟨푐푎푙푙 ∥ 훼⟩. Figure 3.2 shows the successful evaluation from ⟨푐푎푙푙 ∥ 훼⟩ to ⟨푁푖푙 ∥ 훼⟩ using the call-by-value strategy. Evaluating the command using the call-by-name strategy, however, never reaches its end goal ⟨푁푖푙 ∥ 훼⟩. The first PROGRAMMING WITH THE 휇-CALCULUS ̃휇 29 two reduction steps using call-by-name are the same as the first two steps when using call-by-value. ⟨푐푎푙푙 ∥ 훼⟩

≻휇 ⟨푡푎푖푙 ∥ (휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩) ⋅ 훼⟩

≻휇 ⟨휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩ ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩

At this point, evaluation gets stuck. We are using call-by-name, meaning that we prioritise -reductions̃휇 over 휇-reductions. The -co-term̃휇 in question expects a 퐶표푛푠 constructor as its argument, but the term that is supplied to it is 휇훽.⟨푡푎푖푙 ∥ (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ 훽⟩, a 휇-term. To get around this problem, we can alter the definition of Conv for function applications. Instead of generating terms that contain nested computations, we make sure function applications never contain another function application on the right-hand side. To do this, we need to reverse the order in which computations happen. The innermost computation needs to happen first, after which the result is passed to the second innermost computation, et cetera. To do this, we extract all the function applications in the application that we are trying to convert to the 휇-calculus, ̃휇 and create a call-stack of all the left-hand sides in the extracted function applications. For a nested function application term 푀1 (푀2 (… (푀푛 푁))), where 푁 is not a function application, this is done as follows:

Conv(푀1 (푀2 (… (푀푛 푁)))) =

휇훼.⟨Conv(푀푛) ∥ Conv(푁) ⋅ ̃휇푥1.⟨Conv(푀푛−1) ∥ 푥1 ⋅ … ̃휇푥푛.⟨푀1 ∥ 푥푛 ⋅ 훼⟩⟩⟩ Graphically, we can see that a tree of function applications is essentially turned upside down into a tree of terms and co-terms, connected by commands and call-stacks. 휇훼

푀1 푀푛 푁 ⋅ ̃휇푥1

푀2

푀3 ⋮ 푀푛−1 푥1 ⋅ ̃휇푥2

푀푛 푁 푀1 푥푛 ⋅ 훼 We can use this new rule for our earlier example, 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))):

Conv(푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)))) = 휇훼.⟨Conv(푡푎푖푙) ∥ Conv(퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)) ⋅ ̃휇푥.⟨Conv(푡푎푖푙) ∥ 푥 ⋅ 훼⟩

Figure 3.3 shows the reduction steps for this new definition for both call-by-value and call-by-name eval- uation strategies.

3.1.1 Typechecking We now have a complete definition for Conv. In this section we will prove that the conversion from 휆- calculus terms to 휇-calculus ̃휇 terms by Conv is type-safe. In essence, this means that for any 휆-calculus 30 CONVERTING 휆-TERMS TO 휇-TERMS ̃휇

푡푎푖푙 = 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩) 푐푎푙푙 = Conv(푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙)))) = 휇훼.⟨푡푎푖푙 ∥ 퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙) ⋅ ̃휇푥.⟨푡푎푖푙∥ 푥 ⋅ 훼⟩

⟨푐푎푙푙 ∥ 훼⟩

≻휇 ⟨푡푎푖푙 ∥ 퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙) ⋅ ̃휇푥.⟨푡푎푖푙∥ 푥 ⋅ 훼⟩

≻휇 ⟨퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙) ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ ̃휇푥.⟨푡푎푖푙∥ 푥 ⋅ 훼⟩⟩]⟩

≻ ̃휇 ⟨퐶표푛푠 푦 푁푖푙 ∥ ̃휇푥.⟨푡푎푖푙∥ 푥 ⋅ 훼⟩⟩

≻ ̃휇 ⟨푡푎푖푙 ∥ (퐶표푛푠 푦 푁푖푙) ⋅ 훼⟩

≻휇 ⟨퐶표푛푠 푦 푁푖푙 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ 훼⟩]⟩

≻ ̃휇 ⟨푁푖푙 ∥ 훼⟩

Figure 3.3: Evaluating 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))) using the improved version of Conv that generates call-stacks for nested function applications. These reduction steps are the same for both call-by- name and call-by-value evaluation. PROGRAMMING WITH THE 휇-CALCULUS ̃휇 31 term 푀 with type 퐴, the result of Conv(푀) must have type 퐴 too. Terms come in four different shapes in the 휆-calculus: variables, 휆-functions, function applications and constructors. Terms of all shapes will be proven to be type-safe. First, we convert variables. Variables are a special case, because their types cannot be proven without any context. In both the 휆-calculs and the 휇-calculus, ̃휇 it is not possible to prove that a variable 푥 has the type 퐴 without some form of context. In the 휆-calculus, this context is provided via the → 퐼 rules, while in the 휇-calculus, ̃휇 the context is the input environment Γ. Because of this, we can make no claims about the type of an arbitrary variable 푥 in the 휆-calculus. Likewise, we cannot make any claims about the type of the result of Conv(푥) in the 휇-calculus ̃휇 either. However, not knowing the type of 푥 in either calculus means that all type information is retained during the conversion. # » Secondly, we convert constructors. We will convert an arbitrary constructor 퐾 (푀) with datatype #» # #» #» » #» 푖 declaration 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 퐴 ∶ 퐹 (푋) and type 퐹 (퐵). First, we will transform the datatype dec- # »푖 #» # »푗 #» laration to the format of the 휇-calculus: ̃휇 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾푖 ∶ 퐴푖푗 ⊢ 퐹 (푋) ∣ . We can now convert # » # » # »푗 퐾푖(푀) to the 휇-calculus ̃휇 by using Conv. Conv(퐾푖(푀)) = 퐾푖(∅, Conv(푀푗) ). Finally, we need to prove that the constructor’s type remains the same# after» conversion#» to the 휇-calculus. ̃휇 The 퐹# 퐼»rule tells us that we can only reach the conclusion that 퐾 (푀) has type 퐹 (퐵) when each term 푀 in 푀 has type # » 푖 푗 # » 퐴 {퐵/푋}. Similarly, according to the 퐹 푅 rule, each term Conv(푀 ) must have type 퐴 {퐵/푋} 푖푗 퐾푖 푗 푖푗 # »푗 #» before we can claim 퐾푖(∅, Conv(푀푗) ) has type 퐹 (퐵).

# # »푗 # # » »푗 푀 ∶ 퐴 {퐵/푋} ⊢ Conv(푀푗) ∶ 퐴푖푗{퐵/푋} ∣ 푗 푖푗 퐹 푅 # » #» 퐹 퐼 # »푗 #» 퐾푖 퐾 (푀) ∶ 퐹 (퐵) 푖 ⊢ 퐾푖(∅, Conv(푀푗) ) ∶ 퐹 (퐵) ∣

We now have reduced the problem to proving for each of 푀푗 that their types do not change when trans- forming them to the 휇-calculus ̃휇 using Conv. We have therefore inductively proven that the transforma- tion of constructors from the 휆-calculus to the 휇-calculus ̃휇 is type-safe. Next, we will do the same for 휆-functions. The result of using Conv on a 휆-function depends on the patterns in the function. For a single variable pattern, the result is simpler that for one or more patterns that contain a constructor. Let us first look at the former case: a lambda function 휆푥.푀 with type 퐴 → 퐵. the → 퐼푥 rule tells us that to prove 휆푥.푀 ∶ 퐴 → 퐵, we need to prove 푀 ∶ 퐵, and are allowed to use a verification rule that confirms 푥 ∶ 퐴. The 휇-calculus ̃휇 version of 휆푥.푀 ∶ 퐴 is 휇(푥 ⋅ 훼.⟨Conv(푀) ∥ 훼⟩), with the usual co-datatype declaration of functions. The 퐺푅 rule can be used to reduce the judgement to ⟨Conv(푀) ∥ 훼⟩ ∶ (푥 ∶ 퐴 ⊢ 훼 ∶ 퐵). The 퐶푢푡 rule splits this command into its term and its co-term. Both need to have the same type, 퐵. The 푉 퐿 rule is used to verify the co-term branch. What is left is the judgement 푥 ∶ 퐴 ⊢ Conv(푀) ∶ 퐵 ∣. This means we need to prove that the 휇-calculus ̃휇 version of 푀 has type 퐵, and we know that 푥 has type 퐴. This premise is equivalent to the one we still needed to prove for the 휆-calculus, meaning that 휆-functions with a single variable as its pattern can be safely converted to the 휇-calculus. ̃휇

푥 푉 퐿 푥 ∶ 퐴 푥 ∶ 퐴 ⊢ Conv(푀) ∶ 퐵 ∣ ∣ 훼 ∶ 퐵 ⊢ 훼 ∶ 퐵 ⋮ 퐶푢푡 ⟨Conv(푀) ∥ 훼⟩ ∶ (푥 ∶ 퐴 ⊢ 훼 ∶ 퐵) 푀 ∶ 퐵 → 퐼 퐺푅 휆푥.푀 ∶ 퐴 → 퐵 푥 ⊢ 휇(푥 ⋅ 훼.⟨Conv(푀) ∥ 훼⟩) ∶ 퐴 → 퐵 ∣ The strategy for proving the type-safety of the conversion of 휆-functions one or more constructors #» # #» #» » in its patterns is similar. For a given declaration 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 퐴 ∶ 퐹 (푋) and a 휆-function # #» »푖 #» 휆[퐾 (푥 ).푀 ] with type 퐹 (퐶) → 퐵, we can use the → 퐼 rule. After applying this rule, we need 푖 푖 푖 퐾푖 to prove that every branch’s output term 푀푖 is of type 퐵. For each of those branches, we get access to verification functions for all variables 푥푖푗 in the pattern of the branch. 32 CONVERTING 휆-TERMS TO 휇-TERMS ̃휇

# »푖 # »푗 # # » 푥푖푗 푥푖푗 ∶ 퐴푖푗{퐶/푋} ⋮ 푀 ∶ 퐵 푖 → 퐼 # #» »푖 #» 퐾푖 휆[퐾푖(푥푖).푀푖 ] ∶ 퐹 (퐶) → 퐵 # #» »푖 Conv transforms this 휆-function into 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ]⟩). Just like for 휆- functions with a single variable as its pattern, we can consecutively apply the 퐺푅 and the 퐶푢푡 rule. The 푉 푅 rule is used to verify the term side of the command. This leaves us with the co-term # #» »푖 #» ̃휇[퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ], which should have the type 퐹 (퐶). To further reduce this judgement, we apply the 퐹 퐿 rule. As a result of this, we need to prove that for each branch 푖 in the -co-term,̃휇 ⟨Conv(푀푖) ∥ 훼⟩ is type-correct. As we already know that 훼 has type 퐵, type-correct in this instance means that Conv(푀푖) must have type 퐵. # »푖 # »푗 푉 퐿 푥푖푗 ∶ 퐴푖푗{퐶/푥} ⊢ Conv(푀푖) ∶ 퐵 ∣ ∣ 훼 ∶ 퐵 ⊢ 훼 ∶ 퐵 # »푖 퐶푢푡 # »푗 ⟨Conv(푀푖) ∥ 훼⟩ ∶ (푥푖푗 ∶ 퐴푖푗{퐶/푥} ⊢ 훼 ∶ 퐵) #» #» 푉 푅 # #» »푖 #» 퐹 퐿 푦 ∶ 퐹 (퐶) ⊢ 푦 ∶ 퐹 (퐶) ∣ ∣ ̃휇[퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ] ∶ 퐹 (퐶) ⊢ 훼 ∶ 퐵 # #» »푖 #» 퐶푢푡 ⟨푦 ∥ ̃휇[퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ]⟩ ∶ (푦 ∶ 퐹 (퐶) ⊢ 훼 ∶ 퐵) 퐺푅 # #» »푖 #» ⊢ 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ]⟩) ∶ 퐹 (퐶) → 퐵 ∣ Once again, the remaining premises for the 휇-calculus ̃휇 are equivalent to those remaining in the proof tree for the 휆-calculus. Finally, we will convert function applications to the 휇-calculus ̃휇 and prove type-safety. Recall how nested function applications alter the results of Conv. For this reason, we will use the term 푀푛 (푀푛−1 (… (푀1 푁))) for this proof. Assuming we want to prove this term has type 퐴푛, we can repeatedly use the → 퐸 rule. This rule tells us that for our term, 푀푛 must be of type 퐴푛−1 → 퐴푛, while (푀푛−1 (… (푀1 푁))) must have type 퐴푛−1. Applying this rule once more tells us that 푀푛−1 must be of type 퐴푛−2 → 퐴푛−1, and (… (푀1 푁)) of type 퐴푛−2. Repeating this process until we can no longer continue will eventually tell us that every 푀푖 must have type 퐴푖−1 → 퐴푖. The innermost input term, 푁, is supplied to 푀1, which must have type 퐴0 → 퐴1. 푁 must therefore be of type 퐴0. 푀 ∶ 퐴 → 퐴 푁 ∶ 퐴 1 0 1 0 → 퐸 푀1 푁 ∶ 퐴1 ⋮ 푀 ∶ 퐴 → 퐴 푛−1 푛−2 푛−1 → 퐸 푀 ∶ 퐴 → 퐴 푀 (… (푀 푁)) ∶ 퐴 푛 푛−1 푛 푛−1 1 푛−1 → 퐸 푀푛 (푀푛−1 (… (푀1 푁))) ∶ 퐴푛

Transforming 푀푛 (푀푛−1 (… (푀1 푁))) to the 휇-calculus ̃휇 using Conv yields 휇훼.⟨Conv(푀1) ∥ Conv(푁)⋅ ̃휇푥1.⟨Conv(푀2) ∥ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨Conv(푀푛) ∥ 푥푛−1 ⋅훼⟩⟩⟩⟩. For the sake of simplicity and read- ability, we will refer to this term as 휇훼.⟨푀1 ∥ 푁 ⋅ ̃휇푥1.⟨푀2 ∥ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨푀푛 ∥ 푥푛−1 ⋅ 훼⟩⟩⟩⟩, meaning we remove the mention of Conv. Typechecking this can be done by first using the the 퐴푅 and 푐푢푡 rules. This tells us that 푀1 needs to be of type 퐴0 → 퐴1, and that the co-term supplied to 푀1, 푁 ⋅ ̃휇푥1.⟨푀2 ∥ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨푀푛 ∥ 푥푛−1 ⋅ 훼⟩⟩⟩, must be of the same type. ∣ 푁 ⋅ ̃휇푥 .⟨푀 ∥ 푥 ⋅ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩ ⊢ 푀 ∶ 퐴 → 퐴 ∣ 1 2 1 2 푛−1 푛 푛−1 1 0 1 ∶ 퐴 → 퐴 ⊢ 훼 ∶ 퐴 0 1 푛 퐶푢푡 ⟨푀 ∥ 푁 ⋅ ̃휇푥 .⟨푀 ∥ 푥 ⋅ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩⟩ ∶ (⊢ 훼 ∶ 퐴 ) 1 1 2 1 2 푛−1 푛 푛−1 푛 퐴푅 ⊢ 휇훼.⟨푀1 ∥ 푁 ⋅ ̃휇푥1.⟨푀2 ∥ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨푀푛 ∥ 푥푛−1 ⋅ 훼⟩⟩⟩⟩ ∶ 퐴푛 ∣ PROGRAMMING WITH THE 휇-CALCULUS ̃휇 33

From this point on, we can start recursively applying the 퐺퐿 rule, then applying the 퐴퐿 rule on the 푂푖 co-term we get out of the 퐺퐿 rule, and finally applying the 퐶푢푡 rule. This leaves us with three new 푂푖 premises to prove. Knowing that the last term we saw was 푀푖, 푀1 in this case, we now need to prove that the input to this function has type 퐴푖−1. We also add the next function term, 푀푖+1, which has type 퐴푖+1 → 퐴푖+2. Finally, we also need to prove the co-term supplied to 푀푖+1 has the same type.

푥 ∶ 퐴 ∣ 푥 ⋅ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ⊢ 푀 ∶ 퐴 → 퐴 ∣ 1 1 1 2 푛−1 푛 2 1 2 ∥ 푥 ⋅ 훼⟩⟩⟩ ∶ 퐴 → 퐴 ⊢ 훼 ∶ 퐴 푛−1 1 2 푛 퐶푢푡 ⟨푀 ∥ 푥 ⋅ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩ ∶ (푥 ∶ 퐴 ⊢ 훼 ∶ 퐴 ) 2 1 2 푛−1 푛 푛−1 1 1 푛 퐴퐿 ⊢ 푁 ∶ 퐴 ∣ ∣ ̃휇푥 .⟨푀 ∥ 푥 ⋅ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩ ∶ 퐴 ⊢ 훼 ∶ 퐴 0 1 2 1 2 푛−1 푛 푛−1 1 푛 퐺퐿 푂푖 ∣ 푁 ⋅ ̃휇푥1.⟨푀2 ∥ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨푀푛 ∥ 푥푛−1 ⋅ 훼⟩⟩⟩ ∶ 퐴0 → 퐴1 ⊢ 훼 ∶ 퐴푛

We can apply this same strategy once again, and keep doing this until it is no longer possible to do so. Each step adds a new premise that needs to be proven: 푀푖+1.

푥 ∶ 퐴 ∣ 푥 ⋅ ̃휇푥 .⟨… ⟩ ⊢ 푀 ∶ 퐴 → 퐴 ∣ 2 2 2 3 3 2 3 ∶ 퐴 → 퐴 ⊢ 훼 ∶ 퐴 2 3 푛 퐶푢푡 ⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩ ∶ (푥 ∶ 퐴 ⊢ 훼 ∶ 퐴 ) 푉 푅 푛−1 푛 푛−1 2 2 푛 퐴퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐴 ∣ ̃휇푥 .⟨… ̃휇푥 .⟨푀 ∥ 푥 ⋅ 훼⟩⟩⟩ ∶ 퐴 ⊢ 훼 ∶ 퐴 1 1 1 1 2 푛−1 푛 푛−1 2 푛 퐺퐿 푂푖 푥1 ∶ 퐴1 ∣ 푥1 ⋅ ̃휇푥2.⟨… ̃휇푥푛−1.⟨푀푛 ∥ 푥푛−1 ⋅ 훼⟩⟩⟩ ∶ 퐴1 → 퐴2 ⊢ 훼 ∶ 퐴푛

At some point, it is no longer possible to apply these steps, because the judgement that needs to be proven will be 푥푛−1 ∶ 퐴푛−1 ∣ 푥푛−1 ⋅ 훼 ∶ 퐴푛−1 → 퐴푛 ⊢ 훼 ∶ 퐴푛. This final step can be completed using the 퐺퐿 rule, and using the two verification rules on the results. 푂푖 푉 푅 푉 퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐴 ∣ ∣ 훼 ∶ 퐴 ⊢ 훼 ∶ 퐴 푛−1 푛−1 푛−1 푛−1 푛 푛 퐺퐿 푂푖 푥푛−1 ∶ 퐴푛−1 ∣ 푥푛−1 ⋅ 훼 ∶ 퐴푛−1 → 퐴푛 ⊢ 훼 ∶ 퐴푛

After this entire process, there are a number of judgements that have not been proven.# » These are the judgements deciding the type of the innermost input term 푁, and all the functions 푀. These coincide exactly with the premises left open-ended in the 휆-calculus. We can therefore safely conclude that func- tion applications can be transformed to the 휇-calculus ̃휇 in a type-safe manner.

3.1.2 Evaluation

In addition to typechecking, it is important that 휆-calculus terms do not change their meaning when they are converted to the 휇-calculus. ̃휇 The term that they evaluate to must be the same in both calculi. However, as terms by themselves can often not be fully evaluated in the 휇-calculus, ̃휇 we want the eval- uation of a 휆-calculus term 푀 to be the same in a 휇-calculus ̃휇 command with a fresh co-variable 훼: ⟨Conv(푀) ∥ 훼⟩. Formally, we want to prove that if 푀 evaluates to 푁, then ⟨Conv(푀) ∥ 훼⟩ evaluates to ⟨Conv(푁) ∥ 훼⟩ under both evaluation strategies. The evaluation of variables, constructors and 휆-functions are trivial in this case. Neither can be eval- uated further in the 휆-calculus, which means 푀 is equivalent to 푁. By proxy, Conv(푀) is equivalent to Conv(푁), and therefore ⟨Conv(푀) ∥ 훼⟩ is already evaluated to ⟨Conv(푁) ∥ 훼⟩. Function applications are the only terms that can be 훽-reducable in the 휆-calculus. These applica- tions come in two different shapes, according to the two 훽-reduction rules:

휆푥.푀 푁 ≻ 푀{푁/푥} 훽푥 #» #» # » 휆[⋯ ∣ 퐾 ( 푥 ).푀 ∣ … ] 퐾 (푁) ≻ 푀 {푁/푥} 푖 푖 푖 훽퐹 푖 34 NESTED (CO-)PATTERNS

First, we will check the former. The result of Conv(휆푥.푀 푁) is 휇훼.⟨휇(푥 ⋅ 훽.⟨Conv(푀) ∥ 훽⟩) ∥ Conv(푁) ⋅ 훼⟩. In a command, we can evaluate this term as follows:

⟨휇훼.⟨휇(푥 ⋅ 훽.⟨Conv(푀) ∥ 훽⟩) ∥ Conv(푁) ⋅ 훼⟩ ∥ 훼⟩

≻휇 ⟨휇(푥 ⋅ 훽.⟨Conv(푀) ∥ 훽⟩) ∥ Conv(푁) ⋅ 훼⟩ ≻ 훽퐺 ⟨Conv(푁) ∥ ̃휇푥.⟨휇훽.⟨Conv(푀) ∥ 훽⟩ ∥ 훼⟩⟩

≻ ̃휇 ⟨휇훽.⟨Conv(푀) ∥ 훽⟩ ∥ 훼⟩{Conv(푁)/푥}

≻휇 ⟨Conv(푀) ∥ 훼⟩{Conv(푁)/푥}

This final result corresponds to the evalation in the 휆-calculus. Next, we do the same for type application on a 휆-function with a constructor as its argument. We use #» #» #» Conv to convert 휆[⋯ ∣ 퐾푖( 푥 ).푀푖 ∣ … ] 퐾푖(푁) to 휇훼.⟨휇푦 ⋅ 훽.⟨푦 ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훽⟩ ∣ # »푗 … ]⟩ ∥ 퐾푖(Conv(푁푗) ) ⋅ 훼⟩. This term can be evaluated in a command as follows:

#» # »푗 ⟨휇훼.⟨휇푦 ⋅ 훽.⟨푦 ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훽⟩ ∣ … ]⟩ ∥ 퐾푖(Conv(푁푗) ) ⋅ 훼⟩ ∥ 훼⟩

≻휇 #» # »푗 ⟨휇푦 ⋅ 훽.⟨푦 ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훽⟩ ∣ … ]⟩ ∥ 퐾푖(Conv(푁푗) ) ⋅ 훼⟩ ≻ 훽퐺 # »푗 #» ⟨퐾푖(Conv(푁푗) ) ∥ ̃휇푦.⟨휇훽.⟨푦 ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훽⟩ ∣ … ]⟩ ∥ 훼⟩⟩

≻ ̃휇 # »푗 #» ⟨휇훽.⟨퐾푖(Conv(푁푗) ) ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훽⟩ ∣ … ]⟩ ∥ 훼⟩

≻휇 # »푗 #» ⟨퐾푖(Conv(푁푗) ) ∥ ̃휇[⋯ ∣ 퐾푖( 푥 ).⟨Conv(푀푖) ∥ 훼⟩ ∣ … ]⟩ ≻ 훽퐹 # »푗 #» ⟨Conv(푁푗) ∥ ̃휇 푥 .⟨Conv(푀푖) ∥ 훼⟩⟩ # » ≻ ̃휇 # » ⟨Conv(푀푖) ∥ 훼⟩{Conv(푁)/푥}

Like the earlier case, function application on 휆-functions that take constructors as arguments evaluate to the same term in both calculi. We can therefore conclude that the generated 휇-calculus ̃휇 terms from Conv can be safely evaluated, as they reduce to the same term as they would in the 휆-calculus.

3.2 Nested (co-)patterns

So far, the 휇-calculus ̃휇 has only supported simple (co-)patterns. A simple (co-)pattern is a (co-)pattern which exists of either a (co-)variable, or a constructor or observer to which all arguments are (co-)variables. While 휇-terms and -terms̃휇 solely using these simple (co-)patterns are relatively easy to evaluate, they PROGRAMMING WITH THE 휇-CALCULUS ̃휇 35 can also become verbose. To avoid this, we add support nested (co-)patterns. This means that construc- tors and observers in (co-)patterns can now contain additional constructors and observers, instead of just (co-)variables. #» #» #» #» 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾( ̃푝, 푝 ) ̃푝∈ 퐶표푃 푎푡푡푒푟푛 ∶∶= 훼 ∣ 푂[ 푝 , ̃푝]

These nested (co-)patterns allow more readable (co-)terms to be written. For example, let us compare our previous definition of the function 푚푎푝 to a new definition that nested (co-)patterns allow us to write.

map ∶ (퐴 → 퐵) → 퐿푖푠푡 퐴 → 퐿푖푠푡 퐵

map = 휇(푓 ⋅ 훼.⟨휇(푦 ⋅ 훽.⟨푦 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푓 ∥ 푥 ⋅ ̃휇푧.⟨푚푎푝 ∥ 푓 ⋅ 푥푠 ⋅ ̃휇푧푠. ⟨퐶표푛푠 푧 푧푠 ∥ 훽⟩⟩⟩ ∣ 푁푖푙.⟨푁푖푙 ∥ 훽⟩]⟩) ∥ 훼⟩)

map = 휇(푓 ⋅ 퐶표푛푠 푥 푥푠 ⋅ 훼.⟨푓 ∥ 푥 ⋅ ̃휇푦.⟨푚푎푝 ∥ 푓 ⋅ 푥푠 ⋅ ̃휇푧.⟨퐶표푛푠 푦 푧 ∥ 훼⟩⟩⟩

∣ 푓 ⋅ 푁푖푙 ⋅ 훼.⟨푁푖푙 ∥ 훼⟩)

We are now able to write 푓 ⋅ 퐶표푛푠 푥 푥푠 ⋅ 훼 as a co-pattern, whereas before, it was necessary to split this co-pattern into pieces: 푓 ⋅ 훼, 푦 ⋅ 훽 and 퐶표푛푠 푥 푥푠. Reducing this to a single co-pattern also reduces the number of commands in the term, resulting in a much more concise and readable definition. Similarly, nested patterns can be added to the 휆-calculus lc. Once both the 휆-calculus and the 휇- ̃휇 calculus support nested patterns, we can simplify one of the cases in Conv. Previously, 휆-functions that have a constructor in their patterns were converted to the 휇-calculus ̃휇 using the following rule: # #» »푖 # #» »푖 Conv(휆[퐾푖(푥푖).푀푖 ]) = 휇(푦 ⋅ 훼.⟨푦 ∥ ̃휇[Conv(퐾푖(푥푖)).⟨Conv(푀푖) ∥ 훼⟩ ]⟩)

Now that nested patterns are supported, this can be simplified by removing the inner -co-term,̃휇 and instead placing the converted patterns inside a call-stack pattern: # #» »푖 # #» »푖 Conv(휆[퐾푖( 푝 ).푀푖 ]) = 휇(Conv(퐾푖( 푝 )) ⋅ 훼.⟨Conv(푀푖) ∥ 훼⟩ )

We could use the existing 훽-reduction rules in the 휇-calculus ̃휇 to evaluate nested (co-)patterns, but doing so can lead to problematic situations. As an example, we will apply an arbitrary function 푔 to each element in an empty list 푁푖푙, using the definition of 푚푎푝 with nested co-patterns. We will use a fresh variable 푦 as the final co-term in the command.

⟨휇(푓 ⋅ 퐶표푛푠 푥 푥푠 ⋅ 훼.⟨… ⟩ ∣ 푓 ⋅ 푁푖푙 ⋅ 훼.⟨… ⟩) ∥ 푔 ⋅ 푁푖푙 ⋅ 훾⟩

We can rewrite this command using the 훽퐺 rule. However, this rule does not match patterns recursively, but simply rewrites a command whenever a 휇-term on the left side contains a co-pattern with the same observer in it as the co-term on the right side of the command. #» #» #» #» #» #» #» #» ⟨휇(⋯ ∣ 푂 [ 푝 , ̃푝].푐 ∣ … ) ∥ 푂 [ 푣 , 푒 ]⟩ ≻ ⟨ 푣 ∥ ̃휇 푝 .⟨휇 ̃푝.푐 ∥ 푒 ⟩⟩ 푖 푖 푖 훽퐺 푖

This causes the previous command to be rewritten to the following command:

⟨푔 ∥ ̃휇푓.⟨휇(퐶표푛푠 푥 푥푠 ⋅ 훼.⟨… ⟩) ∥ 푁푖푙 ⋅ 훾)⟩⟩

This happens because both cases in the original 휇-term contain a call-stack as its co-pattern. The 훽퐺 rule therefore matches the co-term 푔 ⋅ 푁푖푙 ⋅ 훾 on the first case that it matches on. If we continue evaluating 36 NESTED (CO-)PATTERNS the command, we get the following steps:

⟨푔 ∥ ̃휇푓.⟨휇(퐶표푛푠 푥 푥푠 ⋅ 훼.⟨… ⟩) ∥ 푁푖푙 ⋅ 훾)⟩⟩

≻ ̃휇 ⟨휇(퐶표푛푠 푥 푥푠 ⋅ 훼.⟨… ⟩) ∥ 푁푖푙 ⋅ 훾⟩ ≻ 훽퐺 ⟨푁푖푙 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨휇훼.⟨… ⟩ ∥ 훾⟩]⟩

From this point, evaluation cannot continue, because we have 푁푖푙 as a term, while the -co-term̃휇 on the right side of the command only accepts 퐶표푛푠 푥 푥푠. This is not the behaviour that is expected. To combat this, we could alter the 훽-reduction rules to require nested (co-)patterns to recursively match any incoming (co-)terms. This would work fine for our earlier example. ⟨푚푎푝 ∥ 푔 ⋅ 푁푖푙 ⋅ 훾⟩ would simply evaluate to ⟨푁푖푙 ∥ 훾⟩ in one single reduction step. Unfortunately, this approach can lead to problems as well. If a sub-(co-)term is not fully evaluated yet, this can cause mistakes in the (co-)pattern matching. As an example, we will write a function 푛표푡, which takes a boolean parameter and returns its inverse. To do so, we first need to declare the boolean datatype. 푑푎푡푎 퐵표표푙 푤ℎ푒푟푒 푇 푟푢푒 ∶ (⊢ 퐵표표푙 ∣) ∣ 퐹 푎푙푠푒 ∶ (⊢ 퐵표표푙 ∣)

With this definition, we can define 푛표푡.

푛표푡 ∶ 퐵표표푙 → 퐵표표푙 푛표푡 = 휇(푇 푟푢푒 ⋅ 훼.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 퐹 푎푙푠푒 ⋅ 훼.⟨푇 푟푢푒 ∥ 훼⟩)

Now, applying an unevaluated term that should evaluate to either 푇 푟푢푒 or 퐹 푎푙푠푒 to this function can lock the evaluation context. As an example, let us apply the term 휇훽.⟨푇 푟푢푒 ∣ 훽⟩ to this function.

⟨휇(푇 푟푢푒 ⋅ 훼.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 퐹 푎푙푠푒 ⋅ 훼.⟨푇 푟푢푒 ∥ 훼⟩) ∥ 휇훽.⟨푇 푟푢푒 ∣ 훽⟩ ⋅ 훾⟩

We cannot evaluate this, since 휇훽.⟨푇 푟푢푒 ∣ 훽⟩ cannot be matched by either 푇 푟푢푒 or 퐹 푎푙푠푒.

3.2.1 Expanding (co-)patterns

Instead of recursively (co-)pattern matching during evaluation, we allow (co-)terms to be written with nested (co-)patterns, but rewrite all of the (co-)terms to ones that only contain simple patterns. This process is called pattern expansion. Pattern expansion is a technique of simplifying terms often used in languages based on the 휆-calculus that can be implemented for the 휇-calculus ̃휇 too. The standard algorithm to compute such transformations has been Augustsson’s algorithm C [Aug85] for many years. Augustsson originally developed this algorithm for his own Lazy ML compiler [Aug84]. ML is based on the 휆-calculus, of course, but with some slight alterations, algorithm C can be used for the 휇-calculus ̃휇 as well. From this point on, Augustsson’s algorithm will be referred to as C, while the transformation algorithm for the 휇-calculus ̃휇 will be referred to as C휇. In the 휆-calculus, every object is a term, including computations. C therefore produces terms. In the 휇-calculus, ̃휇 however, we can also write co-terms and commands. Since terms and co-terms cannot interact with each other by themselves, algorithm C휇 produces commands. Both C and C휇 are functions that take three input parameters. For C, these three arguments are a list of terms, a list of tuples of a list of patterns and a term, and a default term. For C휇, the three arguments are a list of expressions, which can be both terms and co-terms, a list of tuples of a list of (co-)patterns and a command, and a default command. Both functions are expressed below. PROGRAMMING WITH THE 휇-CALCULUS ̃휇 37

푣 ∈ 푇 푒푟푚 푒 ∈ 퐶표푇 푒푟푚 푝 ∈ 푃 푎푡푡푒푟푛 ̃푝∈ 퐶표푃 푎푡푡푒푟푛 푀 ∈ 푇 푒푟푚 푒푥푝 ∶∶= 푣 ∣ 푒 푞 ∶∶= 푝 ∣ ̃푝 푝 ∈ 푃 푎푡푡푒푟푛 푐 ∈ 퐶표푚푚푎푛푑 # » # #» » C(푀, ( 푝 , 푀), 푀) ∶ 푀 # » # #» » C휇(푒푥푝, ( 푞 , 푐), 푐) ∶ 푐

Several constraints must be applied to the input arguments for the algorithm to function correctly. First, each pattern sequence needs to be of exactly the same length as the list of expressions. Next, the expressions and (co-)patterns should match their kinds, meaning that a term should never be matched to a co-pattern, and a co-term should never be matched to a pattern.

# » # #» »푖 # » #» Theorem 3.1. Given C휇(푒푥푝, ( 푞 푖, 푐푖) , 푑), for each 푖, 푒푥푝 and 푞 푖 must have the same cardinality. Ad- ditionally, for each 푖 and for each 푗, either 푒푥푝푗 is a term, and 푞푖푗 is a pattern, or 푒푥푝푗 is a co-term, and 푞푖푗 is a co-pattern.

The initial input of C휇 requires a default input expression. To do this, we generate a fresh variable when we want to expand a co-term’s (co-)patterns, or a fresh co-variable when we want to expand a term’s (co-)patterns. Using this method, the input to C휇 for 푛표푡 would be

([푇 푟푢푒 ⋅ 훼], ⟨퐹 푎푙푠푒 ∥ 훼⟩) C ([푒], { }, error) 휇 ([퐹 푎푙푠푒 ⋅ 훼], ⟨푇 푟푢푒 ∥ 훼⟩) where 푒 is a fresh co-variable. The default case is used when matching fails on all (co-)patterns. Since there is no default case when matching fails for the entire term, error is used. Similarly, the input to C휇 for 푚푎푝 would be:

([푓 ⋅ 퐶표푛푠 푥 푥푠 ⋅ 훼], ⟨푓 ∥ 푥 ⋅ ̃휇푦.⟨푚푎푝 ∥ 푓 ⋅ 푥푠 ⋅ ̃휇푧.⟨퐶표푛푠 푦 푧 ∥ 훼⟩⟩⟩) C ([푒], { }, error) 휇 ([푓 ⋅ 푁푖푙 ⋅ 훽], ⟨푁푖푙 ∥ 훽⟩)

Like in C, there are four different cases in C휇 that determine the result of the algorithm.

Case 1

C휇([], { ([], 푐) } , 푑)

This is the simplest case. There are no expressions left to match, and no (co-)patterns that need matching either. All that is left of the input is a command 푐 and the default command 푑. Since there are no (co-)patterns left, it would not be possible to fail to match at this point. This means command 푐 is returned.

Case 2

([푥 , 푝 , … , 푝 ], 푐 ) ⎡ 1 12 1푛 1 ⎤ ⎢ ([푥2, 푝22, … , 푝2푛], 푐2) ⎥ C휇([푣, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢ … ⎥ ⎣([푥푚, 푝푚2, … , 푝푚푛], 푐푚)⎦ or 38 NESTED (CO-)PATTERNS

([훼 , 푝 , … , 푝 ], 푐 ) ⎡ 1 12 1푛 1 ⎤ ⎢ ([훼2, 푝22, … , 푝2푛], 푐2) ⎥ C휇([푒, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢ … ⎥ ⎣([훼푚, 푝푚2, … , 푝푚푛], 푐푚)⎦ In this case, every first (co-)pattern in the lists is a variable or a co-variable. Since matching ona variable is always possible, all that needs to happen at this stage is substituting the first input expression for all the first (co-)variables. This is achieved by returning a 휇 or ̃휇 construction. For terms, the result is:

([푝 , … , 푝 ], 푐 {푥/푥 }) ⎡ 12 1푛 1 1 ⎤ ⎢ ([푝22, … , 푝2푛], 푐2{푥/푥2}) ⎥ ⟨푣 ∥ ̃휇[푥].C휇([푞2 … , 푞푛], ⎢ ⎥ , 푑)⟩ ⎢ … ⎥ ⎣([푝푚2, … , 푝푚푛], 푐푚{푥/푥푚})⎦ and for co-terms:

([푝 , … , 푝 ], 푐 {훼/훼 }) ⎡ 12 1푛 1 1 ⎤ ⎢ ([푝22, … , 푝2푛], 푐2{훼/훼2}) ⎥ ⟨휇[훼].C휇([푞2 … , 푞푛], ⎢ ⎥ , 푑) ∥ 푒⟩ ⎢ … ⎥ ⎣([푝푚2, … , 푝푚푛], 푐푚{훼/훼푚})⎦

Case 3

([퐾 푐푝푠 푝푠 , 푝 , … , 푝 ], 푐 ) ⎡ 1 1 1 12 1푛 1 ⎤ ⎢ … ⎥ ⎢ ⎥ ⎢([퐾푘 푐푝푠푘 푝푠푘, 푝푘2, … , 푝푘푛], 푐푘)⎥ C휇([푣, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢([푥푘+1, 푝푘+1,2, … , 푝푘+1,푛], 푐푘+1)⎥ ⎢ ⎥ ⎢ … ⎥ ⎣([푥푘+푟, 푝푘+푟,2, … , 푝푘+푟,푛], 푐푘+푟)⎦ or

([푂 (푐푝 , … , 푐푝 ) (푝′ , … , 푝′ ), 푝 , … , 푝 ], 푐 ) ⎡ 1 11 1푚 11 1푚 12 1푛 1 ⎤ ⎢ … ⎥ ⎢ ′ ′ ⎥ ⎢([푂푘 (푐푝푘1, … , 푐푝푘푚) (푝푘1, … , 푝푘푚), 푝푘2, … , 푝푘푛], 푐푘)⎥ C휇([푒, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢ ([훼푘+1, 푝푘+1,2, … , 푝푘+1,푛], 푐푘+1) ⎥ ⎢ ⎥ ⎢ … ⎥ ⎣ ([훼푘+푟, 푝푘+푟,2, … , 푝푘+푟,푛], 푐푘+푟) ⎦ In this case the first pattern in each pattern sequence can be either a (co-)constructor or a (co-)variable. However, the order of the pattern sequences is important. Every pattern sequence starting with a (co- )variable comes after every pattern sequence that starts with a (co-)constructor. In this case the pattern sequences are grouped. All pattern sequences starting with the same (co-)constructor are grouped to- gether, and all pattern sequences starting with a variable are part of the same group. For example, take the following list of pattern sequences: PROGRAMMING WITH THE 휇-CALCULUS ̃휇 39

([퐶표푛푠 푥 푥푠, … ], 푐 ) ⎡ 1 ⎤ ⎢ ([푁푖푙, … ], 푐2) ⎥ ⎢ ⎥ ⎢([퐶표푛푠 푦 푦푠, … ], 푐3)⎥ ⎣ ([푧, … ] 푐4) ⎦

([퐶표푛푠 푥 푥푠, … ], 푐1) The formed groups will be [ ], [([푁푖푙, … ], 푐2)] and [([푧, … ] 푐4)]. The groups ([퐶표푛푠 푦 푦푠, … ], 푐3) will form the different cases in the command that is returned. The result for termsis:

1 ([푐푝푠11, 푝푠11, 푝12, … , 푝1푛], … ) ⟨푣 ∥ ̃휇[퐾 (푥11, … , 푥1푚) (훼11, … , 훼1푚)].C휇([푞2, … , 푞푛], [ ], default)⟩ … ∣ …

푁 ([푐푝푠푗1, 푝푠푗1, 푝12, … , 푝1푛], … ) ∣ [퐾 (푥푗1, … , 푥푗푚) (훼푗1, … , 훼푗푚)].C휇([푞2, … , 푞푛], [ ], default) …

∣ [푥].C휇(푞2, … , 푞푛, {([푝푘+1,2, … , 푝푘+1,푛], 푐푘+1{푥/푥푘+1})}, 푑) Here, default can refer to two different commands, depending on the context. If the input setof (co-)pattern sequences contains a sequence that starts with a (co-)variable, default refers to the result of C휇 that is called for the (co-)variable case, as seen in the results above. If there is no (co-)pattern sequence starting with a (co-)variable, default refers to 푑. In that case, an extra case is added to the 휇 or ̃휇 construction: [푣].푑 or [훼].푑.

Case 4

The final case is similar to case 3: The (co-)pattern sequences can start with both (co-)variables and(co- )constructors. However, in this situation, the sequences are not ordered. The pattern sequences starting with a (co-)variable are not necessarily at the end of the sequence set. To solve this issue, the (co-)pattern- sequences are once again grouped, but this time in order. The sequences are divided by repeatedly taking the longest possible prefix that is sorted. If 푃푘 is the set of those groups, the result for both terms and co-terms is:

C휇([푞1, … , 푞푛], 푃1, 푑1)

푤ℎ푒푟푒 푑1 = C휇([푞1, … , 푞푛], 푃2, 푑2)

푤ℎ푒푟푒 푑2 = C휇([푞1, … , 푞푛], 푃3, 푑3) …

푤ℎ푒푟푒 푑푘 = C휇([푞1, … , 푞푛], 푃푘, 푑)

3.2.2 Adding join points

The (co-)pattern expansion introduced by algorithm C휇 can lead to code duplication. In case 3, the result of the function may contain several branches that all use the same default value. To demonstrate this, we define a function 푠푒푐표푛푑퐼푠푇 푟푢푒 that takes a list of boolean value, and returns 푇 푟푢푒 if the second value is 푇 푟푢푒, but 퐹 푎푙푠푒 otherwise. 푠푒푐표푛푑퐼푠푇 푟푢푒 ∶ 퐿푖푠푡 퐵표표푙 → 퐵표표푙 푠푒푐표푛푑퐼푠푇 푟푢푒 = 휇((퐶표푛푠 푥 (퐶표푛푠 푇 푟푢푒 푥푠)) ⋅ 훼.⟨푇 푟푢푒 ∥ 훼⟩ ∣ 푦 ⋅ 훽.⟨퐹 푎푙푠푒 ∥ 훽⟩) 40 NESTED (CO-)PATTERNS

Expanding the (co-)patterns in this function using C휇 yields

휇(푙 ⋅ 훼.⟨푙 ∥ ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨푦 ∥ ̃휇(푇푟푢푒.⟨푇 푟푢푒 ∥ 훼⟩)⟩]⟩]⟩ ∣ 푧.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 푧.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 푧.⟨퐹 푎푙푠푒 ∥ 훼⟩)

Even for such a short and simple term, the (co-)pattern expansion algorithm has duplicated the de- fault command twice. The command ⟨퐹 푎푙푠푒 ∥ 훼⟩ occurs three times in the expanded version of 푠푒푐표푛푑퐼푠푇 푟푢푒. To combat this duplication of commands, we can use join points [Mau+17]. Instead of duplicating the default command, join points let us define the command once, and storing it under some variable. Whenever the command should occur, we can instead refer to this variable. To implement this in the 휇-calculus, ̃휇 we first introduce command-variables, and alter the definitions of commands.

푑 ∈ 퐶표푚푚푎푛푑푉 푎푟 ∶∶= … 푐 ∈ 퐶표푚푚푎푛푑 ∶∶= ⟨푣 ∥ 푒⟩ ∣ 푑

Using this new definition, 휇-terms and -co-terms̃휇 are able to refer to a command using a command- variable 푑. To add meaning to this ability, we add let-bindings for commands to the calculus to both terms and co-terms next.

푣 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ 푙푒푡 푑 = 푐 푖푛 푣 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ⋯ ∣ 푙푒푡 푑 = 푐 푖푛 푒

As command-variables are only supposed to be introduced using these let-bindings, we do not need to introduce typechecking rules that introduce a passive judgement with a command-variable. Instead, we only add rules that introduce let-bindings, and make sure that the command-variables are not present in the premises of the rules, but rather replaced by their definitions.

Γ ⊢ 푣{푐/푑} ∶ 퐴 ∣ Δ Γ ∣ 푒{푐/푑} ∶ 퐴 ⊢ Δ 푙푒푡푅 푙푒푡퐿 Γ ⊢ 푙푒푡 푑 = 푐 푖푛 푣 ∶ 퐴 ∣ Δ Γ ∣ 푙푒푡 푑 = 푐 푖푛 푒 ∶ 퐴 ⊢ Δ

Using these rules, we know that we can never get a valid passive judgement of a command-variable in a typechecking tree. If we do encounter such a judgement, it means that a command-variable has been used that has not been defined. Evaluating join points can be done in one of two separate approaches. First, we could define an envi- ronment Φ, which stores command-variables and the commands they are bound to. Using this approach, we could reduce a term 푙푒푡 푑 = 푐 푖푛 푣 with command environment Φ to 푣 with the updated environment Φ, 푑 = 푐. Then, whenever we encounter the command-variable 푑, we can look up its meaning in the command environment. Alternatively, we could leave out the environment, and instead replace every occurence of 푑 with 푐 as soon as we encounter a let-binding. This way, a term 푙푒푡 푑 = 푐 푖푛 푣 evaluates to 푣{푐/푑}. Similarly to the typechecking rules, this approach causes encountering a command-variable leading to an error, as each command-variable should have been replaced by the 푙푒푡 rules. Whichever approach we choose, it is important that the evaluation of let-bindings has precedence over the 훽-reduction rules. Not having this precedence can lead to pattern match failure. As an example, the command ⟨푙푒푡 푑 = 푐 푖푛 퐶표푛푠 (휇훼.푑) 푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨… ⟩]⟩ PROGRAMMING WITH THE 휇-CALCULUS ̃휇 41 should see the let-binding be reduced before applying 훽-reduction as follows:

⟨푙푒푡 푑 = 푐 푖푛 퐶표푛푠 (휇훼.푑) 푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨… ⟩]⟩ ≻ ⟨퐶표푛푠 (휇훼.푐) 푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨… ⟩]⟩ ≻ 훽퐹 …

Or, using the first approach:

⟨푙푒푡 푑 = 푐 푖푛 퐶표푛푠 (휇훼.푑) 푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨… ⟩]⟩ (Φ) ≻ ⟨퐶표푛푠 (휇훼.푑) 푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨… ⟩]⟩ (Φ, 푑 = 푐) ≻ 훽퐹 …

If we instead give precedence to the 훽-reduction rules, evaluation will get stuck, since a let-binding cannot be matched with a (co-)pattern containing a constructor or an observer.

3.2.2.1 Adding join points to C휇

Now that join points have been implemented in the 휇-calculus, ̃휇 we need algorithm C휇 to produce join points, instead of duplicating commands. We have already established that the duplication of commands happens by passing the same default command to multiple branches in a 휇-term or -co-term.̃휇 The only point at which this happens in algorithm C휇 is in case 3. Note that commands that were already duplicated in the input of C휇 will still occur multiple times. This technique only stops the system from duplicating commands itself. These user-duplicated commands could be removed using join points as well, by analysing the result of C휇. This thesis will, however, not go into detail about this process.

As described in Section 3.2.1, C휇 takes a term or co-term, and a list of patterns and co-patterns. If the input is a term, and all the first elements in the list of (co-)patterns are patterns, the algorithm produces a command with the input term on the left side, and a -co-term̃휇 using each of these patterns as its cases on the right side. Likewise, if the input is a co-term, and all the first (co-)patterns are co-patterns, the produced command consists of a 휇-term on the left side, and the input co-term on the right side. This generated 휇-term or -co-term̃휇 may contain the default command several times. To prevent this, we enclose the generated 휇-term or -co-term̃휇 in a let-binding where we bind a fresh command-variable 푑′ ′ to default. We then pass 푑 to the next recursive usages of C휇, instead of default. Given the input

([퐾 푐푝푠 푝푠 , 푝 , … , 푝 ], 푐 ) ⎡ 1 1 1 12 1푛 1 ⎤ ⎢ … ⎥ ⎢ ⎥ ⎢([퐾푘 푐푝푠푘 푝푠푘, 푝푘2, … , 푝푘푛], 푐푘)⎥ C휇([푣, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢([푥푘+1, 푝푘+1,2, … , 푝푘+1,푛], 푐푘+1)⎥ ⎢ ⎥ ⎢ … ⎥ ⎣([푥푘+푟, 푝푘+푟,2, … , 푝푘+푟,푛], 푐푘+푟)⎦ 42 MU-MU-TILDE-HASKELL

C휇 will produce

⟨푣 ∥ 푙푒푡 푑′ = default 푖푛

1 ([푐푝푠11, 푝푠11, 푝12, … , 푝1푛], … ) ′ ̃휇[퐾 (푥11, … , 푥1푚) (훼11, … , 훼1푚)].C휇([푞2, … , 푞푛], [ ] , 푑 )⟩ … ∣ …

푁 ([푐푝푠푗1, 푝푠푗1, 푝12, … , 푝1푛], … ) ′ ∣ [퐾 (푥푗1, … , 푥푗푚) (훼푗1, … , 훼푗푚)].C휇([푞2, … , 푞푛], [ ] , 푑 ) …

∣ [푥].C휇(푞2, … , 푞푛, {([푝푘+1,2, … , 푝푘+1,푛], 푐푘+1{푥/푥푘+1})}, 푑)

Likewise, with input

([푂 (푐푝 , … , 푐푝 ) (푝′ , … , 푝′ ), 푝 , … , 푝 ], 푐 ) ⎡ 1 11 1푚 11 1푚 12 1푛 1 ⎤ ⎢ … ⎥ ⎢ ′ ′ ⎥ ⎢([푂푘 (푐푝푘1, … , 푐푝푘푚) (푝푘1, … , 푝푘푚), 푝푘2, … , 푝푘푛], 푐푘)⎥ C휇([푒, 푞2 … , 푞푛], ⎢ ⎥ , 푑) ⎢ ([훼푘+1, 푝푘+1,2, … , 푝푘+1,푛], 푐푘+1) ⎥ ⎢ ⎥ ⎢ … ⎥ ⎣ ([훼푘+푟, 푝푘+푟,2, … , 푝푘+푟,푛], 푐푘+푟) ⎦

C휇 will produce

⟨푙푒푡 푑′ = default 푖푛 ∥ 푒⟩

1 ([푐푝푠11, 푝푠11, 푝12, … , 푝1푛], … ) ′ 휇[푂 (푥11, … , 푥1푚) (훼11, … , 훼1푚)].C휇([푞2, … , 푞푛], [ ] , 푑 ) … ∣ …

푁 ([푐푝푠푗1, 푝푠푗1, 푝12, … , 푝1푛], … ) ′ ∣ [푂 (푥푗1, … , 푥푗푚) (훼푗1, … , 훼푗푚)].C휇([푞2, … , 푞푛], [ ] , 푑 ) …

∣ [푥].C휇(푞2, … , 푞푛, {([푝푘+1,2, … , 푝푘+1,푛], 푐푘+1{푥/푥푘+1})}, 푑)

When we use this new version of C휇 on our definition of 푠푒푐표푛푑퐼푠푇 푟푢푒, we now get a term without any command duplication:

휇(푙 ⋅ 훼.⟨푙 ∥ 푙푒푡 푑 = ⟨퐹 푎푙푠푒 ∥ 훼⟩ 푖푛 ̃휇[퐶표푛푠 푥 푥푠.⟨푥푠 ∥ ̃휇[퐶표푛푠 푦 푦푠.⟨푦 ∥ ̃휇(푇푟푢푒.⟨푇 푟푢푒 ∥ 훼⟩)⟩]⟩]⟩ ∣ 푧.푑 ∣ 푧.푑 ∣ 푧.푑)

3.3 Mu-Mu-tilde-Haskell

We have shown how 휆-calculus programs can be converted to 휇-calculus ̃휇 programs, and how these converted programs can be typechecked and evaluated. Using this process, we can use high-level pro- gramming languages that compile to the 휆-calculus as a front-end to the 휇-calculus. ̃휇 A widely used PROGRAMMING WITH THE 휇-CALCULUS ̃휇 43

푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … #» 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 퐴 푥 ∈ 푉 푎푟푖푎푏푙푒 ∶∶= … 훼 ∈ 퐶표푉 푎푟푖푎푏푙푒 ∶∶= … 푐 ∈ 퐶표푚푚푎푛푑 ∶∶= 푣 <||> 푒 # » # » 푣 ∈ 푇 푒푟푚 ∶∶= 푥 ∣ 퐾 (푣 ∣ 푒) ∣ 푣 푣 ∣ (\ ̃푝→ 푐) ∣ (\푐푎푠푒 ̃푝→ 푐) # » # » ∣ 푙푒푡 푑푒푐푙 푖푛 푣 ∣ 푣 푤ℎ푒푟푒 푑푒푐푙 # » # » 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ∼훼 ∣ 푂 (푣 ∣ 푒) ∣ (\푝 → 푐) ∣ (\푐푎푠푒 푝 → 푐) # » # » ∣ 푙푒푡 푑푒푐푙 푖푛 푒 ∣ 푒 푤ℎ푒푟푒 푑푒푐푙 # » 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾 (푝 ∣ ̃푝) # » ̃푝∈ 퐶표푃 푎푡푡푒푟푛 ∶∶= ∼훼 ∣ 푂 (푝 ∣ ̃푝) #» # # » 푑푎푡푎푑푒푐푙 ∈ 퐷푎푡푎퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푑푎푡푎 퐹 푋 = 퐾 푋 ∣ ∼푋 #» # # » ∣ 푐표푑푎푡푎 퐺 푋 = 퐾 푋 ∣ ∼푋 푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푥 ∶∶ 퐴 ∣ ∼훼 ∶∶ 퐴 #» ∣ 푥 푝 = 푣 ∣ ∼훼 = 푒 # » 푝푟표푔푟푎푚 ∈ 푃 푟표푔푟푎푚 ∶∶= (푑푎푡푎푑푒푐푙 ∣ 푑푒푐푙)

Figure 3.4: The syntax of MMH.

example of such a language is Haskell. We can use Haskell to write 휇-calculus ̃휇 programs in a much more comfortable way. However, as Haskell has no notion of co-data, writing programs in plain Haskell does not allow us to fully control the 휇-calculus. ̃휇 Instead, we propose a new programming language that is heavily based on Haskell, but has some syntactical additions and alterations to allow the unique features of the 휇-calculus ̃휇 to be controlled: MMH. Figure 3.4 shows the syntactical definition of MMH. At the top-level, a MMH program exists of declarations. A declaration can be a datatype declaration, a codatatype declaration, a (co-)term type declaration, or a (co-)term definition declaration. Datatype declarations are nearly the same as they are in Haskell. There is one difference: in the 휇-calculus, ̃휇 and therefore in MMH, constructors can contain both terms and co-terms as arguments. To accomodate for this feature, we place a tilde (∼) before types that represent a co-term, and do not place this tilde for types that represent a term.

#» # » 푑푎푡푎 퐹 푋 = 퐾1 푋 ∣ ∼푋 ⋮ # » 퐾푛 푋 ∣ ∼푋

As an example, we can define the datatype 퐸푥푎푚푝푙푒: 푑푎푡푎 퐸푥푎푚푝푙푒 푋 = 푇 푋 ∣ 퐶 ∼푋. To construct a value of type 퐸푥푎푚푝푙푒 푋, we either use 푇 with a term of type 푋, or 퐶 with a co-term of 44 MU-MU-TILDE-HASKELL type 푋. We implement co-datatype declarations the same way. #» # » 푐표푑푎푡푎 퐺 푋 = 푂1 푋 ∣ ∼푋 ⋮ # » 푂푛 푋 ∣ ∼푋

The function co-datatype is declared in MMH as 푐표푑푎푡푎 푎 → 푏 = 푎 . ∼푏. We use a similar technique to differentiate between term and co-term declarations. 푛푎푚푒 = … will define a term called 푛푎푚푒, while ∼푛푎푚푒 = … will define a co-term 푛푎푚푒. To represent commands in MMH, a reserved operator (<||>) is introduced. Using this operator, we can write 푣 <||> ∼ 푎, which is compiled to ⟨푣 ∥ 푎⟩ in the 휇-calculus. ̃휇 In Haskell, term declarations can contain patterns on the left side of the equals sign, e.g. 푚푎푝 푓 (푥 ∶ 푥푠) = … . Internally, this is read as a string of 휆-functions, 휆푓.휆(푥 ∶ 푥푠) … . Since this is only relevant for function terms, we allow these patterns for term declarations in MMH, but not in co-term declarations.

푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푥 ∶∶ 퐴 ∣∼ 훼 ∶∶ 퐴 #» ∣ 푥 푝 = 푣 ∣∼ 훼 = 푒

Terms in Haskell consist of a variable, a constructor, a function application, a 휆-function, a let- binding, or a 푤ℎ푒푟푒-construction. These are integrated in MMH accordingly. Unfortunately, this does not provide full coverage of the terms in the 휇-calculus. ̃휇 We are able to write variables and construc- tors, but 휇-terms cannot be written. To implement this feature, we remove the syntax for anonymous 휆-functions in Haskell, and instead use a similar syntactical structure to support 휇-terms. Whereas in Haskell, we could write (\푥 → 푀), which would be translated to 휆푥.푀 in the 휆-calculus, we can now write (\ ∼ 푎 → 푣 < || > 푒), or 휇푎.⟨푣 ∥ 푒⟩ in the 휇-calculus. ̃휇 Of course, this same syntax also supports observers as patterns. We use the same syntax to write -co-terms.̃휇 The difference between the two is the same as it is in the 휇-calculus: ̃휇 the co-term takes a pattern, while the term takes a co-pattern.

푣 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ (\ ̃푝→ 푐) 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ⋯ ∣ (\푝 → 푐)

This addition does mean that the Haskell syntax for anonymous functions can no longer be used. However, non-anonymous functions can still be introduced using let-bindings, or 푤ℎ푒푟푒-structures. Al- ternatively, the new syntax for 휇-terms can be used to introduce anonymous functions, although this syntax is closer to the 휇-calculus ̃휇 than it is to the 휆-calculus. Where we would write (\푥 → 푀) in Haskell, we write (\푥.∼푎 → 푀 <||> ∼푎) in MMH. There is one more thing missing. As it stands, 휇-terms and -co-terms̃휇 can only contain one single (co-)pattern in MMH. This means we cannot write (co-)terms that pattern match on multiple cases. To allow this, we draw inspiration from GHC’s LambdaCase extension [Men19]. We allow the keyword 푐푎푠푒 to be added to (\)-construction. When this is done, we allow multiple cases to be defined within the (co-)term as follows: # » 푣 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ (\푐푎푠푒 ̃푝→ 푐) # » 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ⋯ ∣ (\푐푎푠푒 푝 → 푐)

These alterations and additions to Haskell together form a solid high-level programming language that uses the 휇-calculus ̃휇 as its foundation. Figure 3.5 shows an example MMH program. PROGRAMMING WITH THE 휇-CALCULUS ̃휇 45

data List a = Nil ∣ Cons a (List a) map ∶∶ (a → b) → List a → List b map f (Cons x xs) = Cons (f x)(map f xs) map f Nil = Nil data Either a b = Left a ∣ Right b codata a & b = ∼a & ∼b handleEither ∶∶ Either a b → a & b handleEither = 휆case (Left a) ∘ (∼a & ∼b) → a < || > ∼a (Right b) ∘ (∼a & ∼b) → b < || > ∼b) foldr1 ∶∶ (a → b → b) → b → List a → b foldr1 f b Nil = b foldr1 f b (Cons x xs) = f x (foldr1 f b xs) foldr2 ∶∶ (a → b → b) → b → List a → b foldr2 = 휆case f ∘ b ∘ Nil ∘ ∼a → b < || > ∼a f ∘ b ∘ (Cons x xs) ∘ ∼a → foldr2 < || > f ∘ b ∘ xs ∘ (휆y → f < || > x ∘ y ∘ ∼a) data Bool = True ∣ False codata Negation a = Not a ∼coval ∶∶ Negation Bool ∼coval = Not True

Figure 3.5: An example MMH program 46 MU-MU-TILDE-HASKELL

3.3.1 Conclusion The 휇-calculus ̃휇 provides a solid basis for a modern high-level programming language. In this chapter, we have shown how 휆-calculus programs can be converted to 휇-calculus ̃휇 programs. Next, we added support for nested (co-)patterns to the 휇-calculus, ̃휇 using an extended version of Augustsson’s algorithm C. With these two definitions, we are able to directly convert most of the programming language Haskell to the 휇-calculus. ̃휇 Finally, we have formalised a new language based on Haskell, called MMH. MMH features some additional syntax to add support for the unique features of the 휇-calculus. ̃휇 4 Adding polymorphism

At this point, the 휇-calculus ̃휇 and MMH do not support type polymorphism. In this chapter, we will propose a way to add polymorphism to the 휇-calculus ̃휇 in a way that is useful in the context of high-level programming languages. To do so, we will first look at a system for polymorphism inthe 휇-calculus ̃휇 by Paul Downen and Zena Ariola. We will discuss how this approach is not suited when the 휇-calculus ̃휇 is used as a core language for a high-level programming language. Finally, we will propose our own system, based on Hindley-Milner polymorphism in the 휆-calculus.

4.1 Downen and Ariola’s polymorphism

One option to introduce a powerful polymorphic typing system to the 휇-calculus ̃휇 is Downen & Ariola’s System 풞풟 [DA19]. System 풞풟 is a polymorphic variant of the 휆휇̄ -calculus ̃휇 [CH00]. In this system, polymorphism is possible through type functions. Before, types consisted of type variables, or connectors. In addition to this, type functions and type applications are included. Type functions (휆푋.퐴) take a type variable 푋, and produce a new type 퐴. Type application works by applying one type to another one (퐴 퐵). #» 퐹 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= … 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) ∣ 휆푋.퐴 ∣ 퐴 퐵

In essence, the untyped 휆-calculus is now embedded within the 휇-calculus’ ̃휇 type system. As a result, the type system needs its own 훽 and 휂-reduction rules: (휆푋.퐴) 퐵 ≻훽 퐴{퐵/푋} and 휆푋.퐴 푋 ≻휂 퐴. Next, we extend constructors, observers and (co-)patterns by allowing them to contain type informa- tion. In addition to terms and co-terms, constructors and observers can now have types as arguments. Patterns and co-patterns are allowed to contain type variables accordingly. An important distinction is the fact that (co-)patterns can only contain type variables, and not types themselves. This makes it possi- ble to define polymorphic (co-)terms, but the system cannot pattern match on types. Since the definition of constructors and observers has been altered, we need to renew (co-)datatype declarations as well. In declarations, constructors and observers can contain type variables that represent the type arguments. These type variables are then bound to the rest of the declaration, meaning that it is possible to declare a 48 DOWNEN AND ARIOLA’S POLYMORPHISM constructor or observer with an argument that has one of the type arguments as its type. #» #» #» 푣 ∈ 푇 푒푟푚 ∶∶= ⋯ ∣ 퐾(퐴, 푒 , 푣 ) ∣ … #» #» #» 푒 ∈ 퐶표푇 푒푟푚 ∶∶= ⋯ ∣ 푂[퐴, 푣 , 푒 ] ∣ … #» #» #» 푝 ∈ 푃 푎푡푡푒푟푛 ∶∶= 푥 ∣ 퐾(푋, ̃푝, 푝 ) #» #» #» ̃푝∈ 퐶표푃 푎푡푡푒푟푛 ∶∶= 훼 ∣ 푂[푋, 푝 , ̃푝] #» # #» #» #» #» » 푑푒푐푙 ∈ 퐷푒푐푙푎푟푎푡푖표푛 ∶∶= 푑푎푡푎 퐹 (푋) 푤ℎ푒푟푒 퐾 푌 ∶ 퐴 ⊢ 퐹 (푋) ∣ 퐵 #» # #» #» #» #» » | 푐표푑푎푡푎 퐺(푋) 푤ℎ푒푟푒 푂 푌 ∶ 퐴 ∣ 퐹 (푋) ⊢ 퐵

With these additions to the calculus come new typing rules. We redefine the 퐹 푅 and 퐺퐿 rules to make sure that type arguments are properly substituted in the other types in both constructors and observers. #» # #» #» #» #» » # Given 푑푎푡푎 퐹 ( 푋) » 푤ℎ푒푟푒# 퐾 푌 ∶ 퐴 ⊢ 퐹 ( 푋) ∣ 퐵 : » # # » » 푗 # # » » 푗 ′ ′ Γ ∣ 푒 ∶ 퐵푖푗{퐶/푋, 퐶 /푌 } ⊢ Δ Γ ⊢ 푣 ∶ 퐴푖푗{퐶/푋, 퐶 /푌 } ∣ Δ # » #» 퐹 푅퐾 ′ #» #» 푖 Γ ⊢ 퐾푖(퐶 , 푒 , 푣 ) ∶ 퐹 (퐶) ∣ Δ #» # #» #» #» #» » # Given 푐표푑푎푡푎 퐺( »푋) 푤ℎ푒푟푒# 푂 푌 ∶ 퐴 ∣ 퐹 ( 푋) ⊢ 퐵 : » # # » » 푗 # # » » 푗 ′ ′ Γ ⊢ 푣 ∶ 퐴푖푗{퐶/푋, 퐶 /푌 } ∣ Δ Γ ∣ 푒 ∶ 퐵푖푗{퐶/푋, 퐶 /푌 } ⊢ Δ # » #» 퐺퐿푂 ′ #» #» 푖 Γ ∣ 푂푖(퐶 , 푣 , 푒 ) ∶ 푂(퐶) ⊢ Δ

We also add new type conversion rules 푇 퐶푅 and 푇 퐶퐿. These conversion rules allow types to be equal under 훽 and 휂 reduction. They contain premises in the shape of 퐴 =훽휂 퐵. Formally, 퐴 =훽휂 퐵 when both 퐴 and 퐵 can be evaluated to the same type 퐶 using 훽-reduction and/or 휂-reduction.

Γ ⊢ 푣 ∶ 퐴 ∣ Δ 퐴 =훽휂 퐵 Γ ∣ 푒 ∶ 퐴 ⊢ Δ 퐴 =훽휂 퐵 푇 퐶푅 푇 퐶퐿 Γ ⊢ 푣 ∶ 퐵 ∣ Δ Γ ∣ 푒 ∶ 퐵 ⊢ Δ

Using type parameters in constructors and observers, we are able to implement both existential and universal quantification as user-defined (co-)datatypes. We denote type variables by prefixing themwith ’@’.

푐표푑푎푡푎 퐹 표푟푎푙푙 푥 = 푑푎푡푎 퐸푥푖푠푡푠 푥 = 퐹 표푟푎푙푙 @푦 ∼(푥 푦) 퐸푥푖푠푡푠 @푦 (푥 푦) Using the co-datatype for universal quantification, we are able to write polymorphic functions, such asa polymorphic identity function.

푖푑 ∶∶ 퐹 표푟푎푙푙 (\푥.푥 → 푥) 푖푑 = \(퐹 표푟푎푙푙 @푦 (푥 ⋅ ∼푎)) → 푥 <||>∼푎

푖푑푇 푟푢푒 ∶∶ 퐵표표푙 푖푑푇 푟푢푒 = \ ∼푎 → 푖푑 <||> 퐹 표푟푎푙푙 @퐵표표푙 (푇 푟푢푒 ⋅ ∼푎)

We can use the new typing rules to typecheck these terms. First, we will typecheck the identity function. To do so, we start with the judgement ⊢ 휇(퐹 표푟푎푙푙 푌 (푥 ⋅ 훼).⟨푥 ∥ 훼⟩) ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ∣. The 퐺푅 rule lets us remove the co-pattern from this term, and typecheck its command instead. To ADDING POLYMORPHISM 49 do so, we first check the outer co-pattern: 퐹 표푟푎푙푙 푌 (푥 ⋅ 훼). The 퐹 표푟푎푙푙 co-datatype definition tells us that for the type 퐹 표푟푎푙푙 (휆푋.푋 → 푋), the pattern (푥 ⋅ 훼) must be of type (휆푋.푋 → 푋) 푌 . We can use the 훽-reduction rule to reduce this type to 푌 → 푌 . We can now split the pattern 푥 ⋅ 훼 into separate parts again, and find out that both 푥 and 훼 must be of type 푌 . We insert them into the input and output environments, and can continue typechecking the judgement ⟨푥 ∥ 훼⟩ ∶ (푥 ∶ 푌 ⊢ 훼 ∶ 푌 ). To do so, we apply the 퐶푢푡 rule to split off the term and co-term. Both can be inmediately verified usingthe 푉 푅 and 푉 퐿 rules respectively. 푉 푅 푉 퐿 푥 ∶ 푌 ⊢ 푥 ∶ 푌 ∣ ∣ 훼 ∶ 푌 ⊢ 훼 ∶ 푌 퐶푢푡 ⟨푥 ∥ 훼⟩ ∶ (푥 ∶ 푌 ⊢ 훼 ∶ 푌 ) 퐺푅 ⊢ 휇(퐹 표푟푎푙푙 푌 (푥 ⋅ 훼).⟨푥 ∥ 훼⟩) ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ∣ Next, we will typecheck the usage of the identity function in 푖푑푇 푟푢푒. We start with the judgement 푖푑 ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ⊢ 휇훼.⟨푖푑 ∥ 퐹 표푟푎푙푙 퐵표표푙 (푇 푟푢푒 ⋅ 훼)⟩ ∶ 퐵표표푙 ∣, as the type of 푖푑 has already been proven. We use the 퐴푅 rule to remove 훼, and instead continue checking the command. We use the 퐶푢푡 rule to split the command, and need to prove that both sides of the command have the same type. We already know 푖푑’s type, meaning both sides need to be of type 퐹 표푟푎푙푙 (휆푋.푋 → 푋). The term-premise is swiftly closed by the 푉 푅 rule, but the co-term-premise needs more work. We need to prove the judgement ∣ 퐹 표푟푎푙푙 퐵표표푙 (푇 푟푢푒 ⋅ 훼) ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ⊢ 훼 ∶ 퐵표표푙. We can use the 퐺퐿 rule to do this. The definition of 퐹 표푟푎푙푙 tells us that the co-term 푇 푟푢푒 ⋅ 훼 needs to be of type (휆푋.푋 → 푋) 퐵표표푙, as 퐵표표푙 is the type argument to 퐹 표푟푎푙푙 in this example. We can further evaluate this type to 퐵표표푙 → 퐵표표푙 using 훽-reduction. Knowing this, we can use the 퐺퐿 once again to split 푇 푟푢푒 ⋅ 훼 into a separate term and co-term. These new judgements can be proven using the 퐹 푅 and 푉 퐿 rules. 퐹 푅 푉 퐿 ⊢ 푇 푟푢푒 ∶ 퐵표표푙 ∣ ∣훼∶퐵표표푙⊢훼∶퐵표표푙 퐺퐿 ∣ 푇 푟푢푒 ⋅ 훼 ∶ (휆푋.푋 → 푋) 퐵표표푙 ⊢ 훼 ∶ 퐵표표푙 푉 푅 퐺퐿 푖푑 ∶ ⋯ ⊢ 푖푑 ∶ ⋯ ∣ ∣ 퐹 표푟푎푙푙 퐵표표푙 (푇 푟푢푒 ⋅ 훼) ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ⊢ 훼 ∶ 퐵표표푙 퐶푢푡 ⟨푖푑 ∥ 퐹 표푟푎푙푙 퐵표표푙 (푇 푟푢푒 ⋅ 훼)⟩ ∶ (푖푑 ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ⊢ 훼 ∶ 퐵표표푙) 퐴푅 푖푑 ∶ 퐹 표푟푎푙푙 (휆푋.푋 → 푋) ⊢ 휇훼.⟨푖푑 ∥ 퐹 표푟푎푙푙 퐵표표푙 (푇 푟푢푒 ⋅ 훼)⟩ ∶ 퐵표표푙 ∣

4.1.1 Type inference Although very powerful, there is a limitation to this type of polymorphism: type inferencing is undecid- able. We can find the types of non-polymorphic (co-)terms by attempting to typecheck the (co-term) using a fresh type variable as the (co-)terms supposed type. The typing rules tell us useful information about this type in the shape of . For example, if we have the judgement ⊢ 푇 푟푢푒 ∶ 휏 ∣, we can use the 퐹 푅 rule to find that type variable 휏 must be equivalent to the type 퐵표표푙. This is denoted as 휏 ∼ 퐵표표푙. These equivalencies, often called constraints [MH88], can be accumulated throughout a typechecking tree. After this is done, we can use them to evaluate the type of the original judgement by substituting types according to the found constraints. Using this technique, we can, for instance, infer the type of 푛표푡. We start a proof tree with the judgement ⊢ 휇(푇 푟푢푒 ⋅ 훼.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 퐹 푎푙푠푒 ⋅ 훼.⟨푇 푟푢푒 ∥ 훼⟩) ∶ 휏1 ∣ where 휏1 is a fresh type variable. We can apply the 퐺푅 rule to remove the 휇, and continue proving the types of the commands in all branches. Doing so, we first use co-pattern matching to fill the input and output environments, and report any constraints to 휏1. There are two co-patterns in the 휇-term: 푇 푟푢푒⋅훼 and 퐹 푎푙푠푒 ⋅ 훼. As the co-patterns are call-stacks, we know that 휏1 must be equivalent to a function type. We also know that 푇 푟푢푒 and 퐹 푎푙푠푒 have type 퐵표표푙. We do not know the type of 훼 yet, however. We therefore assign a fresh type variable 휏2 to 훼 in both branches, and store the constraint 휏1 ∼ 퐵표표푙 → 휏2. ⋮ ⋮ ⟨퐹 푎푙푠푒 ∥ 훼⟩ ∶ (⊢ 훼 ∶ 휏 ) ⟨푇 푟푢푒 ∥ 훼⟩ ∶ (⊢ 훼 ∶ 휏 ) 휏 ∼ 퐵표표푙 → 휏 2 2 1 2 퐺푅 ⊢ 휇(푇 푟푢푒 ⋅ 훼.⟨퐹 푎푙푠푒 ∥ 훼⟩ ∣ 퐹 푎푙푠푒 ⋅ 훼.⟨푇 푟푢푒 ∥ 훼⟩) ∶ 휏1 ∣ 50 HINDLEY-MILNER POLYMORPHISM

Next, we continue with the two commands. Both can be proven in the same way. We apply the 퐶푢푡 rule to split the command into a term and co-term. The 퐶푢푡 rule tells us that the term and co-term need to be of the same type. We already know that 훼 is of type 휏2 in both comands, while 푇 푟푢푒 and 퐹 푎푙푠푒 are constructors of the datatype 퐵표표푙. As such, we add the constraint 휏2 ∼ 퐵표표푙. The remaining two judgements can be proven with the 퐹 푅 and the 푉 퐿 rules in both branches. 퐹 푅 푉 퐿 ⊢ 퐹 푎푙푠푒 ∶ 퐵표표푙 ∣ ∣ 훼 ∶ 휏 ⊢ 훼 ∶ 휏 휏 ∼ 퐵표표푙 2 2 2 퐶푢푡 ⟨퐹 푎푙푠푒 ∥ 훼⟩ ∶ (⊢ 훼 ∶ 휏2) 퐹 푅 푉 퐿 ⊢ 푇 푟푢푒 ∶ 퐵표표푙 ∣ ∣ 훼 ∶ 휏 ⊢ 훼 ∶ 휏 휏 ∼ 퐵표표푙 2 2 2 퐶푢푡 ⟨푇 푟푢푒 ∥ 훼⟩ ∶ (⊢ 훼 ∶ 휏2)

After finishing the entire proof tree, we are left with two constraints: 휏1 ∼ 퐵표표푙 → 휏2 and 휏2 ∼ 퐵표표푙. We can combine these constraints to find that the type of 푛표푡 must be 퐵표표푙 → 퐵표표푙. However, this technique does not work for polymorphic (co-)terms. We can try to execute the same steps for the polymorphic identity function we defined earlier. We start with the judgement ⊢ 휇(퐹 표푟푎푙푙 푌 (푥⋅ 훼).⟨푥 ∥ 훼⟩) ∶ 휏1 ∣. We first apply the 퐺푅 rule to remove 휇-term and its co-pattern, and continue with the command ⟨푥 ∥ 훼⟩. In doing so, we find two constraints. The first is 휏1 ∼ 퐹 표푟푎푙푙 휏2, because the 휇-term contains a 퐹 표푟푎푙푙 constructor, but we are not sure of the 퐹 표푟푎푙푙 connector’s argument. We do know that applying 푌 to this argument would give us a function type 휏3 → 휏4, since 푥 ⋅ 훼 is a call-stack. We therefore get the constraint 휏2 푌 ∼ 휏3 → 휏4, and the input and output environment are extended with 푥 ∶ 휏3 and 훼 ∶ 휏4. Next, we can split the command ⟨푥 ∥ 훼⟩ by using the 퐶푢푡 rule. This tells us that 푥 and 훼 are of the same type, leading to the constraint 휏4 ∼ 휏3. We can then use the verification rules to finish the proof.

푉 푅 푉 퐿 푥 ∶ 휏 ⊢ 푥 ∶ 휏 ∣ ∣ 훼 ∶ 휏 ⊢ 훼 ∶ 휏 휏 ∼ 휏 3 3 4 4 4 3 퐶푢푡 ⟨푥 ∥ 훼⟩ ∶ (푥 ∶ 휏 ⊢ 훼 ∶ 휏 ) 휏 ∼ 퐹 표푟푎푙푙 휏 , 휏 푌 ∼ 휏 → 휏 3 4 1 2 2 3 4 퐺푅 ⊢ 휇(퐹 표푟푎푙푙 푌 (푥 ⋅ 훼).⟨푥 ∥ 훼⟩) ∶ 휏1 ∣

As a result, we have three constraints: 휏1 ∼ 퐹 표푟푎푙푙 휏2, 휏2 푌 ∼ 휏3 → 휏4, and 휏3 ∼ 휏4. We can simplify this by applying the last constraint to the others. We are then left with two constraints: 휏1 ∼ 퐹 표푟푎푙푙 휏2 and 휏2 푌 ∼ 휏3 → 휏3. We are not able to solve this set of constraints any further. We cannot possibly determine what 휏2 must be, which means that all we know of the type of the identity function is that it must be 퐹 표푟푎푙푙 휏2, where 휏2 could be anything. As this is not useful information, this example proves that this typing system does not suffice, and another approach is needed.

4.2 Hindley-Milner polymorphism

Type inferencing being undecidable for polymorphic terms is a problem that is not unique to the 휇- ̃휇 calculus. In fact, it holds true for the polymorphic 휆-calculus System F as well [Wel99]. To support both polymorphism and type inference, many functional programming languages use the Hindley-Milner type system [Mil78]. The Hindley-Milner type system was invented for the 휆-calculus, but its core ideas can be used in the 휇-calculus. ̃휇 In the 휆-calculus this typing system differentiates between types and type schemes. Types can be type variables or connectors, while type schemes are either a type, or a universally quantified type. Most terms have types, but top-level declared terms and let-bound terms are allowed to have a type scheme. Using this system, the 휆-calculus can support polymorphism, but only on the top-level. #» 퐹 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= … 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) 휎 ∈ 푇 푦푝푒푆푐ℎ푒푚푒 ∶∶= 퐴 ∣ ∀푋.퐴 ADDING POLYMORPHISM 51

It is not possible to use a constructor to create a quantified type. Instead, we generalise the inferred types of all top-level definitions. For example, if we have a top level definition 푖푑 = 휆푥.푥, we can infer its type as follows: 휏 ∼ 휏 2 3 푥 푥 ∶ 휏3 휏1 ∼ 휏2 → 휏3 → 퐼푥 휆푥.푥 ∶ 휏1

The constraints we find in this typechecking tree, 휏1 ∼ 휏2 → 휏3 and 휏2 ∼ 휏3, tell us that 휏1 can be rewritten to 휏3 → 휏3. We generalise types by finding all free type variables in the type, and binding them with a universal . In the case of 푖푑, this means the final type will be ∀휏3.휏3 → 휏3. Whenever 푖푑 is used in a term, we instantiate its type. If we infer the type of the term 푖푑 푇 푟푢푒, we start the process by claiming its type is a fresh type variable 휏1. We then use the → 퐸 rule to split the term into two parts: 푖푑 and 푇 푟푢푒. We know that 푖푑 must have some function type that produces a term of type 휏1. We also know that 푖푑 takes 푇 푟푢푒 as an argument. Since 푇 푟푢푒’s type is 퐵표표푙, we know that in this step, 푖푑 must be of type 퐵표표푙 → 휏1. As we have determined previously, 푖푑’s fully quantified type is actually ∀휏3.휏3 → 휏3. This means that 퐵표표푙 → 휏1 must be an instance of ∀휏3.휏3 → 휏3. This is a new type of constraint, denoted as 퐵표표푙 → 휏1 < ∀휏3.휏3 → 휏3.

퐵표표푙 → 휏1 < ∀휏3.휏3 → 휏3 푖푑 ∶ 퐵표표푙 → 휏 푇 푟푢푒 ∶ 퐵표표푙 1 → 퐸 푖푑 푇 푟푢푒 ∶ 휏1

We can instantiate the right-hand side of this constraint by passing it a fresh type-variable 휏4. This gives us the constraint 퐵표표푙 → 휏1 ∼ 휏4 → 휏4. This constraint can then be split into 휏1 ∼ 휏4 and 휏4 ∼ 퐵표표푙. Reducing these constraints once more gives us 휏1 ∼ 퐵표표푙, which means that the type of the term 푖푑 푇 푟푢푒 must be 퐵표표푙.

4.2.1 Hindley-Milner in the 휇-calculus ̃휇 We can implement a similar typing system in the 휇-calculus, ̃휇 and therefore in MMH. Just like in the 휆- calculus, we differentiate between types and type schemes. (Co-)terms that are defined at the top-level, in let-bindings or in where-clauses have type schemes, other (co-)terms have types. To do this, the inferred types of all the (co-)terms that can have a type scheme are generalised. Because of these changes, our type inferencing process can now result in two kinds of constraints: equivalencies (퐴 ∼ 퐵), and instances (퐴 < 퐵). The difference between the two is subtle. 퐴 < 퐵 means that 퐴 and 퐵 must be equivalent when all quantification from 퐵 is removed, and the quantified variables are replaced with fresh variables. 퐴 ∼ 퐵 means that 퐴 and 퐵 are simply equivalent. We define three constraint rules that allow instance constraints to be simplified. The first rule we define is the 퐶표푛 − 푀표푛표 rule. This rules specifies that if two types - and not type schemes - are equal, they are automatically an instance of each other. 퐴 ∼ 퐵 퐶표푛 − 푀표푛표 퐴 < 퐵

Next, we add the 퐶표푛 − 퐼푛푠푡 rule. This rule tells us that if a type scheme 휎1 is an instance of another type scheme 휎2, 휎1 will also be an instance of a quantified version of 휎2. 휎 < 휎 {푌 /푋} 1 2 퐶표푛 − 퐼푛푠푡 휎1 < ∀푋.휎2

Then, we define the 퐶표푛 − 푆푘표푙 rule, which dictates that for a type scheme 휎1 that is an instance of another type scheme 휎2, a quantified version of 휎1 is also an instance of 휎2, as long as the type parameter of this quantified version does not occur in 휎2. 52 HINDLEY-MILNER POLYMORPHISM

#» 퐴, 퐵, 퐶 ∈ 푇 푦푝푒 ∶∶= 푋 ∣ 퐹 (퐴) 푋, 푌 , 푍 ∈ 푇 푦푝푒푉 푎푟푖푎푏푙푒 ∶∶= … 휎 ∈ 푇 푦푝푒푆푐ℎ푒푚푒 ∶∶= 퐴 ∣ ∀푋.휎 퐹 , 퐺 ∈ 퐶표푛푛푒푐푡표푟 ∶∶= …

Core rules: 퐵 < 퐴 퐵 < 퐴 푉 푅 푉 퐿 푥 ∶ 퐴 ⊢ 푥 ∶ 퐵 ∣ ∣ 훼 ∶ 퐵 ⊢ 훼 ∶ 퐴 Constraint rules: 퐴 ∼ 퐵 퐶푀 퐶푉 퐴 < 퐵 퐴 ∼ 퐴 휎 < 휎 {푌 /푋} 휎 < 휎 푋 ∉ 퐹 푉 (휎 ) 1 2 퐶퐼 1 2 2 퐶푆 휎1 < ∀푋.휎2 ∀푋.휎1 < 휎2

Figure 4.1: Changes to the 휇-calculus ̃휇 to support the Hindley-Milner type system.

휎 < 휎 푋 ∉ 퐹 푉 (휎 ) 1 2 2 퐶표푛 − 푆푘표푙 ∀푋.휎1 < 휎2 Finally, we add one more rule: the 퐶표푛 − 푉 푒푟푖푓푦 rule. This rule does not handle instante constraints, but it allows equivalency constraints to be removed if the constraint represent a type being equivalent to itself. These kinds of constraints are trivially true, and therefore not useful. 퐶표푛 − 푉 푒푟푖푓푦 퐴 ∼ 퐴 Figure 4.1 shows all the changes that are made to the 휇-calculus ̃휇 to support the Hindley-Milner type system. With these new rules in place, we are able to write polymorphic (co-)terms. Just like we did for the 휆-calculus, we will write an identity function 푖푑, and show its type inferencing process. As we have seen before, an identity function in the 휇-calculus ̃휇 can be written as 휇(푥 ⋅ 훼.⟨푥 ∥ 훼⟩). We will start the type inference process by claiming this term has type 휏1. We can use the 퐺푅 rule to continue. This tells us that 휏1 must be of some function type. We therefore get the constraint 휏1 < 휏2 → 휏3, and insert 푥 ∶ 휏2 and 훼 ∶ 휏3 into the input and output environments. As the constraint has unquantified types on both sides of the instance operator, it can be reduced to the equivalency constraint 휏1 ∼ 휏2 → 휏3. Next, we typecheck the command ⟨푥 ∥ 훼⟩, with environments 푥 ∶ 휏2 ⊢ 훼 ∶ 휏3. We use the 퐶푢푡 rule to split the command. As both sides of the command must have the same type, we obtain the constraint 휏2 ∼ 휏3. We also obtain two more judgements to prove: 푥 ∶ 휏2 and 훼 ∶ 휏3. These can be proven using the 푉 푅 and 푉 퐿 rules respectively. This gives us the constraints 휏2 < 휏2 and 휏3 < 휏3. These can be reduced to equivalencies using the 퐶푀 rule, after which they can be removed with the 퐶푉 rule. 퐶푉 퐶푉 휏2 ∼ 휏2 휏3 ∼ 휏3 휏 < 휏 휏 < 휏 2 2 푉 푅 3 3 푉 퐿 푥 ∶ 휏2 ⊢ 푥 ∶ 휏2 ∣ ∣ 훼 ∶ 휏3 ⊢ 훼 ∶ 휏3 휏2 ∼ 휏3 휏 ∼ 휏 → 휏 퐶푢푡 1 2 3 ⟨푥 ∥ 훼⟩ ∶ (푥 ∶ 휏 ⊢ 훼 ∶ 휏 ) 휏 < 휏 → 휏 퐶푀 2 3 1 2 3 퐺푅 ⊢ 휇(푥 ⋅ 훼.⟨푥 ∥ 훼⟩) ∶ 휏1 ∣

The constraints we get from the typechecking tree are 휏2 ∼ 휏3 and 휏1 ∼ 휏2 → 휏3. We can reduce this to 휏1 ∼ 휏3 → 휏3. Finally, we generalise this type and find that 푖푑’s type is ∀휏3.휏3 → 휏3. Next, we will infer the type of a term that uses 푖푑: 휇훼.⟨푖푑 ∥ 푇 푟푢푒 ⋅ 훼⟩. As usual, we start with a judgement that binds this term to a fresh type variable 휏1. We already know the type of 푖푑, which is stored ADDING POLYMORPHISM 53

Γ ⊢ 푣1 ∶ 퐵 ∣ Δ Γ, 푥 ∶ 푔푒푛(퐵) ⊢ 푣2 ∶ 퐴 ∣ Δ 퐿푒푡푅1 Γ ⊢ 푙푒푡 푥 = 푣1 푖푛 푣2 ∶ 퐴 ∣ Δ Γ ∣ 푒 ∶ 퐵 ⊢ Δ Γ ⊢ 푣 ∶ 퐴 ∣ 훼 ∶ 푔푒푛(퐵), Δ 퐿푒푡푅 Γ ⊢ 푙푒푡 훼 = 푒 푖푛 푣 ∶ 퐴 ∣ Δ 2

Γ ⊢ 푣 ∶ 퐵 ∣ Δ Γ, 푥 ∶ 푔푒푛(퐵) ∣ 푒 ∶ 퐴 ⊢ Δ 퐿푒푡퐿 Γ ∣ 푙푒푡 푥 = 푣 푖푛 푒 ∶ 퐴 ⊢ Δ 1

Γ ∣ 푒1 ∶ 퐵 ⊢ Δ Γ ∣ 푒2 ∶ 퐴 ⊢ 훼 ∶ 푔푒푛(푏), Δ 퐿푒푡퐿2 Γ ∣ 푙푒푡 훼 = 푒1 푖푛 푒2 ∶ 퐴 ⊢ Δ

Figure 4.2: Typing rules for let-bindings in MMH. These rules can be used for top-level definitions and where-clauses too.

in the input environment. The starting judgement is 푖푑 ∶ ∀휏3.휏3 → 휏3 ⊢ 휇훼.⟨푖푑 ∥ 푇 푟푢푒⋅훼⟩ ∶ 휏1 ∣. The first step is to apply the 퐴푅 rule. This removes the 휇-term, and places 훼 ∶ 휏1 in the output environment. The remaining judgement is a passive one, and it can be split using the 퐶푢푡 rule. This rule forces the term 푖푑 and the co-term 푇 푟푢푒 ⋅ 훼 to be of the same type. Since we know that 푇 푟푢푒 has type 퐵표표푙 and 훼 has type 휏1, this type will be 퐵표표푙 → 휏1. The co-term side is relatively easily proven. We apply the 퐺퐿 rule to split it into 푇 푟푢푒 and 훼. We can use 퐹 푅 to finish the judgement ⊢ 푇 푟푢푒 ∶ 퐵표표푙 ∣. Applying 푉 퐿 to ∣ 훼 ∶ 휏1 ⊢ 훼 ∶ 휏1 gives us the constraint 휏1 < 휏1. Since both of these types are unquantified, and equivalent, we can use the 퐶푀 and 퐶푉 rules to finish this branch of the proof. Next, we goback to proving the judgement 푖푑 ∶ ∀휏3.휏3 → 휏3 ⊢ 푖푑 ∶ 퐵표표푙 → 휏1 ∣. We can use the 푉 푅 rule to get the constraint 퐵표표푙 → 휏1 < ∀휏3.휏3 → 휏3. The right-hand side of this constraint is then instantiated by the 퐶퐼 rule. This results in 퐵표표푙 → 휏1 < 휏4 → 휏4. As both types are now unquantified, we can use 퐶푀 to turn the instance constraint into an equivalency constraint 퐵표표푙 → 휏1 ∼ 휏4 → 휏4. This is the only constraint that remains from the type inference tree. It can be split into two new constraints: 휏4 ∼ 퐵표표푙 and 휏1 ∼ 휏4. Combining these two results into 휏1 ∼ 퐵표표푙, which means the original term has type 퐵표표푙. 퐶푉 퐵표표푙 → 휏1 ∼ 휏4 → 휏4 휏 ∼ 휏 퐶푀 1 1 퐶푀 퐵표표푙 → 휏 < 휏 → 휏 휏1 < 휏1 1 4 4 퐶퐼 퐹 푅 푉 퐿 퐵표표푙 → 휏 < ∀휏 .휏 → 휏 ⊢ 푇 푟푢푒 ∶ 퐵표표푙 ∣ ∣ 훼 ∶ 휏 ⊢ 훼 ∶ 휏 1 3 3 3 푉 푅 1 1 퐺퐿 푖푑 ∶ ∀휏 .휏 → 휏 ⊢ 푖푑 ∶ 퐵표표푙 → 휏 ∣ ∣ 푇 푟푢푒 ⋅ 훼 ∶ 퐵표표푙 → 휏 ⊢ 훼 ∶ 휏 3 3 3 1 1 1 퐶푢푡 ⟨푖푑 ∥ 푇 푟푢푒 ⋅ 훼⟩ ∶ (푖푑 ∶ ∀휏 .휏 → 휏 ⊢ 훼 ∶ 휏 ) 3 3 3 1 퐴푅 푖푑 ∶ ∀휏3.휏3 → 휏3 ⊢ 휇훼.⟨푖푑 ∥ 푇 푟푢푒 ⋅ 훼⟩ ∶ 휏1 ∣

We have seen how polymorphic (co-)terms can be defined and used in the 휇-calculus. ̃휇 This is made possible by generalising the types of all top-level definitions. In MMH, we need several extra typing rules, because it should also be possible to define polymorphic (co-)terms using let-bindings and where-clauses. To support this, the types of (co-)terms defined this way must be generalised. Figure 4.2 formalises the typing rules that facilitate this.

4.2.2 Conclusion

We have seen two separate approaches to type polymorphism in the 휇-calculus. ̃휇 First, we have explored Downen and Ariola’s system. While this system is very powerful, it renders type inference undecidable. For this reason, we have implemented Hindley-Milner polymorphism into the 휇-calculus. ̃휇 Using this 54 HINDLEY-MILNER POLYMORPHISM system, we have shown how it is possible to write polymorphic terms and co-terms, and how the types of these (co-)terms can be inferred. 5 Closing remarks

5.1 Conclusions

This thesis has proposed a modern high-level programming language called MMH, that uses the 휇- ̃휇 calculus at its core. Syntactically, MMH is an extension to Haskell. MMH has been formalised by first defining conversion steps for 휆-calculus terms to 휇-calculus ̃휇 terms. Since Haskell can be compiled to the 휆-calculus, this conversion method lets Haskell programs be converted to a collection of 휇-calculus ̃휇 terms. Next, support for nested (co-)patterns in the 휇-calculus ̃휇 has been implemented. This is done by formalising a pattern expansion algorithm C휇, based on Augustsson’s algorithm C. Finally, custom syntactical extensions have been added to Haskell to allow co-terms and commands to be written. This way, users of MMH are able to take full control of the power of the 휇-calculus. ̃휇 In addition, the 휇-calculus ̃휇 and MMH have been given support for polymorphic types. This has been done by adapting the Hindley-Milner typing system for the 휇-calculus. ̃휇

5.2 Future work

5.2.1 Formalisation

Although MMH has strong fundamental roots in both the 휆-calculus and the 휇-calculus, ̃휇 its soundness has not been proven in this thesis. For future stability, proving this, as well as Turing completeness, would be a logical next step.

5.2.2 Adding modern features

MMH can be used as a general purpose programming language. However, it lacks certain features that the modern programmer may expect. For instance, there is as of yet no way of creating interfaces. This could perhaps be done by implementing type classes in a similar way as Haskell has it. Likewise, the specifications of MMH in this thesis do not discuss splitting programs over multiple files. This means that MMH programs need to be written in one single file, and there is no way to organise code into chunks. From a user’s point of view, this is unacceptable for a modern programming language. Another feature 56 FUTURE WORK that is lacking in MMH, is a way to interact with the user. As it stands, MMH programs cannot read or write files, and they have no way of receiving user input. Implementing such features could be interesting.

5.2.3 Code generation This thesis has focused on compiling programs to the 휇-calculus. ̃휇 However, in a compiler, 휇-calculus ̃휇 would not be the end station. The 휇-calculus ̃휇 would be used as an intermediate language. More research is needed on converting this intermediate language to actual machine code. An especially interesting point here, are the call-stacks that are generated by functions in MMH. Since this neatly organises all computations in a program in a strict order, they can perhaps be taken advantage of.

5.2.4 Adding dependent types In dependently typed languages, types traditionally are seen as terms. How could a dependently typed programming language be built on top of the 휇-calculus? ̃휇 The 휇-calculus ̃휇 differentiates between terms and co-terms. What would the effect of this be in a dependently typed setting?

5.2.5 Converting 휇-calculus ̃휇 programs to the 휆-calculus Section 3.1 describes a process of transforming 휆-calculus programs to 휇-calculus ̃휇 programs. Doing this in the reversed direction could be an interesting alternative, as the 휆-calculus does not contain a direct replacement for co-terms in the 휇-calculus. ̃휇 Listing of figures

2.1 Propositions in the sequent calculus and natural deduction ...... 3 2.2 Judgements in natural deduction and the sequent calculus ...... 4 2.3 The core rules of the sequent calculus ...... 5 2.4 The structural rules of the sequent calculus ...... 5 2.5 Logical rules for truth (⊤), falsehood (⊥), conjunction (∧), disjunction (∨), negation (¬), implication (⊃), universal quantification (∀) and existential quantification (∃) in the se- quent calculus...... 6 2.6 Logical rules for truth (⊤), falsehood (⊥), conjunction (∧), disjunction (∨), implication (⊃), universal quantification (∀) and existential quantification (∃) in natural deduction. 6 2.7 Implication in natural deduction and the sequent calculus ...... 7 2.8 An example proof of the proposition (퐴 ∧ 퐵) ∧ 퐶 ⊃ 퐴 ∧ 퐶 in the two logical systems. 7 2.9 The simply typed 휆-calculus with function (→), sum (+) and product (×) types. . . . . 10 2.10 The core 휇-calculus ̃휇 ...... 12 2.11 The 휇-calculus ̃휇 with user-defined (co-)datatypes...... 17 2.12 Evaluation rules and strategies in the 휇-calculus ̃휇 with user-defined (co-)datatypes. . . . 20

3.1 The 휆-calculus with datatypes and constructors...... 26 3.2 Evaluating 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))) using a call-by-value strategy in the 휇- ̃휇 calculus...... 28 3.3 Evaluating 푡푎푖푙 (푡푎푖푙 (퐶표푛푠 푥 (퐶표푛푠 푦 푁푖푙))) using the improved version of Conv that generates call-stacks for nested function applications. These reduction steps are the same for both call-by-name and call-by-value evaluation...... 30 3.4 The syntax of MMH...... 43 3.5 An example MMH program ...... 45

4.1 Changes to the 휇-calculus ̃휇 to support the Hindley-Milner type system...... 52 4.2 Typing rules for let-bindings in MMH. These rules can be used for top-level definitions and where-clauses too...... 53 58 LISTING OF FIGURES Bibliography

[Aug84] Lennart Augustsson. “A compiler for lazy ML”. In: Proceedings of the 1984 ACM Symposium on LISP and functional programming. ACM. 1984, pp. 218–227. [Aug85] Lennart Augustsson. “Compiling pattern matching”. In: Conference on Functional Program- ming Languages and Computer Architecture. Springer. 1985, pp. 368–381. [CH00] Pierre-Louis Curien and Hugo Herbelin. “The duality of computation”. In: ACM sigplan notices 35.9 (2000), pp. 233–243. [Chu36] Alonzo Church. “An unsolvable problem of elementary number theory”. In: American jour- nal of mathematics 58.2 (1936), pp. 345–363. [Chu40] Alonzo Church. “A formulation of the simple theory of types”. In: The journal of symbolic logic 5.2 (1940), pp. 56–68. [DA19] Paul Downen and Zena M Ariola. “Compiling With Classical Connectives”.In: arXiv preprint arXiv:1907.13227 (2019). [Dow+16] Paul Downen et al. “Sequent calculus as a compiler intermediate language”. In: Proceed- ings of the 21st ACM SIGPLAN International Conference on Functional Programming. 2016, pp. 74–88. [Dow17] Paul Downen. “Sequent Calculus: A Logic and a Language for Computation and Duality”. In: (2017). [Gen35] Gerhard Gentzen. “Untersuchungen über das logische Schließen. I”.In: Mathematische zeitschrift 39.1 (1935), pp. 176–210. [Her05] Hugo Herbelin. “C’est maintenant qu’on calcule: au cœur de la dualité”.In: Mémoire d’habilitation, available from cited url (2005). [Mau+17] Luke Maurer et al. “Compiling without continuations”. In: ACM SIGPLAN Notices. Vol. 52. 6. ACM. 2017, pp. 482–494. [Men19] Alejandro Serrano Mena. “Increasing Code Reuse”. In: Practical Haskell. Springer, 2019, p. 71. [MH88] John C Mitchell and Robert Harper. “The essence of ML”. In: Proceedings of the 15th ACM SIGPLAN-SIGACT symposium on Principles of programming languages. 1988, pp. 28–46. [Mil78] Robin Milner. “A theory of type polymorphism in programming”. In: Journal of computer and system sciences 17.3 (1978), pp. 348–375. [Wel99] Joe B Wells. “Typability and type checking in System F are equivalent and undecidable”. In: Annals of Pure and Applied Logic 98.1-3 (1999), pp. 111–156.