A Blend of Intersection Types and Union TypesbyBaber Rehman

A thesis submitted in partial fulfillment of the requirements forthe degree of Doctor of Philosophy
at The University of Hong Kong

August 2023

Abstract of thesis entitled“A Blend of Intersection Types and Union Types”
Submitted byBaber Rehman
for the degree of Doctor of Philosophy
at The University of Hong Kong
in August 2023

Languages define the constructs and protocols of communication. Programming languages, in particular, define the constructs that assist in communicating with machines.
With the growing complexity and need of software applications, it has become a challenge
to design an expressive, yet simple programming language. The complexity in a language
design mostly occurs due to the interaction of disparate features. This thesis is an effort insimplifying and formally studying the design of intersection and union types in programming languages.
Intersection and union types are advance and powerful features available in many modern
programming languages. Intersection types provide an interface to introduce an expressionof multiple types. Union types, on the other hand, provide an interface to express an expression of variant types. Interaction of intersection and union types is known to be non-trivial
in theory. This thesis examines the interaction of intersection and union types. Our study
starts with a deterministic type-based switch expression for the elimination of union types.
Disjointness plays an integral role in keeping the calculus deterministic, which ensures thatno two branches of a type-based switch expression overlap. Thus the scrutinee falls in a maximum of one branch. The resulting calculus is called λu . We further extend λu with powerful
and advance features of intersection types, subtyping distributivity, nominal types, and polymorphism. Moreover, we study λu with the merge operator. An extension with the merge
operator poses novel challenges in determinism.
Intersection and union types are well-known dual features. We also examine the duality ofintersection and union types formally in this thesis. The duality unifies some of the subtyping rules and reduces the theoretical complexity of a system with dual features. The benefits
include the reduction in number of subtyping rules and simplified proofs for certain theo-rems such as subtyping transitivity. All of the metatheory of this thesis has been formalized
in the Coq theorem prover.

An abstract of 310 words

To my late father...

Declaration
I declare that this thesis represents my own work, except where due acknowledgment is made,and that it has not been previously included in a thesis, dissertation or report submitted tothis University or to any other institution for a degree, diploma or other qualifications.

...............................
Baber Rehman
August 2023

iAcknowledgments
I have no words to show the gratitude towards my advisor Dr. Bruno C. d. S. Oliveira andI think it certainly cannot be expressed in words. The study of programming languages andspecifically the type systems was an alien universe for me before starting the PhD. I haveprogrammed in a few programming languages but never really thought about the theoreticalcomplexity involved behind the design of a sound programming language. All credits due toBruno’s guidance which made me able to learn a glimpse of this once an alien universe. I am
grateful to Bruno for his guidance and support throughout my doctoral studies.
I would like to extend my sincere gratitude towards the examiners, reviewers, and thereferees. The comments, suggestions, and recommendations from Prof. Sukyoung Ryu, Dr.
H.F. Ting, Dr. Chenshu Wu, Dr. Sergio D.N. Lourenco, Dr. Lionel Parreaux, Dr. MarcoCampion, Dr. Khurram Shahzad, and Dr. Imran Farid Khan have been imperative.
Afterwards, the support from the Snow (Xuejing Huang) has been incredible. Thank you
for showing me a way out when I was lost in in my research. Moving on, the insightful
philosophical discussions with Alvin (Mingqi Xue) have been inspiring. This has been a great
pleasure for me to meet with Alvin over a meal and discuss various social topics. Perhaps
the homo sapiens may learn from their recurring mistakes someday and the world would
be a better place. My sincere thanks to all of my colleagues, Xuan Bi (Jeremy), Haoyuan
Zhang, Yanlin Wang (Grace), Yanpeng Yang (Linus), Weixin Zhang, Ningning Xie, Jinxu
Zhao (Jimmy), Yaoda Zhou, Yaozhu Sun, Wenjia Ye, Xu Xue, Chen Cui, Jinhao Tan, Shengyi
Jiang, Litao Zhou (Tony), and Han Xu.
Hong Kong blessed me with the amazing experiences and people. I am grateful to the
New College tutorial team and specifically to Prof. CHOW Y.M. Amy for her support andguidance. Sustainable learning from the New College would be an integral part of my career.
I am thankful to the beautiful souls I met in Hong Kong. Sharas, Sabir, Niraj, Shujahat,
Huzaifa, Mahram, Adeel, Abdullah, Feroz, Atta, Najam, Nadeem and Hassan. I would liketo take the opportunity and express my gratitude to my friends and colleagues at CloudPlex
PVT LTD, where I worked after my Bachelor’s degree. Most of all, Asad Faizi and Haseeb
Humayun, thank you for your support. Importantly, I am grateful to Asad Faizi for showing
me around Seattle post OPLSS’19.

iii

Being a recipient of the University Postgraduate Fellowship (UPF), I am much obliged
to the Lee Shau Kee Postgraduate Fellowship donors for the award. Last but not the least,
thanks to my wife, mother and siblings for their incredible support. This would not havebeen possible without your support. A supporting partner can make the hard times easier tobear. I have felt that through thick and thin with my adorable wife.

iv

Contents
Declarationi

Acknowledgmentsiii

List of Figuresix

List of Tablesxi

I

Preface11Introduction

3

1.1

Union types and disjoint switch expressions . . . . . . . . . . . . . . . . .

6

1.2

Duality of subtyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10

1.3

Outline and contributions . . . . . . . . . . . . . . . . . . . . . . . . . . .

132

Background

17

2.1

Intersection Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

2.2

Merge Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19

2.3

Tagged Union Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

2.4

Un-tagged Union Types . . . . . . . . . . . . . . . . . . . . . . . . . . . .

22

2.5

Type-directed Elimination forms for Union Types . . . . . . . . . . . . . .

23

2.6

Union Types and Disjoint Switches in Ceylon . . . . . . . . . . . . . . . .

25

2.7

Nullable Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

26

2.8

Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

2.9

Duality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28

IIDeterministic Union Elimination (λu )31

3Union Types with Disjoint Switches

33

3.1

33

Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

v

Contents
3.2

3.3

3.4

4

vi

37

3.2.1

Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

37

3.2.2

Subtyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

38

3.2.3

Disjointness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

38

3.2.4

Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

40

3.2.5

Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . .

42

3.2.6

Type Soundness and Determinism . . . . . . . . . . . . . . . . . .

43

3.2.7

An Alternative Specification for Disjointness . . . . . . . . . . . .

44

λu with Intersections, Distributive Subtyping and Nominal Types . . . . . .

44

3.3.1

Syntax, Well-formedness and Ordinary Types . . . . . . . . . . . .

45

3.3.2

Distributive Subtyping . . . . . . . . . . . . . . . . . . . . . . . .

46

3.3.3

Disjointness Specification . . . . . . . . . . . . . . . . . . . . . . .

49

3.3.4

Algorithmic Disjointness . . . . . . . . . . . . . . . . . . . . . . .

51

3.3.5

Typing, Semantics and Metatheory . . . . . . . . . . . . . . . . . .

53

Switches with Disjoint Polymorphism and Empty Types . . . . . . . . . . .

54

3.4.1

Disjoint Polymorphism . . . . . . . . . . . . . . . . . . . . . . . .

55

3.4.2

A More General Subtyping Rule for Bottom Types . . . . . . . . .

59

3.4.3

Implementation of Disjoint Switches . . . . . . . . . . . . . . . . .

60

Revisiting Disjointness

61

4.1

Disjointness with Intersection and Union Types . . . . . . . . . . . . . . .

62

4.1.1

Disjointness, Union Ordinary, and Union Splittable Types . . . . .

63

4.1.2

Essence of Union Ordinary Types . . . . . . . . . . . . . . . . . .

65

4.1.3

Essence of Union Splittable Types . . . . . . . . . . . . . . . . . .

67

4.1.4

Metatheory with Union Ordinary and Union Splittable Types . . .

71

Redesigning Disjoint Polymorphism . . . . . . . . . . . . . . . . . . . . .

73

4.2.1

Disjointness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

74

4.2.2

Subtyping, typing, and operational semantics . . . . . . . . . . . .

76

4.2

5

The Union Calculus λu . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Togetherness: Switches and Merges

83

5.1

Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

83

5.2

λum Calculus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

85

5.2.1

Syntax, subtyping, and typing . . . . . . . . . . . . . . . . . . . . .

85

5.2.2

Type casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87

5.2.3

Operational semantics . . . . . . . . . . . . . . . . . . . . . . . . .

89

5.2.4

Applicative dispatch and rule step-dispatch . . . . . . . . . . . . .

90

5.2.5

Metatheory of λum . . . . . . . . . . . . . . . . . . . . . . . . . .

92

Contents
5.3

5.4

λum and Dunfield’s system . . . . . . . . . . . . . . . . . . . . . . . . . . .

93

5.3.1

Dunfield’s calculus . . . . . . . . . . . . . . . . . . . . . . . . . . .

94

5.3.2

Completeness with respect to Dunfield’s calculus . . . . . . . . . .

96

Stumbling block: non-determinism . . . . . . . . . . . . . . . . . . . . . .

97

5.4.1

Non-determinism in the presence of merge operator . . . . . . . .

97

5.4.2

Non-determinism in the presence of switch expression . . . . . . .

98

5.4.3

Non-determinism with merge operator and switch expression . . .

99

III Duality of Intersection and Union Types

101

6

103

The Duality of Subtyping (Duotyping)
6.1

6.2

6.3

6.4

Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.1.1

Subtyping with union and intersection types . . . . . . . . . . . . . 103

6.1.2

Subtyping Specifications using Duotyping . . . . . . . . . . . . . . 104

6.1.3

Implementations using Duotyping . . . . . . . . . . . . . . . . . . 107

6.1.4

Discovering new features . . . . . . . . . . . . . . . . . . . . . . . 109

6.1.5

New proof techniques . . . . . . . . . . . . . . . . . . . . . . . . . 112

The λ∧∨♢ calculus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.2.1

Syntax and Duotyping . . . . . . . . . . . . . . . . . . . . . . . . . 114

6.2.2

Semantics and type soundness . . . . . . . . . . . . . . . . . . . . 116

6.2.3

Summary and Comparison . . . . . . . . . . . . . . . . . . . . . . 117∧∨ calculus
The Fk♢. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

6.3.1

Syntax and Duotyping . . . . . . . . . . . . . . . . . . . . . . . . . 118

6.3.2

Semantics and type soundness . . . . . . . . . . . . . . . . . . . . 121

6.3.3

Summary and Comparison . . . . . . . . . . . . . . . . . . . . . . 122

A Case Study on Duotyping . . . . . . . . . . . . . . . . . . . . . . . . . . 123
6.4.1

Case Study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

6.4.2

Does Duotyping provide shorter specifications? . . . . . . . . . . . 125

6.4.3

Does Duotyping increase the complexity of the formalization andmetatheory of the language? . . . . . . . . . . . . . . . . . . . . . 127

6.4.4

Does Duotyping make transitivity proofs simpler? . . . . . . . . . . 128

6.4.5

Is Duotyping a generally applicable technique? . . . . . . . . . . . 128

vii

Contents

IV Related Work

131

7

133

Related Work
7.1

Union Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

7.2

Disjoint Intersection Types . . . . . . . . . . . . . . . . . . . . . . . . . . 135

7.3

Overloading and Dynamic Dispatch . . . . . . . . . . . . . . . . . . . . . 136

7.4

Duality in Logic and Programming Language Theory . . . . . . . . . . . . 137

7.5

Generalizations in Type Systems and Type Theory . . . . . . . . . . . . . . 138

V

Epilogue

141

8

Conclusion and Future Work

143

8.1

Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

8.2

Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
8.2.1

Determinism for λum . . . . . . . . . . . . . . . . . . . . . . . . . 143

8.2.2

Multiple Interface Inheritance . . . . . . . . . . . . . . . . . . . . 147

8.2.3

Explicit Disjointness of Nominal Types . . . . . . . . . . . . . . . . 148

8.2.4

Gradual Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

Bibliography151

VI Technical Appendix

163

A Alternative Disjointness

165

A.1 Common Ordinary Subtypes (COST) . . . . . . . . . . . . . . . . . . . . . 165
A.2 Disjointness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
A.3 Typing, Operational Semantics, and Type-safety . . . . . . . . . . . . . . . 168

viii

List of Figures
2.1

Ceylon’s subtyping hierarchy. Note that Null only has Nothing as its subtype.

27

3.1

Syntax and subtyping for λu . . . . . . . . . . . . . . . . . . . . . . . . . . .

37

3.2

Bottom-like types, algorithmic disjointness and typing for λu . . . . . . . .

41

3.3

Operational semantics and approximate type definitions for λu . . . . . . . .

42

3.4

Syntax and well-formedness. . . . . . . . . . . . . . . . . . . . . . . . . . .

45

3.5

Distributive subtyping for λu with intersection types and nominal types. . .

47

3.6

Algorithmic subtyping for λu with distributivity, intersection and nominal
types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3.7

Lowest ordinary subtypes function and additional typing rule for λu withintersection types and nominal types. . . . . . . . . . . . . . . . . . . . . .

3.8

48
52

Syntax, additional typing, subtyping, and reduction rules for λu with polymorphism. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

Subtyping for polymorphic λu . . . . . . . . . . . . . . . . . . . . . . . . .

57

3.10 Typing for polymorphic λu . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

3.11 Operational Semantics for polymorphic λu . . . . . . . . . . . . . . . . . .

59

4.1

Syntax and subtyping for λu with intersection types. . . . . . . . . . . . . .

62

4.2

Syntax, union ordinary, and union splittable types. . . . . . . . . . . . . . .

63

4.3

Disjointness based on splittable types for λu . . . . . . . . . . . . . . . . . .

64

4.4

Disjointness without union ordinary and union splittable types for λu . . . .

66

4.5

Typing and operational semantics for λu . . . . . . . . . . . . . . . . . . . .

72

4.6

Syntax, union ordinary, and union splittable types for polymorphic λu . . . .

73

4.7

Disjointness with union splittable types for polymorphic λu . . . . . . . . .

75

4.8

Subtyping for λu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

4.9

Operational Semantics for λu . . . . . . . . . . . . . . . . . . . . . . . . . .

78

4.10 Operational Semantics for λu . . . . . . . . . . . . . . . . . . . . . . . . . .

79

5.1

Syntax and subtyping for λum . . . . . . . . . . . . . . . . . . . . . . . . . .

85

5.2

Typing for λum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

86

3.9

ix

List of Figures
5.3

Type casting for λum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

88

5.4

Operational semantics for λum . . . . . . . . . . . . . . . . . . . . . . . . .

90

5.5

Dynamic dispatch, dynamic type and input type relation for λum . . . . . .

91

5.6

Source syntax and source typing of Dunfield’s calculus. . . . . . . . . . . .

95

6.1

Subtyping for union and intersection types. . . . . . . . . . . . . . . . . . . 104

6.2

The Duotyping relation for a calculus with union and intersection types. . . 106

6.3

Haskell code for implementing an algorithmic formulation of Duotyping rules. 108

6.4

The Duotyping relation for simply typed lambda calculus calculus. . . . . . 112

6.5

Syntax and Duotyping relation for union and intersection types. . . . . . . 114

6.6

Typing and reduction for λ∧∨♢ . . . . . . . . . . . . . . . . . . . . . . . . . . 116

6.7∧∨ . . . . . . . . . . . . . . . 119
Syntax and additional rules for Duotyping in Fk♢

6.8

Typing and reduction of the duotyped kernel F<: . . . . . . . . . . . . . . . 121

A.1 Syntax, subtyping, ordinary, union ordinary and union splittable types for
λu with intersection types. . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
A.2 Common ordinary subtypes based on union splittable types for λu . . . . . . 167
A.3 Typing for λu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

x

List of Tables
6.1

Description of all systems. . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

6.2

Declarative Duotyping rules of all systems. . . . . . . . . . . . . . . . . . . 126

6.3

Comparing the features and number of rules with subtyping and Duotyping. 127

6.4

SLOC of traditional subtyping and Duotyping systems. . . . . . . . . . . . 127

6.5

SLOC for transitivity proofs. . . . . . . . . . . . . . . . . . . . . . . . . . . 128

xi

Part I
Preface1

1 Introduction
Programming languages are the first mode of communication between humans and computers. As the name indicates, programming languages are primarily used to program computers and machines in general. The robustness of a program largely depends upon the programming language used to build that program. Programming languages have evolved over the
decades. Researches are working in various areas in the programming languages including
security, parallel computing, domain specific languages, compilers and quantum computing
among others.
Type systems [Cardelli 1996; Pierce 2002b] is one such area in the study of programming
languages. Type systems received significant attention in programming languages since they
were first introduced. Programming languages can roughly be categorized into statically
typed and dynamically typed languages considering the type system. C++ [Stroustrup 1986],
Java [Gosling et al. 2000] and Scala [Odersky et al. 2004] are some of the examples of statically
typed programming languages. Whereas, JavaScript [Crockford 2008], Python [Van Rossum
et al. 1995] and most scripting languages are classified as dynamically typed programming
languages. Our focus will mainly revolve around statically typed programming languages
in this thesis. Statically typed languages perform type-checking at compile time. Whereas,
dynamically typed languages may perform type-checking at runtime.
Nevertheless, types play an essential role both in statically and dynamically typed languages. Types provide a robust mechanism to introduce and eliminate program expressions
by classifying the expressions in various categories or sets. A sound type system ensures thata well-typed expression is used in the program where expected. In statically typed languages,
the type system raises an error at compile time if the expressions are ill-typed. The following
program illustrates the use of types in a program:
Int add (x : Int, y : Int) {return x + y}
result = add (1, 2)//accepted31 Introduction
The above program will run fine because add is expecting two integers. The arguments 1 and2 being passed to the add function are integers. Whereas, the following call to add will berejected at compile time:add (1, "Hello")//rejected

The problem here is that we cannot and should not be adding an integer and a string together.
In this case, type system statically detects that "Hello" is not an integer and restrains fromthe compilation. If not detected before compilation, such errors may cause the program toterminate abnormally during execution. On the other hand, in an untyped programming
language, following error may go undetected during compilation:add (x, y) {return x + y}
add (1, "Hello")//undetected error

Notice that no types are involved in this program. There is no static guarantee that either weare adding two integers, strings or booleans. Such a program may crash at runtime.
Type systems further categorize programs into well-typed and ill-typed programs. Illtyped programs may contain a runtime error thus rejected at compile time. Whereas, welltyped programs do not go wrong because of typing errors. Thus type system guarantees theabsence of certain runtime behaviors where program may go wrong. Types also provide a formof program documentation. For example, by looking at the return type of a function one canlearn what should be expected from the function.
While type systems come with benefits, the study of type systems is hard. Adding a newfeature with sound theoretical standings is non-trivial. Normally, the theoretical complexityof the type system increases with the number of features and how various features interact.
Interacting features are hard to deal with compared to the orthogonal features. The typesystem becomes sophisticated with more advance features resulting in significant increaseof complexity in the metatheory. Intersection and union types [Barbanera et al. 1995; Dunfield 2014; Muehlboeck and Tate 2018; Pierce 2002a] are such sophisticated advance features
which can significantly increase the theoretical complexity of a sound type system.
Intersection types allow to construct a term of multiple (maybe) non-overlapping types.
Union types allow a term to have alternative types. Many modern programming languages
support intersection and union types, such as Scala [Odersky 2021] and Ceylon [King 2013].
This illustrates the significance of these features in today’s programming universe. The intersection and union types are useful to encode various programming constructs such asfunction overloading [Cardelli and Wegner 1985; Castagna et al. 1995; Wadler and Blott41989] and multiple inheritance [Compagnoni and Pierce 1996]. The following Scala codedemonstrates the use of intersection and union types in practice:trait Person () {}trait Robot () {}//illustrating intersection types
class Hybrid () extends Person with Robot {}//illustrating union types
def speak(x: Person | Robot): String = {x matchcase p: Person ⇒ "I am person"case r: Robot ⇒ "I am robot"}

The first two lines of the code declare two traits i.e. Person and Robot. We then create a classnamed Hybrid. Note that the class Hybrid extends two traits, the Person and Robot using the
keyword with . In Scala, the keyword with uses intersection types for multiple interface inheritance. Next, we define a function speak. It takes an input of type Person or Robot (Person| Robot). The function speak then returns different value depending upon the specific type ofparameter x. Such a type-based case analysis on an expression of union of types is analogous
to function overloading [Cardelli and Wegner 1985; Castagna et al. 1995; Wadler and Blott1989]. Similar to function overloading, the type-based case analysis provides the flexibility of executing disparate code depending on the dynamic (specific) type of the expression.
Another Scala example1 illustrating the usability of intersection and union types is:trait Fish {def swim(): Unit = {}}trait Bird {def fly(): Unit = {}}//illustrating intersection types
class FlyingFish() extends Fish with Bird {override def swim(): Unit = {}override def fly(): Unit = {}}var flyingFish: Fish & Bird = FlyingFish()1

https://medium.com/@Methrat0n/intersection-type-in-scala-5320dedf5cf.

51 Introduction
flyingFish.fly() //safe
flyingFish.swim() //safe//illustrating union types
def move(FishOrBird: Fish | Bird): Unit =FishOrBird matchcase fish: Fish ⇒ fish.swim()case bird: Bird ⇒ bird.fly()

Although, intersection and union types can encode such powerful and advance programming features. The interaction of intersection and union types is known to be non-trivial intheory. For example, the challenges involved in the subtyping distributivity of intersection
and union types [Barendregt et al. 1983]. Huang and Oliveira [2021] elegantly explain suchchallenges. This thesis examines the interaction of intersection and union types in various
theoretical and practical settings. It consists of two main technical parts:
1. In part 1 (Chapters 3 to 5), we discuss the constructs to introduce and eliminate intersection types and union types respectively in a type-safe manner. Specifically, we startwith a type-based switch expression for union elimination based upon the disjointness
called λu calculus. λu is designed to keep the branches of a switch expression disjoint.
Disjointness prevents a value to fall in multiple branches and ensures the determinismof the calculus. This work is dual to disjoint intersection types [Oliveira et al. 2016] inits simplified form. We then extend λu with advanced features including intersection
types, nominal types, subtyping distributivity and disjoint polymorphism. Moreover,
we study λu with the merge operator which results in loss of determinism still retaining
type soundness.
2. Part 2 (Chapter 6) consists of a study of subtyping relation which exploits the dualityof intersection and union types. We show that the theoretical complexity of subtyping
decreases by exploiting the duality of intersection types and union types. We also
explore extra features that come for free with the duality.

1.1 Union types and disjoint switch expressions
Most programming languages support some mechanism to express terms with alternative
types. Algol 68 [Van Wijngaarden et al. 1969; van Wijngaarden et al. 2012] included a formof tagged unions for this purpose. With tagged unions an explicit tag distinguishes between
different cases in the union type. Such an approach has been adopted by functional languages, like Haskell, ML, or Ocaml, which allow tagged unions (or sum types [Pierce 2002b]),6

1.1 Union types and disjoint switch expressions
typically via either algebraic datatypes [Burstall et al. 1981] or variant types [Garrigue 1998].
Languages like C or C++ support untagged union types where values of the alternative types
are simply stored at the same memory location. However, there is no checking of types whenaccessing values of such untagged types. It is up to the programmer to ensure that the proper
values are accessed correctly in different contexts; otherwise the program may produce errors
by accessing the value at the incorrect type.
Modern OOP languages, such as Scala 3 [Odersky 2021], Flow [Chaudhuri 2015], TypeScript [Bierman et al. 2014], and Ceylon [King 2013], support a form of untagged union
types. In such languages a union type A∨B denotes expressions which can have type A or type
B. Union types have grown to be quite popular in some of these languages. A simple Google
search on questions regarding union types on StackOverflow returns around 6620 results (atthe time of writing), many of which arising from TypeScript programmers. Union types canbe useful in many situations. For instance, union types provide an alternative to some formsof overloading and they enable an approach to nullable types (or explicit nulls) [Gunnerson
2012; Nieto et al. 2020].
Elimination construct for union types. To safely access values with union types, someform of elimination construct is needed. Many programming languages often employ a language construct that checks the types of the values at runtime for this purpose. Several elimination constructs for (untagged) union types have also been studied in the research literature [Benzaken et al. 2003; Castagna et al. 2014a; Dunfield 2014]. Typically, such constructs
take the form of a type-based case analysis expression. An example of the elimination construct for union types in Scala is shown next:
var str: String | Null = nullstr matchcase s: String ⇒ s.length
case n: Null
⇒ -1

The code starts by declaring a variable str of type String | Null. It then does a typebased case analysis on str to safely access the length function. Without such a type-based
case analysis, a call to length function (s.length) may result in a runtime error because thelength function is not defined on null values. This code uses a recently proposed explicit
nulls [Nieto et al. 2020] feature of the Scala programming language. The core of this featurelies in revising the subtyping hierarchy of Scala. Interested reader may refer to the original
publication for details. When enabled, explicit nulls allow to explicitly mark the nullable
values. For example, str in this example stores a null value, therefore, it must be having the7

1 Introduction
type Null explicitly. In other words, the following program will be rejected which stores anull value but does not explicitly marks it nullable with the static type of str.
var str: String = null //rejected

Complication with subtyping. A complication is that the presence of subtyping introduces the possibility of overlapping types. For instance, we may have a Student and a Person,
where every student is a person (but not vice-versa). If we try to eliminate a union using
such types we can run into situations where the type in one branch can cover a type in adifferent branch (for instance Person can cover Student). More generally, types can partiallyoverlap and for some values two branches with such types can apply, whereas for some othervalues only one branch applies. Therefore, the design of such elimination constructs has toconsider what to do in situations where overlapping types arise. A first possibility is to have anon-deterministic semantics, where any of the branches that matches can be taken. However,in practice determinism is a desirable property, so this option is not practical. A second possibility, which is commonly used for overloading, is to employ a best-match semantics, where
we attempt to find the case with the type that best matches the value. Yet another option is
to use a first-match semantics, which employs the order of the branches in the case. Various
existing elimination constructs for unions [Benzaken et al. 2003; Castagna et al. 2014a] employ a first-match approach. All of these three options have been explored and studied in theliterature.
They Ceylon programming language. The Ceylon language [King 2013] is a JVMbased language that aims to provide an alternative to Java. The type system is interestingin that it departs from existing language designs, in particular with respect to union types
and method overloading. The Ceylon designers had a few different reasons for this. They
wanted to have a fairly rich type system supporting, among others: subtyping; generics withbounded quantification; union and intersection types; and type-inference. The aim was to support most features available in Java, as well as a few new ones. However the Ceylon designers
wanted to do this in a principled way, where all the features interacted nicely. A stumbling
block towards this goal was Java-style method overloading [Tate 2011]. The interaction of
overloading with other features was found to be challenging. Additionally, overloaded methods with overlapping types make reasoning about the code hard for both tools and humans.
Algorithms for finding the best match for an overloaded method in the presence of rich typesystem features (such as those in Ceylon) are challenging, and not necessarily well-studied
in the existing literature. Moreover allowing overlapping methods can make the code harder
to reason for humans: without a clear knowledge of how overloading resolution works, pro-8

1.1 Union types and disjoint switch expressions
grammers may incorrectly assume that a different overloaded method is invoked. Or worse,
overloading can happen silently, by simply reusing the same name for a new method. These
problems can lead to subtle bugs. For these reasons, the Ceylon designers decided not tosupport Java-style method overloading.
To counter the absence of overloading, the Ceylon designers turned to union types instead,but in a way that differs from existing approaches. Ceylon includes a type-based switch construct where all the cases must be disjoint. If two types are found to be overlapping, then the
program is statically rejected. For example, the following program will statically be rejected
in Ceylon (where Student is a subtype of Person):// Student <: Person, PG <: Student
Student | Person human = PG();
switch (human)case (is Person) {}case (is Student) {}Whereas, the following program is accepted:
Person | Robot hybrid = Robot();
switch (hybrid)case (is Person)case (is Robot){}{}

Many common cases of method overloading, which are clearly not ambiguous, can bemodelled using union types and disjoint switches. By using an approach based on disjointness, some use cases for overloading that involve Java-style overloading with overlappingtypes are forbidden. However, programmers can still resort to creating non-overloaded
methods in such a case, which arguably results in code easier to reason about. Disjointness
ensures that it is always clear which implementation is selected for an “overloaded” method,and only in such cases overloading is allowed2 . In the switch construct, the order of the cases
does not matter and reordering the cases has no impact on the semantics, which can also aid
program understanding and refactoring. Finally, from the language design point of view, it
would be strange to support two mechanisms (method overloading and union types), which
greatly overlap in terms of functionality.
Theoretical study of disjoint switches. While implemented in the Ceylon language,
disjoint switches have not been studied formally. To our knowledge, the work by Muehlboeck
2

Ceylon does allow dynamic type tests, which in combination with switches can simulate some overlapping.

9

1 Introduction
and Tate [2018] is the only work where Ceylon’s switch construct and disjointness are mentioned. However, their focus is on algorithmic formulations of distributive subtyping with
unions and intersection types. No semantics of the switch construct is given. Disjointness is
informally defined in various sentences in the Ceylon documentation. It involves a set of 14
rules described in English [King 2016]. Some of the rules are relatively generic, while others
are quite language specific. Interestingly, a notion of disjointness has already been studied
in the literature for intersection types [Oliveira et al. 2016]. That line of work studies calculi
with intersection types and a merge operator [Reynolds 1988]. Disjointness is used to prevent ambiguity in merges, which can create values with types such as Int ∧ Bool. Only values
with disjoint types can be used in a merge.

1.2 Duality of subtyping
Subtyping is a concept frequently encountered in many programming languages and calculi. It is also a pervasive and fundamental feature in Object-Oriented Programming (OOP).
Various forms of subtyping exist for different type system features, including intersection
types [Barbanera et al. 1995], union types [Barbanera et al. 1995] or bounded quantification [Canning et al. 1989]. Modern OOP languages such as Scala [Odersky et al. 2004],
Ceylon [King 2013], Flow [Chaudhuri 2015] or TypeScript [Bierman et al. 2014] all support
the aforementioned type system features.
As programming languages evolve, new features are added. This requires that subtyping
for these new features is developed and also integrated with existing features. However, thedesign and implementation of subtyping for new features is quite often non-trivial. There areseveral, well-documented issues in the literature. These include finding algorithmic forms
for subtyping (for instance doing transitivity elimination) [Steffen and Pierce 1994] or proving metatheoretical properties such as transitivity or narrowing [Abel and Rodriguez 2008].
Such issues occur, for instance, in some of the latest developments for OOP languages, suchas the DOT calculi (which model the essence of Scala) [Amin et al. 2012]. One possible way
to reduce the non-trivial amount of work needed to develop new features, would be if two
related features could be develop at once with a coherent design. This thesis explores a new
methodology that enables such benefits.
Normally programming language features are designed independently of each other. However there are features that are closely related to each other, and can be viewed as dual features.
Various programming language features are known to be dual in programming language
theory. For instance sum and product types are well-known to be duals [Bird and de Moor
1996]. Similarly universal and existential quantification are dual concepts as well [Barwise101.2 Duality of subtyping
and Cooper 1981]. Moreover duality is a key concept in category theory [Lane 1998] andmany abstractions widely used in functional programming (such as Monads and CoMonads [Uustalu and Vene 2008]) are also known to be duals.
In OOP type systems dual features are also common. For instance all OOP languages contain a top type (called Object in Java or Any in Scala), which is the supertype of all types.
Many OOP languages also contain a bottom type, which is a subtype of all types. Top andbottom types can be viewed as dual features, mirroring the functionality of each other. Intersection and union types are another example of dual features. The intersection of two types
A and B can be used to type a value that implements both A and B. The union of two types A
and B can be used to type a value that implements either A or B.
Duality in OOP and subtyping is often only informally observed by humans. For instance,by simply understanding the behaviour of the features and observing their complementaryroles, as we just did in the previous paragraph. At best duality is more precisely observed bylooking at the rules for the language constructs and their duals and observing a certain symmetry between those rules. However existing formalisms and language designs for type systems and subtyping relations do not directly incorporate duality. Unfortunately this meansthat an opportunity to exploit obvious similarities between features is lost.
This thesis proposes a novel methodology for designing subtyping relations that exploits
duality between features directly in the formalism. At the core of our methodology is a generalization of subtyping relations, which we call Duotyping. Duotyping is parameterized by
the mode of the relation. One of these modes is the usual subtyping, while another modeis supertyping (the dual of subtyping). Using the mode it is possible to generalize the usual
rules of subtyping to account not only for the intended behaviour of one particular language
construct, but also of its dual. This means that the behaviour of the language construct andits dual is modelled by a single, common set of rules. In turn this ensures that the behaviourof the two features is modelled consistently. Moreover it also enables various theorems/properties of subtyping to be generalized to account for the dual features. Therefore, Duotyping
offers similar benefits to the how duality is exploited in category theory. More concretely,
Duotyping brings multiple benefits for the design of subtyping relations, which are discussed
next.
Shorter specifications. When duality is exploited in specifications of subtyping it leads
to shorter specifications because rules for dual features are shared. This also ensures a consistent design of the rules between the dual features directly in the formalism. Such consistency
is not enforceable in traditional formulations of subtyping where the rules are designed separately, and thus their design is completely unconstrained with respect to the dual feature. A

11

1 Introduction
concrete example that illustrates shorter specifications is a traditional subtyping relation with
top, bottom, union and intersection types, which would normally have 8 subtyping rules for
those constructs. In a design with Duotyping we only need 5 subtyping rules. Basically weneed only half of the rules (4 in this case) to model the feature-specific rules, plus an additional duality rule which is generic (and plays a similar role to reflexivity and transitivity).
“Buy” one feature get one feature for free! Duality can lead to the discovery of newfeatures. While top and bottom types, or intersection and union types are well-known in theliterature (and understood to be duals), other features in languages with subtyping do nothave a known dual feature in the literature. This is partly because, when a language designer
employs traditional formulations of subtyping, he/she is often only interested in the designof a feature (but not necessarily of its dual). Even for the case of union and intersection types,
intersection types were developed first and the development of union types occurred years
later. Because the dual feature is often also useful, the traditional way to design subtyping
rules represents a loss of opportunity to get another language feature essentially for free.
One well-known example of a language feature that has been widely exploited in the literature, but its dual feature has received much less attention is bounded quantification [Cardelliand Wegner 1985]. Bounded quantification allows type variables to be defined with upperbounds. However lower bounds are also useful. One can think of universal quantification
with lower bounds as a dual to universal quantification with upper bounds. The essence of(upper) bounded quantification is captured by the well-known F<: calculus [Cardelli andWegner 1985]. However, as far as we know, there is no design that extends F<: with lower
bounded quantification in the literature. Applying a Duotyping design to F<: gives us, naturally, the two features at once (lower and upper bounded quantification), as illustrated in
our Section 6.3. Such generalization of bounded quantification is related to the recent formof universal quantification with type bounds employed in Scala and the DOT family of calculi [Amin et al. 2012]. However, while Scala’s type bounds are more expressive than what
we propose, they are also much more complex and are in fact one of the key complicationsin the type systems of languages like Scala. Most DOT calculi require a built-in transitivity
rule in subtyping because it is not known how to eliminate transitivity. In contrast, the generalization of F<: proposed by us has a formulation of subtyping where transitivity can beproved as a separate lemma.
New proof techniques. Designs of subtyping with duality also enable new proof techniques that exploit such duality. For instance there are various theorems that can be stated forboth a feature and its dual, instead of having separate theorems for both. Some of the proper-121.3 Outline and contributions
ties of union and intersection types are examples of this. Moreover, Duotyping also enables
new proof techniques to prove traditionally hard theorems such as transitivity. Surprisingly
to us, for the vast majority of the calculi that we have applied Duotyping to, transitivity proofs
have been considerably simpler than their corresponding traditional formulations due to theuse of Duotyping!
Shorter implementations. Finally Duotyping also enables for shorter implementations.
The benefits of shorter implementations are similar and follow from the benefits of shorter
specifications. However there is a complicating factor when moving from a relational specification into an implementation: the duality rule is non-algorithmic. This is akin to what
happens with transitivity, which is often also used in declarative formulations of subtyping.
Eliminating transitivity to obtain an algorithmic system can often be a non-trivial challenge(as illustrated, for instance, by the DOT family of calculi [Amin et al. 2012]). However, weshow that there is a simple and generally applicable technique that can be used to move froma declarative formulation of Duotyping into an algorithmic version. This contrasts with transitivity, for which there is not a generally applicable transitivity elimination technique.

1.3 Outline and contributions
In this thesis we discuss the introduction and elimination constructs for intersection andunion types respectively. We also discuss the challenges involved in the integration of such
constructs in a calculus. Moreover, we study the duality of subtyping with intersection andunion types. We show that the duality of subtyping decreases the theoretical complexity of acalculus with dual features. All of the metatheory of this thesis has been formalized in Coq3
theorem prover and is available at:• Chapter 3: https://github.com/baberrehman/disjoint-switches• Chapter 4: https://github.com/baberrehman/phd-thesis-artifact/tree/main/artifact/chap4• Chapter 5: https://github.com/baberrehman/phd-thesis-artifact/tree/main/artifact/chap5• Chapter 6: https://github.com/baberrehman/coq-duotyping• Appendix A: https://github.com/baberrehman/phd-thesis-artifact/tree/main/artifact/appendixA3

Coq is an interactive theorem prover where the proofs are written by humans and are machine-checked.

13

1 Introduction
Contributions towards disjoint switches. We study union types with disjoint switches
formally and in a language independent way. We present the union calculus (λu ), which includes disjoint switches and union types. The notion of disjointness in λu is interesting inthe sense that it is the dual notion of disjointness for intersection types. We prove several
results, including type soundness, determinism and the soundness and completeness of algorithmic formulations of disjointness. We also study several extensions of λu . In particular,
the first extension (discussed in Section 3.3) adds intersection types, nominal types and distributive subtyping to λu . It turns out such extension is non-trivial, as it reveals a challengethat arises for disjointness when combining union and intersection types: the dual notion of
disjointness borrowed from disjoint intersection types no longer works, and we must employ
a novel, more general, notion instead. Such change also has an impact on the algorithmic
formulation of disjointness, which must change as well. We also study two other extensions
for parametric polymorphism and a subtyping rule for a class of empty types in this thesis.
We prove that all the extensions retain the original properties of λu . Furthermore, for our
subtyping relation in Section 3.3 we give a sound, complete and decidable algorithmic formulation by extending the algorithmic formulation employing splittable types by Huang andOliveira [2021].
To illustrate the applications of disjoint switches, we show that they provide an alternative to certain forms of overloading, and they enable a simple approach to nullable (or optional) types. We also study λu with the merge operator [Dunfield 2014; Oliveira et al. 2016;
Reynolds 1988] and the resulting calculus is called λum . The merge operator is an introduction form for intersection types. This addition results in another category of challenges
for determinism. Therefore, λum studied in this thesis is non-deterministic but type-safe.
All the results about λu , its extensions, and λum have been formalized in the Coq theorem
prover.
Contributions towards duality. To evaluate a design based on Duotyping against traditional designs of subtyping, we formalized various calculi with common OOP features (including union types, intersection types and bounded quantification) in Coq in both styles.
Our results show that the metatheory when using Duotyping has similar complexity and size
compared to traditional designs. However, the Duotyping formalizations come with morefeatures (for instance lower-bounded quantification) that dualize other well-known features(upper-bounded quantification). Finally, we also show that Duotyping can significantly simplify transitivity proofs for many of the calculi studied by us.
Outline.

14

The outline of the thesis is:1.3 Outline and contributions• Chapter 2 provides related background. Specifically, this chapter discusses the background of intersection types, merge operator, union types and elimination constructs
for union types. Finally, this chapter discusses the concept of duality in logic and in
programming languages.
• Chapter 3 studies a union calculus for deterministic elimination of union types. The
calculus is called λu . Later parts of Chapter 3 enrich λu with advance features including intersection types, subtyping distributivity, nominal types and a restrictive formof disjoint polymorphism based on the ground types restriction.
• Chapter 4 further increases the expressiveness of disjoint polymorphism. In particular,
this chapter removes ground type restriction from disjoint polymorphism and studies
a novel disjointness algorithm based on splittable types [Huang and Oliveira 2021].
• Chapter 5 studies λu with the merge operator. The merge operator [Dunfield 2014;
Oliveira et al. 2016; Reynolds 1988] is an introduction form for intersection types.
The resulting calculus is called λum . The integration of the merge operator and disjoint
switches result in novel challenges for determinism. Therefore, λum retains type-safety
but lacks determinism. This chapter also studies the completeness of λum with respect
to Dunfield [2014].
• Chapter 6 Studies the duality of subtyping formally with intersection and union types.
The duality of subtyping results in various benefits such as reduced complexity in themetatheory and extra features of lower bounds and lower bounded quantification.
• Chapters 7 and 8 discuss the related work and the future work respectively.
• Appendix A studies another variant of algorithmic disjointness based on the so called
Common Ordinary Subtypes (COST).
A part of this thesis is based on the following publications:• Chapter 3: Baber Rehman, Xuejing Huang, Ningning Xie and Bruno C. d. S. Oliveira.
2022. “Union Types with Disjoint Switches”. In European Conference on Object-Oriented
Programming (ECOOP).
• Chapter 5: Baber Rehman and Bruno C. d. S. Oliveira. 2023. “Type Soundness with
Unrestricted Merges”. (In Submission). Journal of Functional Programming (JFP).
• Chapter 6: Bruno C. d. S. Oliveira, Cui Shaobo and Baber Rehman. 2020. “The Duality of Subtyping”. In European Conference on Object-Oriented Programming (ECOOP).

15

1 Introduction
Prerequisites. A background in type systems is assumed to understand the technical contents of this thesis. Interested readers may go over Types and Programming Languages [Pierce
2002b] or volume 1 and volume 2 of Software Foundations [Pierce et al. 2010] to acquire therelevant technical background.

16

2 Background
This chapter provides background on intersection types, union types, and common approaches
to introduce and eliminate intersection and union types respectively. It then explains the
Ceylon approach to union types, and discusses a few applications of union types. Finally,
it illustrates the concept of duality in logic and in the subtyping of intersection and union
types.

2.1 Intersection Types
Intersection types have a sound history in the study of programming languages and logic
in general. They have been studied by multiple researchers [Barbanera et al. 1995; Dunfield 2014; Muehlboeck and Tate 2018; Pierce 2002a] in various settings. Intersection types
correspond to product types and conjunction in category theory and logic respectively. In
classical logic, types are interpreted as sets and the intersection of types correspond to theintersection of sets. In particular, the intersection of types is viewed as an intersection ofvalues inhabited by the types. The intersection of Int and Bool does not inhabit any values
in this view because set-intersection of the values of Int and Bool is an empty set. Therefore,
the type Int ∧ Bool is an empty type in classical logic. But as per the Curry–Howard correspondence [Curry et al. 1958; Howard 1980], intersection types correspond to conjunctions
and conjunctions correspond to product types. The product of type Int and Bool is inhabitedby a pair such as (1, true). We follow the later interpretation of the intersection types where avalue of the type Int ∧ Bool is inhabited.
In programming languages an expression e has an intersection type A∧B when e is both oftype A and B simultaneously. Therefore, an expression of the type A ∧ B can safely be treatedas an expression of the type A or B. In other words, we can extract the value of any of thegiven types from an expression of intersection of those types. For example:
Int&Bool temp (x : Int) {}Function temp takes an integer value as an input and returns both an integer and a boolean
value. In a calculus with subtyping temp can safely be used in the context where an integer
or a boolean is expected. This is because the return type of temp says that it returns both17

2 Backgroundan integer and a boolean at the same time. Therefore, it is safe to extract an integer as well
as a boolean value from the return value of temp. For example, the following add function
consumes the return value of temp function, filters the integer part and performs addition
operation only on the integer part:Int add (x : Int, y : Int) {return temp(x) + y}

This is because of the inversion of following subtyping rules for intersection types:s-andbs-andcA <: CB <: CA ∧ B <: CA ∧ B <: C

History. Coppo et al. [1981] and Pottinger [1980] initially studied intersection types inprogramming languages to assign meaningful types to terms. The intersection types forthe multiple inheritance are studied by Compagnoni and Pierce [1996]. Extending a classby an intersection of multiple types naturally results in multiple inheritance. Pierce [1991]
studied a calculus with intersection types, union types and polymorphism. Pierce has also
shown the practicality of intersection types using diverse programming examples. Dunfield[2014] studied a calculus with intersection and union types using an elaboration semantics.
Dunfield elaborated intersections to product types and unions to sum types. The intersection types have also been studied in the context of refinement types [Freeman and Pfenning1991]. Refinement types increase the expressiveness of types. They pose a restriction on theformation of intersections and does not allow intersections of certain types. Specifically, in
refinement types, only those intersections are allowed which can be refined. For example:
Int temp2 (x : Int&Real) {}is allowed in such calculi because Int is a refinement of Real. But an intersection of Int∧Bool
is not allowed because none of the types is a refinement of each other.
Introduction and elimination. The traditional typing rule to introduce an expressionof intersection types is:
typ-and

Γ⊢e:A

Γ⊢e:B

Γ⊢e:A∧B
This rule says that if an expression is of type A and type B, then that expression can safely
be treated as of type A ∧ B. It is trivial to say by inversion that if an expression is of type A ∧ B,
then that expression can safely be cast to A and B separately, as illustrated by:18

2.2 Merge Operator
typ-andl

typ-andrΓ⊢e:A∧B

Γ⊢e:A∧B

Γ⊢e:A

Γ⊢e:B

It is important to notice that the rules typ-andl and typ-andr are subsumed by the subsumption rule in a system with subtyping. A natural question arises at this point is what if
we want to construct an expression of two non-overlapping types? Such as Int and Bool. For
example, considering the temp function:
Int&Bool temp (x : Int) {}How would we construct an expression of type Int ∧ Bool as the return value of this function? The traditional introduction rule for intersection types does not have enough expressive power to construct such an expression. The so called merge operator [Dunfield 2014;
Huang and Oliveira 2020; Oliveira et al. 2016; Reynolds 1988] was introduced for this purpose and is discussed next.

2.2 Merge Operator
The intersection types roughly correspond to the product types in category theory. But classical intersections cannot model all of the expressions of category theory. For example, theproduct types allow to construct a pair with the following terms:(1, true) : (Int, Bool)

But the traditional introduction rule for the intersection types (rule typ-and) cannot construct a term having both integer and boolean in it. It is because of the fact that there isno corresponding term level construct which can encode an expression of multiple nonoverlapping types. The merge operator [Dunfield 2014; Huang and Oliveira 2020; Oliveira
et al. 2016; Reynolds 1988] is studied to overcome this shortcoming. The merge operator
significantly increases the term level expressiveness of a calculus. The introduction rule forthe intersection types with the merge operator is:
typ-mergaΓ ⊢ e1 : AΓ ⊢ e2 : BΓ ⊢ e1 ‚‚e2 : A ∧ B[Oliveira et al. 2016]

Notice that e1 and e2 may not necessarily be the same expressions in rule typ-merga. By
exploiting the expressiveness of the merge operator, one can write the following program:x : Int&Bool = 1,,true19

2 Background
Int&Bool extend(x : Int, y : Bool) {return x,,y}The merge operator was first introduced in Forsythe programming language by Reynolds[1997]. Dunfield [2014] further studied the merge operator with union types. Dunfield
followed an elaboration semantics where intersection types elaborate to product types andmerges to pairs. However, the calculus proposed by Dunfield is non-deterministic. For example, an expression 1,,2 may reduce to either 1 or 2. Oliveira et al. [2016] studied disjoint intersection types to overcome the non-deterministic behaviour of the merge operator.
Oliveira et al. [2016] proposed a disjointness constraint for the merge operator. The typing
rule for the merge operator in their calculus is:
typ-mergb

Γ ⊢ e1 : AΓ ⊢ e2 : BA∗BΓ ⊢ e1 ‚‚e2 : A ∧ B
The last premise of the rule typ-mergb imposes a so called disjointness constraint. In summary, non-overlapping types are disjoint types. For example Int is disjoint to Bool but Int is
not disjoint to Int or ⊤. Therefore a merge of 1,,true is allowed but a merge of 1,,2 is notallowed with such a disjointness constraint.
The calculi studied by Dunfield [2014] and Oliveira et al. [2016] follow an elaboration
semantics. Recently, Huang and Oliveira [2020] proposed a direct operational semantics forthe merge operator. Alpuim et al. [2017] further studied disjoint intersection types and themerge operator with disjoint polymorphism. It is a variant of the parametric polymorphism[Canning et al. 1989; Cardelli and Wegner 1985] and allows the following program:
X&Y extend[X * Y] (x : X, y : Y) {return x,,y}X and Y are the type variables. The type variable Y can be instantiated with any type, whereas
X can only be instantiated with types that are disjoint with Y.

The merge operator is useful to encode multi-field records from single-field records [Reynolds
1988]. A core language only needs to support single-field records and single-field record
types. In the presence of the merge operator, multi-field records are simply the merges ofsingle-field records and multi-field records types are the intersections of single-field record
types:
type Student = {name : String} & {id : Int}s : Student = {name = "John"} ,, {id = 123}20//multi-field record type//multi-field record

2.3 Tagged Union Types
Bi et al. [2018a] further studied merge operator with nested composition and family polymorphism. Bi and Oliveira [2018] studied typed first class traits and dynamic inheritancewith static guarantee by exploiting the disjoint intersection types. The first class traits treat
classes or traits as first class citizens where they may be passed to or return from a function, thus enabling dynamic inheritance. This enables type-checking highly dynamic object oriented features which are non-trivial to deal with in traditional OOP languages. Xie
et al. [2020] show that row and bounded polymorphism can be encoded using disjoint polymorphism. The merge operator can also encode function overloading [Castagna et al. 1995;
Reynolds 1988]. This is illustrated by the following example:(succ,,not) 1(succ,,not) true

The succ function has type Int → Int and not function has type Bool → Bool. The merge ofthese two functions has the type Int → Int ∧ Bool → Bool.
(succ,,not) : (Int → Int)&(Bool → Bool)The application of (succ,,not) to 1 yields 2, whereas an application on true yields false. The
semantics chooses the right function for application depending upon the dynamic type of
the argument. This is further discussed in detail in Chapter 5.

2.3 Tagged Union Types
Many systems model tagged union types (also called sum types or variants types), where
explicit tags are used to construct terms with union types, as in languages with algebraic
datatypes [Burstall et al. 1981] or (polymorphic) variants [Garrigue 1998]. In their basic
form, there are two introduction forms: inj1 : A → A ∨ B turns the type of an expression
from A into A ∨ B; and inj2 : B → A ∨ B turns the type of an expressions from B into A ∨ B.
For example, we can have:inj1 "foo": String | Int
inj2
1 : String | Int

Note that in the code above we write union types as String | Int (instead of String ∨ Int),
since this is a common notation in many programming languages, including Ceylon.
Using tagged union types, we can implement a safe integer division function, as1 :String | Int safediv (x : Int) (y : Int) =if (y == 0) then inj1 "Divided by zero" else inj2 (x / y)1// uses tags

Throughout this thesis, we write union types as A | B in code, since this is widely adopted in programming
languages (e.g., Ceylon, Scala, and TypeScript), and as A ∨ B in the formal calculi, which is more frequently
used in the literature.

21

2 Background
Here the intention is to have a safe (integer) division operation that detects division by zeroerrors, and requires clients of this function to handle such errors. The return type String| Int denotes that the function can either return an error message (a string), or an integer,

when division is performed without errors.

Elimination form for tagged union types. Tagged union types are eliminated by someform of case analysis. For consistency with the rest of the thesis, we use a syntactic form withswitch expressions for such case analysis. For example, the following program tostring has

different behaviors depending on the tag of x, where show takes an Int and returns back its
string representation.
String tostring (x: String | Int) = switch (x)inj1 str → str
inj2 num → show num

Combining union type construction in safediv and its elimination in tostring, we can easily
implement an interface which returns the result of safe division as one String.
> tostring (safediv 42 2)"21"> tostring (safe 42 0)"Divided by zero"

2.4 Un-tagged Union Types
Union types are commonly used to deal with heterogeneous data where we do not know
the exact type of data at compile time. However, we know that data can only be among thecertain types. For example, we know statically that argument to a function may either be Int
or Bool, but we do not know if it’s Int or Bool.
As the name suggests, untagged union types do not carry a tag to distinguish between rightand left part of the union. Thus with untagged union types, above example will be written
as:"foo": String | Int
1: String | Int

Notice that we do not carry explicit tags. Specifically, inj1 and inj2 in the example above
have been eliminated.

22

2.5 Type-directed Elimination forms for Union Types

2.5 Type-directed Elimination forms for Union Types
While tags are useful to make it explicit which type a value belongs to, they also add clutter
in the programs. On the other hand, in systems with subtyping for union types [Dunfield
2014; Muehlboeck and Tate 2018; Pierce 1991], explicit tags are replaced by implicit coercions
represented by the two subtyping rules A <: A ∨ B and B <: A ∨ B. In this thesis we refer
to union types where the explicit tags are replaced by implicit coercions as untagged union
types, or simply union types. In those systems, a term of type A or B can be directly used as
if it had type A ∨ B, and thus we can write safe division as:
String | Int safediv2 (x : Int) (y : Int) =if (y == 0) then "Divided by zero" else (x / y)// no tags!

However, now the elimination form of union types cannot rely on explicit tags anymore, anddifferent systems implement elimination forms differently. The most common alternative isto employ types in the elimination form. We review type-directed union elimination next.
Please note that type-directed elimination corresponds to type-directed elimination form forunion types. Generally, when we write tag or type-directed union elimination, we refer toelimination form for tag or type-directed union types.
Type-directed elimination. Some systems [Castagna et al. 2014a] support type-directed
elimination of union types. For instance, tostring2 has different behaviors depending on thetype of x.
String tostring2 (x: String | Int) = switch (x)(y : String) → y(y : Int)→ show y

Note that here show does not need to be overloaded, as the type-directed elimination turns
the variable x of type String | Int into the variable y of type Int.
However, compared to tag-directed elimination, extra care must be taken with type-directed
elimination. In particular, while we can easily distinguish tags, ambiguity may arise whentypes in a union type overlap for type-directed elimination. For example, consider the type
Person | Student, where we assume Student is a subtype of Person. With type-directed elim-

ination, we can write:
Bool isstudent (x: Person | Student) = switch (x)(y : Person) → false(y : Student) → true

Now it is unclear what happens if we apply isstudent to a term of type Student, as its typematches both branches. In some calculi [Dunfield 2014], the choice is not determined in the23

2 Backgroundsemantics, in the sense that either branch can be chosen. This leads to a non-deterministic
semantics. In some other languages or calculi [Castagna et al. 2014a], branches are inspectedfrom top to bottom, and the first one that matches the type gets chosen. However, in thosesystems, as Person is a supertype of Student, the first branch subsumes the second one andwill always get chosen, and so the second branch will never get evaluated! This may be unintentional, and similar programs being accepted can lead to subtle bugs. Even if a warning
is given to alert programmers that a case can never be executed, there are other situations
where two cases overlap, but neither case subsumes the other. For instance we could have
Student and Worker as subtypes of Person. For a person that is both a student and a worker, aswitch statement that discriminates between workers and students could potentially choose
either branch. However for persons that are only students or only workers, only one branch
can be chosen.
Best-match and overloading. Some languages support an alternative to typed-based
union elimination via method overloading. Such form is used in, for example, Java [Gosling
et al. 2021] and Julia [Zappa Nardelli et al. 2018]. In Java, we can encode isstudent2 as anoverloaded method, which has different behaviors when the type of the argument differs.
boolean isstudent2 (Person x) { return false; }boolean isstudent2 (Student x) { return true; }Java resolves overloading by finding and selecting, from all method implementations, the one
with the best type signature that describes the argument. If we apply isstudent2 to a term oftype Student, the second implementation is chosen, as Student is the best type describing theargument. As we can see, such a best-match strategy eliminates the order-sensitive problem,
as it always tries to find the best-match despite the order. That is, in Java the method orderdoes not matter: in this case, we have the method for Person before the one for Student, butJava still finds the one for Student.
However, the best-match strategy can also be confusing, especially when the system features implicit upcasting (e.g., by subtyping). If programmers are not very familiar with howoverloading resolution works, they may assume that the wrong implementation is called intheir code. For instance, in Java we may write:
Person p = new Student();
isstudent2(p);In this case Java will pick the isstudent2 method with the argument Person, since Java overloading uses the static type (p has the static type Person) to resolve overloading. But some
programmers may assume that the implementation of the method for Student would be cho-24

2.6 Union Types and Disjoint Switches in Ceylon
sen instead, since the person is indeed a student in this case. This can be confusing and leadto subtle bugs.
Moreover, there are other tricky situations that arise when employing a best-match strategy. For example, suppose that the type Pegasus is a subtype of both type Bird and type
Horse. If a method isbird is overloaded for Bird and Horse, then which method implemen-tation should we choose when we apply isbird to a term of type Pegasus, the one for Bird, or
the one for Horse? In such case, we have an ambiguity. Things get worse when the type system includes more advanced type system features, such as generics, intersections and union
types, or type-inference.

2.6 Union Types and Disjoint Switches in Ceylon
The Ceylon language [King 2013] supports type-directed union elimination by a switch expression with branches. The following program is an example with union types using Ceylon’s syntax:
void print(String|Integer|Float x) {switch (x)case (is String){ print("String: ``x``"); }case (is Integer|Float) { print("Number: ``x``"); }}For the switch expression, Ceylon enforces static type checking with two guarantees: exhaustiveness, and disjointness. First, Ceylon ensures that all cases in a switch expression are
exhaustive. In the above example, x can either be a string, an integer or a floating point number. The types used in the cases do not have to coincide with the types of x. Nevertheless,the combination of all cases must be able to handle all possibilities. If the last case only dealt
with Integer (instead of Integer|Float), then the program would be statically rejected, sinceno case deals with Float.
Second, Ceylon enforces that all cases in a switch expression are disjoint. That is, unlike
the approaches described in Section 2.5, in Ceylon, it is impossible to have two branches thatmatch with the input at the same time. For instance, if the first case used the type String |Float instead of String, the program would be rejected statically with an error. Indeed, if the

program were to be accepted, then the call print(3.0) would be ambiguous, since there are
two branches that could deal with the floating point number. Note that, since the cases in aswitch cannot overlap, their order is irrelevant to the program’s behavior and its evaluation
result. All of the overlapping examples from the previous section will statically be rejectedin similar fashion.

25

2 Background
Union types as an alternative to overloading. One motivation for such type-directedunion elimination in Ceylon is to model a form of function overloading. The following example, which is adapted from TypeScript’s documentation [Microsoft 2023], demonstrates
how to define an “overloaded” function padLeft, which adds some padding to a string. The
idea is that there can be two versions of padLeft: one where the second argument is a string;and the other where the second argument is an integer:
String space(Integer n){
if (n==0) { return ""; }else{ return " "+space(n-1); }}String padLeft(String v, String|Integer x) {switch (x)case (is String) { return x+v; }case (is Integer) { return space(x)+v; }}print(padLeft("?", 5)); // "?"print(padLeft("World", "Hello ")); // "Hello World"

In padLeft, there are two cases of the switch construct depending on the type of x: the firstone appends a string to the left of v, and the other calls function space to generate a stringwith x spaces, and then append that to v. Although statically x has type String|Integer, as aconcrete value it can only be a string or an integer. As such, when values with such types are
passed to the function, the corresponding branch is chosen and executed.

2.7 Nullable Types
Besides being used for overloading, union types can be used for other purposes too. Nullpointer exceptions (NPEs) are a well-known and tricky problem in many languages. The
problem arises when dereferencing a pointer with the null value. For instance, if we havea variable str, which is assigned to null, the the code print(str.size), in a Java-like language, will raise a null pointer exception. This is because of so-called implicit nulls in Javaand other popular languages. With implicit nulls, any variable of a reference type can benull.

An interesting application of union types in Ceylon is to encode nullable types (or optional
types) [Gunnerson 2012] in a type-safe way. A similar approach to nullable types has also
been recently proposed for Scala [Nieto et al. 2020]. In those languages, there is a special type
Null, which is inhabited by the null value. Note that Null differs from Nothing (the bottom

type in Ceylon), in the sense that Null is inhabited while Nothing is not. To illustrate thesubtle difference, Figure 2.1 presents a part of the subtyping lattice in Ceylon. Anything, the26

2.7 Nullable Types
Anything
ObjectNull

IntegerChar
Nothing

Figure 2.1: Ceylon’s subtyping hierarchy. Note that Null only has Nothing as its subtype.

top type in Ceylon, is an enumerated class. Anything is also a supertype of Object, which is
the root of primitive types, function types, all interfaces and any user-defined class. Notably,
Null is disjoint to Object, and therefore, to all user-defined classes.

In Ceylon the following code:
String str = null;is rejected with a type error, since null cannot have type String. Instead, a type that can havethe null value must be defined explicitly in Ceylon using union types:
String | Null str = null;

Now we cannot call str.size, as str may be null, and size is not defined on null. To get thesize of str, we must first check whether str is null or not using disjoint switches:
String | Null str = null;switch (str)case (is String) { print(str.size); }case (is Null){ print(); }Other uses of Union Types. Union types are also useful in many other situations. In
Section 2.5 we illustrated a safediv operation, which can be easily encoded in Ceylon as:
String | Integer safediv3 (Integer x, Integer y){
if (y==0) { return "Divided by zero"; }else{ return (x/y); }}The return value can be a string or an integer, with no explicit tag needed, as union types are
implicitly introduced. As long as the declared return type of the function is a supertype of
all possible return values, it is valid in Ceylon.

27

2 Background

2.8 Polymorphism
Polymorphism is an essential feature supported in almost all the modern programming languages. The word polymorphism can map to three important concepts in programming languages. This includes subtype polymorphism, ad-hoc polymorphism [Cardelli and Wegner
1985; Castagna et al. 1995; Wadler and Blott 1989] and parametric polymorphism [Canning et al. 1989; Cardelli and Wegner 1985]. In our current setting polymorphism refers toparametric polymorphism. It enables generic programming by abstracting over types notjust terms. Parametric polymorphism lies at the core of most functional programming languages including Haskell and Ocaml among others. The following programming example
illustrates parametric polymorphism:
Int length [T] (l Array[T]) {}Length is a generic function which calculates the size of array of any type. It does not dependon the type of the elements in an array, it only cares about the number of elements. In a
programming language where parametric polymorphism is not supported one may have towrite multiple length functions dealing with one type each. Parametric polymorphism canfurther be refined using bounded quantification to restrict the instantiation of type variables
to be subtype of certain types. For example:
Int length′ [T <: Number] (l Array[T]) {}The function length′ type-checks as long as the type variable T is a subtype of Number.
We discuss a variant of parametric polymorphism called the disjoint polymorphism in Section 3.4 and Chapter 4. Disjoint polymorphism poses a disjointness restriction on type variables instead of subtyping restriction. This is illustrated by the following example:
Bool isstudent [T * Student] (x: T | Student) = switch (x)(y : T)→ false(y : Student) → true

The function isstudent is abstracted over types. Type variable T has a disjointness constraint.
It can be instantiated by any type given that the type is disjoint to Student. Disjoint polymorphism is essential for polymorphic disjoint switches. This will further be discussed in
Section 3.4 and Chapter 4.

2.9 Duality
Duality is a common concept in logic. Many features and their duals have been studied.
Modal logic [Nanevski et al. 2008; Simpson 1994] studies duality between necessity and possibility. Necessity is dual to possibility in a sense that necessity must always hold, whereas282.9 Duality
the possibility may not always hold. Similarly, conjunctions and disjunctions are known tobe dual features in logic. Duality of conjunction and disjunction is shown by De Morgan’s
laws [Copi et al. 2018] as: (where A and B are propositions)A ∧ B ≡ ¬ (¬ A ∨ ¬ B)
andA ∨ B ≡ ¬ (¬ A ∧ ¬ B)
Similarly, universal and existential quantifiers are known to be dual in logic.
∀x. A≡¬∃x. ¬AConversely, the other dual equality is written as:∃x. A≡¬∀x. ¬A
There is also a strong connection between conjunction and universal quantifier and disjunction and the existential quantifier. Universal quantifier can be thought as conjunction of
infinite propositions. Whereas, existential quantifier can be though as disjunction of infinite
propositions. Since propositions are types as per the Curry–Howard correspondence [Curryet al. 1958; Howard 1980]. This points to a study of the duality among types. Intersection
types and union types are the dual features in the theory of programming languages. In particular, we study the duality in the subtyping of intersection types and union types in thisthesis.

29

Part II
Deterministic Union Elimination (λu)

31

3 Union Types with Disjoint Switches
3.1 Overview
In this chapter we first introduce a simplified formulation of union calculus (λu ), which
formalizes the basic ideas of union types with disjoint switches similar to those in the Ceylon language. To our best knowledge, there is yet no formalism of disjoint switches, andλu studies those features formally and precisely. In particular, λu captures the key idea for
type-directed elimination of union types in its switch construct in a language independent
way, and formally defines disjointness, disjointness and subtyping algorithms, and the operational semantics. The simplified formulation of λu is useful to compare with existing
calculi with union types in the literature [Barbanera et al. 1995; Castagna and Frisch 2005;
Dunfield 2014; Dunfield and Pfenning 2003; MacQueen et al. 1984; Pierce 1991]. Moreover,
we study a more fully featured formulation of λu that includes practical extensions, such as
intersection types, distributive subtyping, nullable types and a simple form of nominal types.
λu is proved to enjoy many desirable properties, such as type soundness, determinism andthe soundness/completeness of disjointness and subtyping definitions. All the Ceylon examples in Sections 2.6 and 2.7 can be encoded in λu . Finally, we discuss λu with disjoint
polymorphism and a more general subtyping rule for bottom type.
The typing rules guarantee that cases in the switch are disjoint and exhaustive. Reduction
preserves types and produces deterministic results. We start with an overview of our designand discuss some challenges we met for the calculi designed.
Disjointness. A central concept in the formulation of disjoint switches is disjointness.
Our first hurdle was to come up with a suitable formal definition of disjointness. Consider
the simple λu switch expression:
switch x {(y : String | Int)(y : Int | Bool)
}

→ 0
→ 1

Here we wish to determine whether String ∨ Int and Int ∨ Bool are disjoint or not. In otherwords, we wish to determine whether, for any possible (dynamic) type that x can have, it is33

3 Union Types with Disjoint Switches
unambiguous which branch to choose. In this case, it turns out that there is ambiguity. For
instance, if x is an integer, then either branch can be chosen. Thus λu rejects this programwith a disjointness error. In this example, the reason to reject the program is basically that
Int <: String ∨ Int and Int <: Int ∨ Bool. That is we can find a common subtype (Int) of the types

in both branches. Moreover, that subtype can be inhabited by values (integer values in thiscase). If the only common subtypes of the types in the two branches would be types like ⊥(which has no inhabitants), then the switch should be safe because we would not be able tofind a value for x that would trigger two branches. This observation leads to the notion of
disjointness employed in the first variant of λu in Section 3.2. Formally, we have:
Definition 1 (⊥-Disjointness). A ∗ B ::= ∀ C, if C <: A and C <: B then ⌋C⌊
Here we use ⌋C⌊ to denote that type C is equivalent to type ⊥, or, bottom-like (i.e. C <: ⊥).
In either definition, Int serves as a counter-example for String ∨ Int and Int ∨ Bool to be disjoint. Thus λu rejects the program above with a disjointness error. It is worth noting that thisfirst notion of disjointness is essentially dual to a definition of disjointness for intersection
types in the literature in terms of top-like common supertypes [Oliveira et al. 2016].
Disjointness for Intersections in the Literature. This is very similar to the related
idea of disjointness for intersection types [Oliveira et al. 2016]. In that setting two types are
disjoint if the only common supertypes that two types can have are top-like types (i.e. types
such as ⊤ or ⊤ ∧ ⊤). While a disjoint switch provides deterministic behavior for downcasting, disjointness in intersection types prevents ambiguity in upcasting. In a type-safe setting,if two values v1 and v2 (of type A1 and A2 ) can both be upcasted to type B, then B must be
a common supertype of A1 and A2 . The disjointness restriction of A1 and A2 means theycannot have any meaningful common supertype, so when the two values together get upcast
to a type like Int, only one of them can contribute to the result. Prior work on disjoint intersection types is also helpful to find an algorithmic formulation of disjointness: essentially we
can find an algorithmic formulation that employs dual rules to those for disjoint intersection
types. In essence, disjointness definition for disjoint intersection types [Oliveira et al. 2016]is dual to ours and is stated as:
Definition 2 (Disjointness in disjoint intersection types). A ∗ B ::= ∀ C, if A <: C andB <: C then ⌉C⌈
where ⌉C⌈ represents the top-like types: types that are supertype and subtype of top.
Disjointness in the presence of union and intersection types. The variant of λu
in Section 3.2 does not include intersection types. Unfortunately, the disjointness defini-34

3.1 Overview
tion above does not work in the presence of intersection types. The reason is simple: withintersection types we can always find common subtypes, such as Int ∧ Bool, which are notbottom-like, and yet they have no inhabitants. That is, Int ∧ Bool is not a subtype of ⊥, but novalue can have both type Int and type Bool. In other words, when intersection types are added,
empty types and bottom-like types no longer coincide. We address this issue by reformulating disjointness in terms of ordinary types [Davies and Pfenning 2000], which are basicallyprimitive types (such as integers or functions). Importantly, ordinary types are always inhabited. If we can find common ordinary subtypes between two types, we know that they are
not disjoint. Thus the disjointness definition used for formulations of λu with intersection
types is:Definition 3 (∧-Disjointness). A ∗ B ::= ∄ C ◦ , C ◦ <: A and C ◦ <: B.
Note that here C ◦ is a metavariable denoting ordinary types. Under this definition we cancheck that Int and Bool are disjoint, since no ordinary type is a subtype of both of these two
types. This definition avoids the issue with Definition 1, which would not consider these two
types disjoint. Moreover, this definition is a generalization of the previous one, and in thevariant with union types only the two definitions coincide.
This new definition requires a different approach to algorithmic disjointness. Our new
approach is to use the notion of lowest ordinary subtypes: For any given type, we calculate
a finite set to represent all the possible values that can match the type. Then we can easily
determine whether two types are disjoint by ensuring that the intersection of their lowest
ordinary subtypes is empty.
Typing and exhaustiveness In λu , a switch expression has two branches. For multiple
cases, one can write nested switch expression. We assume the two branches expect A and B.
To make sure they exhaust all possible types of the switched term e, there is a premise that e
can be checked by the type A ∨ B. In other words, the dynamic type of e should be a subtype
of A ∨ B, like Int to Int ∨ Char.
typ-switch

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C

Γ, y : B ⊢ e2 : CA∗BΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
Another premise requires the two cases to be disjoint. Besides, the two branches are typed
under different assumption of the bound variable. Although the same type C is used for bothof them in the rule, it does not prevent them to return different types. Assuming the type of
e1 is C1 and the type of e2 is C2 , we can make C to be C1 ∨ C2 .

35

3 Union Types with Disjoint Switches
Distributive Subtyping. In Section 3.3, we study λu with an enriched distributive subtyping relation inspired by Ceylon programming language. Distributive subtyping is more
expressive than standard subtyping and adds significant complexity in the system, in particular for a formulation of algorithmic subtyping and the completeness proof of the disjointness
algorithm. Nevertheless, distributive subtyping does not affect the disjointness definition
and its algorithm remains the same with and without distributive subtyping. The following
code snippet elaborates on the expressiveness of distributive subtyping:void do (<Integer & String> | Boolean val) { /* do something */ }The function do in above code snippet takes input value of type (Int ∧ String) ∨ Bool. However,
we cannot pass a value of type (Int ∨ Bool) ∧ (String ∨ Bool) to the function do: we get a typeerror if we try to do that in a system with standard subtyping (without distributivity), as standard subtyping fails to identify that the value has a subtype of the expected argument type.
Distributive rules enable this subtyping relation. With distributivity of unions over intersections (and vice-versa), the type (Int ∨ Bool) ∧ (String ∨ Bool) is a subtype of (Int ∧ String) ∨ Bool(in particular, by rule ds-distor in Figure 3.5). As such with distributive subtyping, thefollowing Ceylon program type-checks:
variable <Integer | Boolean> & <String | Boolean> x = true; do(x);Nominal Types and Other Extensions to λu . We also study several extensions to λu ,including nominal types. The extension with nominal types is interesting, since nominal
types are highly relevant in practice. We show a sound, complete and decidable algorithmic formulation of subtyping with nominal types by extending an approach by Huang andOliveira [2021]. We show that disjointness can also be employed in the presence of nominal types. This extension rejects ambiguous programs with overlapping nominal types indifferent branches of switch construct at compile time. It illustrates that disjointness is practically applicable to structural types as well as the nominal types. For example, the following
program will statically be rejected in λu with nominal types:
Bool isstudent (x: Person | Student) = switch (x)(y : Person) → false(y : Student) → true

Whereas, the following program will be accepted if we know that Person and Vehicle are
disjoint:
Bool isvehicle (x: Person | Vehicle) = switch (x)(y : Person) → false(y : Vehicle) → true36

3.2 The Union Calculus λu
Type
Expr
Value
Context

A, B, Cev
Γ

⊤ | ⊥ | Int | A → B | A ∨ B | Nullx | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | nulli | λx.e | null
· | Γ, x : A

::=
::=
::=
::=

A <: B(Subtyping)s-top

s-nulls-int

A <: ⊤

Null <: Null

Int <: Ints-ora
A <: CB <: CA ∨ B <: Cs-arrow
B1 <: A1A2 <: B2
A1 → A2 <: B1 → B2s-orb
A <: BA <: B ∨ C

s-bot⊥ <: As-orc
A <: CA <: B ∨ C

Figure 3.1: Syntax and subtyping for λu .

3.2 The Union Calculus λu
This section introduces the simplified union calculus λu . The distinctive feature of the λu
calculus is a type-based switch expression with disjoint cases, which can be used to eliminate values with union types. In this first formulation of λu we only include the essential
features of a calculus with disjoint switches: union types and disjoint switches. Section 3.3
then presents a richer formulation of λu with several extensions of practical relevance. We
adapt the notion of disjointness from previous work on disjoint intersection types [Oliveira
et al. 2016] to λu , and show that λu is type sound and deterministic.

3.2.1 Syntax
Figure 3.1 shows the syntax for λu . Metavariables A, B and C range over types. Types include top (⊤), bottom (⊥), integer types (Int), function types (A → B), union types (A ∨ B)
and null types (Null). Metavariable e ranges over expressions. Expressions include variables(x), integers (i), lambda abstractions (λx.e), applications (e1 e2 ), a novel switch expression(switch e {(x : A) → e1 , (y : B) → e2 }) and the null expression. The switch expression evaluates a specific branch by matching the types in the cases. Note that, although theswitch expression in λu only has two branches, a multi-branch switch can be easily encodedby employing nested switch expressions. We model the two-branch switch for keeping theformalization simple, but no expressive power is lost compared to a multi-branch switch.
Metavariable v ranges over values. Values include i, λx.e and null expressions. Finally, acontext (Γ) maps variables to their associated types.

37

3 Union Types with Disjoint Switches

3.2.2 Subtyping
The subtyping rules for λu are shown at the bottom of Figure 3.1. The rules are standard.
Rule s-top states that all types are subtypes of the ⊤ type. Rule s-bot states that ⊥ typeis subtype of all types. Rule s-null states that the Null type is a subtype of itself. Rules sint and s-arrow are standard rules for integers and functions respectively. Functions arecontravariant in input types and covariant in output types. Rules s-ora, s-orb, and s-orc
deal with the subtyping for union types. Rule s-ora says that the union type of A and B isa subtype of another type C if both A and B are subtypes of C. Rules s-orb and s-orc state
if a type is subtype of one of the components of a union type, then it is subtype of the union
type. The subtyping relation for λu is reflexive and transitive.
Lemma 3.1 (Subtyping reflexivity). A <: A
Lemma 3.2 (Subtyping transitivity). If A <: B and B <: C then A <: C

3.2.3 Disjointness
The motivation for a definition of disjointness based on bottom-like types is basically thatin disjoint switches, the selection of branches can be viewed as a type-safe downcast. For
instance, recall the example in Section 3.1:
switch x {(y : String | Int)(y : Int | Bool)
}

→ 0
→ 1

Here x may have type Int | String | Bool and the two branches in the disjoint switch cover
two subtypes String | Int and Int | Bool. When considered together those subtypes cover
all possibilities for the value x (i.e. x can be either an integer, a string or a boolean, and thetwo cases cover all those possibilities). The exhaustiveness of the downcasts is what ensuresthat the downcasts are type-safe (that is they cannot fail at runtime). However, we also needto ensure that the two cases do not overlap to prevent ambiguity. In essence, in this simple
setting of λu , checking that two types do not overlap amounts to check that there are no basic
types (like Int or Bool) in common. In other words the only common subtypes should bebottom-like types.
Bottom-Like Types. Bottom-like types are types that are equivalent (i.e. both supertypes
and subtypes) to ⊥. In λu , there are infinitely many such types, and they all are uninhabitedby values. According to the inductive definition shown at the top of Figure 3.2, they include38

3.2 The Union Calculus λu
the bottom type itself (via rule bl-bot) and unions of two bottom-like types (via rule blor), e.g. ⊥ ∨ ⊥. The correctness of our definition for bottom-like types is established by thefollowing property:
Lemma 3.3 (Bottom-Like Soundness and Completeness). ⌋A⌊ if and only if ∀B, A <: B.
Declarative Disjointness. The declarative definition for disjointness is as follows:
Definition 4 (⊥-Disjointness). A ∗ B ::= ∀ C, if C <: A and C <: B then ⌋C⌊
That is, two types are disjoint if all their common subtypes are bottom-like.
We give a few examples next, employing a bold font to highlight the types being comparedfor disjointness: (Note that A and B are placeholders for actual types)
1. A = Int, B = Int → Bool : Int and Int → Bool are disjoint types. All common
subtypes of Int and Int → Bool are bottom-like types, including ⊥ and unions of ⊥ types.
2. A = Int ∨ Bool, B = ⊥ : Int ∨ Bool and ⊥ are disjoint types. All common subtypes
are bottom-like. In general, the type ⊥ (or any bottom-like type) is disjoint to anothertype.
3. A = Int, B = Int :Int and Int are not disjoint types because they share a common subtype Int which is
not bottom-like. In general, one type is not disjoint with itself, unless it is bottom-like.
4. A = Int, B = ⊤ : Int and ⊤ are not disjoint types because they share a common
subtype Int which is not bottom-like. In general no type is disjoint to ⊤, except forbottom-like types. Also, one type is not disjoint with itself, unless it is bottom-like.
5. A = Int → Bool, B = String → Char : The types Int → Bool and String → Charare not disjoint, since we can find non-bottom-like types that are subtypes of bothtypes. For instance ⊤ → ⊥ is a subtype of both types. More generally, any two function
types can never be disjoint: it is always possible to find a common subtype, which is
not bottom-like.
Disjointness for Intersection Types. In essence, disjointness for λu is dual to the disjointness notion in λi [Oliveira et al. 2016], a calculus with disjoint intersection types. In
λu , two types are disjoint if they do not share any common subtype which is not bottom-like.
While in λi , two types are disjoint if they do not share any common supertype which is not
top-like (i.e. equivalent to ⊤). While a disjoint switch provides deterministic behavior for39

3 Union Types with Disjoint Switches
downcasting, disjointness in intersection types prevents ambiguity in upcasting. In a typesafe setting, if two values v1 and v2 (of type A1 and A2 ) can both be upcasted to type B, then B
must be a common supertype of A1 and A2 . The disjointness restriction of A1 and A2 means
they cannot have any non-top-like common supertype, so when the two values together upcasted to a type like Int, only one of them can contribute to the result. Prior work on disjoint
intersection types is also helpful to find an algorithmic formulation of disjointness. Declarative disjointness does not directly lead to an algorithm. However, we can find an algorithmic
formulation that employs dual rules to those for disjoint intersection types.
Algorithmic Disjointness. We present an algorithmic version of disjointness in the middle of Figure 3.2. Rules ad-btmr and ad-btml state that the ⊥ type is disjoint to all types.
Rules ad-intl and ad-intr state that Int and A → B are disjoint types. Algorithmic disjointness can further be scaled to more primitive disjoint types such as Bool and String by adding
more rules similar to rules ad-intl and ad-intr for additional primitive types. Rules adnull-intl and ad-null-intr state that Null and Int are disjoint types. Similarly, rules adnull-funl and ad-null-funr state that Null and A → B are disjoint types. Rules ad-orl
and ad-orr are two symmetric rules for union types. Any type C is disjoint to an union typeA ∨ B if C is disjoint to both A and B. We show that algorithmic disjointness is sound andcomplete with respect to its declarative specification (Definition 4).
Theorem 3.4 (Soundness and Completeness of Algorithmic Disjointness). A∗a B if and only
if A ∗ B.
A natural property of λu is that if type A and type B are two disjoint types, then subtypes of
A are disjoint to subtypes of B. This property dualises the covariance of disjointness property
in calculi with disjoint intersection types [Alpuim et al. 2017].
Lemma 3.5 (Disjointness contravariance). If A ∗ B and C <: A and D <: B then C ∗ D.
Further, disjointness relation is symmetric:
Lemma 3.6 (Disjointness Symmetry). If A ∗ B then B ∗ A.

3.2.4 Typing
The typing rules are shown at the bottom of Figure 3.2. They are mostly standard. An integer
has type Int, null has type Null and variable x gets type from the context. Rule typ-app is thestandard rule for function application. Similarly, rule typ-sub and rule typ-abs are standard
subsumption and abstraction rules respectively. The most interesting and novel rule is for40

3.2 The Union Calculus λu⌋A⌊(Bottom-Like Types)
bl-or
⌋A⌊
⌋B⌊
⌋A ∨ B⌊

bl-bot⌋⊥⌊A ∗a B(Algorithmic Disjointness)

ad-btmrad-btmlad-intlad-intrad-null-intl

A ∗a ⊥⊥ ∗a AInt ∗a A → B

A → B ∗a Int

Null ∗a Int

ad-null-intr

ad-null-funl

ad-null-funr

Int ∗a Null

Null ∗a A → B

A → B ∗a Nullad-orlA ∗a CB ∗a CA ∨ B ∗a C

ad-orrA ∗a BA ∗a CA ∗a B ∨ C
Γ⊢e:A(Typing)

typ-int

typ-nullΓ ⊢ i : IntΓ ⊢ null : Nulltyp-subΓ⊢e:A
A <: B
Γ⊢e:B

typ-appΓ ⊢ e1 : A → BΓ ⊢ e2 : AΓ ⊢ e1 e2 : B

typ-switch
typ-abs
Γ, x : A ⊢ e : BΓ ⊢ λx.e : A → B

typ-varx:A ∈ Γ
Γ⊢x:A

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C
Γ, y : B ⊢ e2 : CA∗BΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C

Figure 3.2: Bottom-like types, algorithmic disjointness and typing for λu .

switch expressions (rule typ-switch). It has four conditions. First, Γ ⊢ e : A ∨ B ensures
exhaustiveness of the cases in the switch: e must check against the types in the branches ofthe switch. The next two conditions ensure that branches of case expressions are well-typed
and have type C, where the input variable is bound to type A and to type B respectively in thetwo branches. Finally, A ∗ B guarantees the disjointness of A and B. This forbids overlappingtypes for the branches of case expressions to avoid non-deterministic results. Since all the
branches have type C, the whole switch expression has type C. Note that the two branches
can have different return types. For example, if e1 and e2 have type Int and String respectively,
the whole expression can have type Int ∨ String.

41

3 Union Types with Disjoint Switchese −→ e′(Operational Semantics)
step-appl
e1 −→ e′1
e1 e2 −→ e′1 e2

step-appre −→ e′v e −→ v e′

step-beta(λx.e) v −→ e[x ⇝ v]

step-switche −→ e′switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }step-switchl⌊v⌋ <: A
switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]

step-switchr⌊v⌋ <: B
switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]

Approximate Type ⌊v⌋⌊i⌋⌊λx.e⌋⌊null⌋

=
=
=

Int
⊤→⊥
Null

Figure 3.3: Operational semantics and approximate type definitions for λu .

3.2.5 Operational Semantics
Now we discuss the small-step operational semantics of λu . An important aspect of thissemantics is that union elimination is type-directed: types are used to pick the branch of theswitch expression.
Figure 3.3 shows the operational semantics of λu . Rules step-appl, step-appr, and stepbeta are the standard call-by-value reduction rules for applications. Of particular interest are
rules step-switch, step-switchl, and step-switchr, which reduce the switch expressions.
First, rule step-switch reduces the case expression e, until it becomes a value v, at which
point we must choose between the two branches of switch. We do so by inspecting the type ofv: if the approximate type of v is a subtype of type of the left branch, then rule step-switchl
evaluates the left branch of the switch expression, or otherwise if it is a subtype of the type ofthe right branch, rule step-switchr evaluates the right branch.
Note that the approximate type definition gives only a subtype of the actual type for alambda value. This works, because the approximate type is only employed to allow the selection of a case with a function type, and in λu two function types can never be disjoint.

42

3.2 The Union Calculus λu
Therefore, if there is a branch with a function type, then that must be the branch that applies to a lambda value. Note also that the program has been type-checked before hand, so
we know that the static type of the value is compatible with the types on the branches. The
subtyping condition in rules step-switchl and step-switchr is important, as it provides
flexibility for the value to have various subtypes of A and B, instead of strictly having those
types. Recall that the typing rule for switch (rule typ-switch) requires that types of left andright branches of a switch expression to be disjoint. This ensures that rules step-switchl
and step-switchr cannot overlap, which, as we will see, is important for the operationalsemantics to be deterministic.

Approximate Type. The dynamic semantics employs a simple function that retrieves the
dynamic type of a value. The definition is shown at the bottom of Figure 3.3. Int and Null
are returned when v is an integer i or a null respectively. Otherwise, for functions, the least
function type ⊤ → ⊥ is returned.

3.2.6 Type Soundness and Determinism
In this section, we prove that λu is type sound and deterministic. Type soundness is established by the type preservation and progress theorems. Type preservation (Theorem 3.7)
states that types are preserved during reduction. Progress (Theorem 3.8) states that well
typed programs never get stuck: a well typed expression e is either a value or it can reduce tosome other expression e′ .
Theorem 3.7 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then Γ ⊢ e′ : A.
Theorem 3.8 (Progress). If · ⊢ e : A then either e is a value; or e −→ e′ for some e′ .
Determinism of λu (Theorem 3.10) ensures that a well-typed program reduces to a uniqueresult. In particular, it guarantees that switch expressions are not order-sensitive: at any time,
only one of the rules step-switchl and step-switchr can apply. The determinism of theswitch expression relies on an essential property that a value cannot check against two disjoint
types (Lemma 3.9).
Lemma 3.9 (Exclusivity of Disjoint Types). If A ∗ B then ∄ v such that both Γ ⊢ v : A andΓ ⊢ v : B holds.
Theorem 3.10 (Determinism). If Γ ⊢ e : A and e −→ e1 and e −→ e2 then e1 = e2 .

43

3 Union Types with Disjoint Switches

3.2.7 An Alternative Specification for Disjointness
The current definition of disjointness (Definition 4) works well for the calculus presented
in this section. But it is not the only possible formulation of disjointness. An equivalent
formulation of disjointness is:Definition 5 (∧-Disjointness). A ∗ B ::= ∄ C ◦ , C ◦ <: A and C ◦ <: B
According to the new definition, two types are disjoint if they do not have common subtypes that are ordinary. Ordinary types (denoted by C ◦ ) are essentially those types that are
primitive, such as integers and functions (see Figure 3.4 for a formal definition).
For the calculus presented in this section, we prove that the new definition is equivalent
to the previous definition of disjointness.
Lemma 3.11 (Disjointness Equivalence). Definition 5 (∧-Disjointness) is sound and complete
to Definition 4 (⊥-Disjointness) in λu defined in this section.
Why do we introduce the new definition of disjointness? It turns out that the previous definition is not sufficient when the calculus is extended with intersection types. As we will see,the new definition will play an important role in such variant of the calculus.

3.3 λu with Intersections, Distributive Subtyping andNominal Types
In this section we extend λu with intersection types, nominal types and an enriched distributive subtyping relation. The study of an extension of λu with intersection types is motivatedby the fact that most languages with union types also support intersection types (for example
Ceylon, Scala or TypeScript). Furthermore, languages like Ceylon or Scala also support someform of distributive subtyping, as well as nominal types. Therefore it is important to understand whether those extensions can be easily added or whether there are some challenges. As
it turns out, adding intersection types does pose a challenge, since the notion of disjointness
inspired from disjoint intersection types [Oliveira et al. 2016] no longer works. Moreover
subtyping relations with distributive subtyping add significant complexity, and we need an
extension that supports nominal types as well. We show that desirable properties, including
type soundness and determinism, are preserved in the extended version of λu . Moreover weprove that both disjointness and subtyping have sound, complete and decidable algorithms.

44

3.3 λu with Intersections, Distributive Subtyping and Nominal Types

A, B, CA◦ , B ◦ , C ◦ev
Γ∆⊤ | ⊥ | Int | A → B | Null | A ∨ B | A ∧ B | P
Int | Null | A → B | P
x | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | null | new P
i | λx.e | null | new P· | Γ, x : A· | ∆, P1 ≤ P2 | ∆, P ≤ ⊤

::=
::=
::=
::=
::=
::=

∆⊢A

(Well-formed Types)

wft-topwft-bot

wft-int

wft-null∆⊢⊤∆⊢⊥∆ ⊢ Int∆ ⊢ Null

wft-primP ∈ dom ∆∆⊢P

wft-or
∆⊢A
∆⊢B
∆⊢A∨B

ok ·

wft-and
∆⊢A
∆⊢B
∆⊢A∧B

(Well-formed Nominal Contexts)

ok ∆okp-empty

wft-arrow
∆⊢A
∆⊢B
∆⊢A→B

okp-cons
ok ∆P∈/ dom ∆ok ∆, P ≤ ⊤okp-subok ∆∆ ⊢ P2
P1 ∈/ dom ∆ok ∆, P1 ≤ P2

Figure 3.4: Syntax and well-formedness.

3.3.1 Syntax, Well-formedness and Ordinary Types
The syntax for this section mostly follows from Section 3.2, with the additional syntax given
in Figure 3.4. The most significant difference and novelty in this section is the addition ofintersection types A ∧ B and an infinite set of nominal types. We use metavariable P tostand for nominal types. Expressions are extended with a new expression (new P) to create
instances of nominal types. The expression new P is also a value. Context Γ stays the sameas in Section 3.2. We add a new context ∆, to track nominal types and their supertypes.
For example, adding P1 ≤ P2 to ∆ declares a new nominal type P1 that is a subtype of P2 .
For a well-formed context, the supertype P2 has to be declared before P1 . We also allow to
declare a new nominal type P1 with ⊤ as its supertype by adding P1 ≤ ⊤ to ∆. MetavariableA◦ , B ◦ and C ◦ ranges over ordinary types [Davies and Pfenning 2000]. There are four kindsof ordinary types: integers, null, function types and nominal types. Well-formed types andwell-formedness of ordinary contexts ∆ are shown in Figure 3.4.

45

3 Union Types with Disjoint Switches
Remark on Nominal Types. Note that our formulation of nominal types is simplified
in two ways compared to languages like Java. Firstly, we do not consider arguments whenbuilding new expressions (i.e. we do not allow expressions like new Person("John")). Secondly, we also do not introduce class declarations, which would allow nominal types to be
associated with method implementations. We follow a design choice for nominal types similar to Featherweight Java [Igarashi et al. 2001]. Featherweight Java uses a fixed size context
for nominal types. Diamond inheritance is also not supported in Featherweight Java, andwe follow that design choice as well. However, we believe that supporting diamond inheritance in our calculus is relatively easy. These simplifications keep the calculus simple, while
capturing the essential features that matter for disjointness and the formalization of disjoint
switches. Allowing for a more complete formulation of nominal types can be done in mostly
standard ways.

3.3.2 Distributive Subtyping
Another interesting feature of this section is the addition of distributive subtyping to λu .
Ceylon employs an enriched distributive subtyping relation [Muehlboeck and Tate 2018] thatis based on the B+ logic [Routley and Meyer 1972; van Bakel et al. 2000]. To obtain an equivalent algorithmic formulation of subtyping, we employ the idea of splittable types [Huang
and Oliveira 2021], but extend that algorithm with the Null type and nominal types.
Distributive subtyping relation. Figure 3.5 shows a declarative version of distributive
subtyping for λu with intersection and nominal types. Subtyping includes axioms for reflexivity (rule ds-refl) and transitivity (rule ds-trans). Rules ds-top, ds-bot, ds-arrow, andds-ora have been discussed in Section 3.2. Rule ds-prim states that a nominal type is a subtype of type A if it is declared as subtype of A in ∆. Note that A can either be a nominal typeor ⊤ under a well-formed context ∆. With the help of rule ds-trans, the subtyping of primitive types can also be constructed indirectly, e.g. P1 ≤ ⊤, P2 ≤ P1 , P3 ≤ P2 ⊢ P3 ≤ P1 .
Compared with the algorithmic formulation, having an explicit transitivity rule considerably simplifies the rules for nominal types. Rules ds-orb and ds-orc state that a subpartof a union type is a subtype of whole union type. Rule ds-anda states that a type A is asubtype of the intersection of two types B and C only if A is a subtype of both B and C.
Rules ds-andb and ds-andc state that intersection type A1 ∧ A2 is a subtype of both A1 andA2 separately. Rule ds-distarr distributes function types over intersection types. It statesthat (A → B1 ) ∧ (A → B2 ) is a subtype of A → (B1 ∧ B2 ). Rule ds-distarru states that
(A1 → B) ∧ (A2 → B) is a subtype of (A1 ∨ A2 ) → B type. Rule ds-distor distributes
intersections over unions.

46

3.3 λu with Intersections, Distributive Subtyping and Nominal Types∆⊢A≤B(Declarative Subtyping with Distributivity)

ds-refl
ok ∆
∆⊢A
∆⊢A≤A
ds-prim
ok ∆
P≤A∈∆
∆⊢P≤A

ds-trans
∆⊢A≤B
∆⊢B≤C
∆⊢A≤C

ds-arrow∆ ⊢ B1 ≤ A1
∆ ⊢ A2 ≤ B2
∆ ⊢ A1 → A2 ≤ B1 → B2

ds-ora∆ ⊢ A1 ≤ B∆ ⊢ A2 ≤ B∆ ⊢ A1 ∨ A2 ≤ B

ds-orbok ∆∆ ⊢ A1∆ ⊢ A2∆ ⊢ A1 ≤ A1 ∨ A2

ds-orcok ∆∆ ⊢ A1∆ ⊢ A2∆ ⊢ A2 ≤ A1 ∨ A2

ds-anda∆ ⊢ B ≤ A1
∆ ⊢ B ≤ A2
∆ ⊢ B ≤ A1 ∧ A2

ds-andbok ∆∆ ⊢ A1∆ ⊢ A2∆ ⊢ A1 ∧ A2 ≤ A1

ds-andcok ∆∆ ⊢ A1∆ ⊢ A2∆ ⊢ A1 ∧ A2 ≤ A2

ds-distarru
ok ∆∆ ⊢ A1∆ ⊢ A2∆⊢B∆ ⊢ (A1 → B) ∧ (A2 → B) ≤ (A1 ∨ A2 ) → B

ds-top
ok ∆
∆⊢A
∆⊢A≤⊤

ds-distorok ∆∆ ⊢ A1∆ ⊢ A2∆⊢B∆ ⊢ (A1 ∨ B) ∧ (A2 ∨ B) ≤ (A1 ∧ A2 ) ∨ B

ds-bot
ok ∆
∆⊢A
∆⊢⊥≤A

ds-distarrok ∆∆⊢A∆ ⊢ B1∆ ⊢ B2∆ ⊢ (A → B1 ) ∧ (A → B2 ) ≤ A → (B1 ∧ B2 )Figure 3.5: Distributive subtyping for λu with intersection types and nominal types.

Algorithmic Subtyping. Distributive rules make it hard to eliminate the transitivity rule.
Our algorithmic formulation of distributive subtyping is based on a formulation using splittable types by Huang and Oliveira [2021]. The basic idea is to view the distributive rules
as some expansion of intersection and union types. For example, rule ds-distarr makes
A → B1 ∧ B2 and (A → B1 ) ∧ (A → B2 ) mutual subtypes. Thus A → B1 ∧ B2 is treatedlike (A → B1 ) ∧ (A → B2 ) in the three intersection-related rules as-anda, as-andb, andas-andc. Here we use A ≊ B ∧ C to denote that type A can be split into B and C (and therefore, A is equivalent to B ∧ C) according to the procedure designed by Huang and Oliveira.
Union and union-like types (e.g. (A1 ∨ A2 ) ∧ B ≊ A1 ∧ B ∨ A2 ∧ B) are handled in similar
way in rules as-ora, as-orb, and as-orc. For further details of algorithmic subtyping werefer to their paper.

47

3 Union Types with Disjoint Switches∆ ⊢ A <: B

(Algorithmic Subtyping with Distributivity)

as-arrow∆ ⊢ B1 <: A1∆ ⊢ A2 <: B2∆ ⊢ A1 → A2 <: B1 → B2
as-reflok ∆∆⊢A∆ ⊢ A <: A
as-oraA ≊ A1 ∨ A2

as-primNeq
ok (∆, P2 <: A)P1 ̸= P2
∆ ⊢ P1 <: P3∆, P2 <: A ⊢ P1 <: P3∆ ⊢ A1 <: B
∆ ⊢ A <: B

as-orbA ≊ A1 ∨ A2
∆ ⊢ B <: A1∆ ⊢ B <: A
as-andaA ≊ A1 ∧ A2

as-primEq
ok (∆, P1 <: P2 )∆ ⊢ P2 <: P3∆, P1 <: P2 ⊢ P1 <: P3∆ ⊢ B <: A1∆ ⊢ B <: A

as-andb
A ≊ A1 ∧ A2
∆ ⊢ A1 <: B
∆ ⊢ A <: B

∆ ⊢ A2 <: B

as-topok ∆∆⊢A∆ ⊢ A <: ⊤as-orcA ≊ A1 ∨ A2
∆ ⊢ B <: A2∆ ⊢ B <: A∆ ⊢ B <: A2

as-botok ∆∆⊢A∆ ⊢ ⊥ <: A

as-andc
A ≊ A1 ∧ A2
∆ ⊢ A2 <: B
∆ ⊢ A <: B

Figure 3.6: Algorithmic subtyping for λu with distributivity, intersection and nominal types.

Subtyping Nominal Types. However, Huang and Oliveira’s algorithm does not accountfor Null and nominal types. We add the nominal context ∆ in the subtyping judgment andextend the subtyping algorithm with Null and nominal types. Nominal types are not splittable, and their subtyping relation is defined by the transitive closure of the context. They
are supertypes of ⊥ and subtypes of ⊤, but not related with other primitive types like Int andNull. So for nominal types, we mainly focus on checking the subtyping relationship among
them in our algorithm. Given a well-formed context, any nominal type P appears only oncein a subtype position as an explicit declaration for P, and its direct supertype, if is not ⊤,must be declared before P. Thus if ∆ ⊢ P1 <: P2 holds, either P2 is introduced before P1 in∆, or they are the same type, in which case the goal can be solved by rule as-refl. For theother cases, we recursively search for P1 in all subtype positions of the context ∆ (rule asprimNeq). When we find P1 , we check its direct supertype. If it is ⊤, no other nominal types
can be supertypes of P1 . So in rule as-primEq, we only consider when the direct supertype isanother primitive P2 . For P3 to be a supertype of P1 , it must either equal to P2 , or it is related48

3.3 λu with Intersections, Distributive Subtyping and Nominal Types
to P2 by the smaller context. In either case, we can prove that P3 is a supertype of the directsupertype of P1 . We show that the algorithmic subtyping relation for λu with intersection
types, nominal types and distributivity rules is reflexive and transitive:
Lemma 3.12 (Subtyping reflexivity). ∆ ⊢ A <: A
Lemma 3.13 (Subtyping transitivity). If ∆ ⊢ A <: B and B <: C then A <: C
Inversion Lemmas for Type Soundness. Having an algorithmic formulation of subtyping is useful to prove several inversion lemmas that are used in the type soundness proof.
For instance, it allows us to prove the following lemma:
Lemma 3.14 (Inversion on Function Types). If ∆ ⊢ A1 → A2 <: B1 → B2 then ∆ ⊢ B1 <:A1 and ∆ ⊢ A2 <: B2 .
While the additional distributive rules make function types more flexible, they retain thecontravariance of argument types and covariance of return types. In addition, we show theformulation is sound and complete to the declarative subtyping and it is decidable whether
a subtyping judgment holds under a given context.
Lemma 3.15 (Equivalence of subtyping). ∆ ⊢ A ≤ B if and only if ∆ ⊢ A <: B.
Lemma 3.16 (Decidability of subtyping). ∆ ⊢ A ≤ B is decidable.

3.3.3 Disjointness Specification
Disjointness is another interesting aspect of the extension of λu . Unfortunately, Definition 4
does not work with intersection types. In what follows, we first explain why Definition 4
does not work, and then we show how to define disjointness in the presence of intersection
types.
Bottom-like types, intersection types and disjointness. Recall that disjointness in
Section 3.2 (Definition 4) depends on bottom-like types, where two types are disjoint only if
all their common subtypes are bottom-like. However, this definition no longer works withthe addition of intersection types. According to the subtyping rule for intersection types,
any two types have their intersection as one common subtype. For non-bottom-like types,
their intersection is also not bottom-like. For example, type Int and type Bool now have a
non-bottom like subtype Int ∧ Bool. In other words, the disjointness definition fails, since we
can always find a common non-bottom-like subtype for any two (non-bottom-like) types.

49

3 Union Types with Disjoint Switches
A possible solution: the Ceylon approach. A possible solution for this issue is to add
a subtyping rule which makes intersections of disjoint types subtypes of ⊥.
s-disjA∗B
A ∧ B <: ⊥This rule is adopted by the Ceylon language [Muehlboeck and Tate 2018]. With the rule sdisj now the type Int ∧ Bool would be a bottom-like type, and the definition of disjointness
used in Section 3.2 could still work. The logic behind this rule is that if we interpret types as
sets of values, and intersection as set intersection, then intersecting disjoint sets is the empty
set. In other words, we would get a type that has no inhabitants. For instance the set of
all integers is disjoint to the set of all booleans, and the intersection of those sets is empty.
However we do not adopt the Ceylon solution here for two reasons:
1. Rule s-disj complicates the system because it adds a mutual dependency between subtyping and disjointness: disjointness is defined in terms of subtyping, and subtyping
now uses disjointness as well in rule s-disj. This creates important challenges for themetatheory. In particular, the completeness proof for disjointness becomes quite challenging.
2. Additionally, the assumption that intersections of disjoint types are equivalent to ⊥is too strong for some calculi with intersection types. If a merge operator [Reynolds
1988] is allowed in the calculus, intersection types can be inhabited with values (forexample, in λi Oliveira et al. [2016], the type Int ∧ Bool is inhabited by 1, , true). Considering those types bottom-like would lead to a problematic definition of subtyping,since some bottom-like types (those based on disjoint types) would be inhabited.
For those reasons we adopt a different approach in λu . Nevertheless, in Section 3.4 we showthat it is possible to create an extension of λu that includes (and in fact generalizes) the
Ceylon-style rule s-disj.
Disjointness based on ordinary types to the rescue. To solve the problem with the
disjointness specification, we resort to the alternative definition of disjointness presented in
Section 3.2.7. Note that now the disjointness definition also contains ∆ as an argument toaccount for nominal types.
Definition 6 (∧-Disjointness). ∆ ⊢ A ∗ B ::= ∄ C ◦ , ∆ ⊢ C ◦ <: A and ∆ ⊢ C ◦ <: B.

50

3.3 λu with Intersections, Distributive Subtyping and Nominal Types
Interestingly, while in Section 3.2 such definition was equivalent to the definition using
bottom-like types, this is no longer the case for λu with intersection types. To see why, consider again the types Int and Bool. Int and Bool do not share any common ordinary subtype.
Therefore, Int and Bool are disjoint types according to Definition 6. We further illustrate Definition 6 with a few concrete examples:
1. A = Int, B = Int → Bool : Since there is no ordinary type that is a subtype ofboth Int and Int → Bool, the two types are disjoint.
2. A = Int ∨ Bool, B = ⊥ : Since there is no ordinary type that is a subtype of bothInt ∨ Bool and ⊥, Int ∨ Bool and ⊥ are disjoint types. In general, the ⊥ type is disjoint

to all types because ⊥ does not have any ordinary subtype.
3. A = Int ∧ (Int → Bool), B = Int : There is no ordinary type that is a subtype ofboth Int ∧ (Int → Bool) and Int. Therefore, Int ∧ (Int → Bool) and Int are disjoint
types. In general, an intersection of two disjoint types (Int ∧ (Int → Bool) in this case)is always disjoint to all types.
4. A = Int ∧ Bool, B = Int ∨ Bool : There is no ordinary type that is a subtype ofboth Int ∧ Bool and Int ∨ Bool. Therefore, Int ∧ Bool and Int ∨ Bool are disjoint types. In
general, an intersection of two disjoint types (Int ∧ Bool in this case) is always disjoint
to all types.
5. A = Int, B = ⊤ : In this case, Int and ⊤ share a common ordinary subtype which
is Int. Therefore, Int and ⊤ are not disjoint types. ⊤ overlaps with any other types.

3.3.4 Algorithmic Disjointness
The change in the disjointness specification has a significant impact on an algorithmic formulation. In particular, it is not obvious at all how to adapt the algorithmic formulationin Figure 3.2. To obtain a sound, complete and decidable formulation of disjointness, weemploy the novel notion of lowest ordinary subtypes.
Lowest ordinary subtypes (|A|∆ ). Figure 3.7 shows the definition of lowest ordinary
subtypes (LOS) (|A|∆ ). LOS is defined as a function which returns a set of ordinary subtypesof the given input type. No ordinary type is a subtype of ⊥. The only ordinary subtype of Int
is Int itself. The function case is interesting. Since no two functions are disjoint in the calculus
proposed in this section, the case for function types A → B returns ⊤ → ⊥. This type isthe least ordinary function type, which is a subtype of all function types. Lowest ordinary51

3 Union Types with Disjoint Switches
Lowest Ordinary Subtypes (LOS) |A|∆
|⊤|∆
|⊥|∆
|Int|∆|A → B|∆|A ∨ B|∆|A ∧ B|∆|Null|∆
|P|∆

=
=
=
=
=
=
=
=

{Int, ⊤ → ⊥, Null} ∪ dom ∆{}{Int}{⊤ → ⊥}|A|∆ ∪ |B|∆
|A|∆ ∩ |B|∆
{Null}{P} ∪ ∆(P)

Nominal Subtypes ∆(A)·(A)(∆′ , P ≤ B)(A)

=



=


{}
{P} ∪ ∆′ (A) if P ≤ A ∈ ∆∆′ (A) otherwise

Figure 3.7: Lowest ordinary subtypes function and additional typing rule for λu with intersection
types and nominal types.

subtypes of ⊤ are Int, ⊤ → ⊥, Null and all the nominal types defined in ∆. In the case ofunion types A ∨ B, the algorithm collects the LOS of A and B and returns the union of thetwo sets. For intersection types A ∧ B the algorithm collects the LOS of A and B and returns
the intersection of the two sets. The lowest ordinary subtype of Null is Null itself. Finally, the
LOS of P is the union of P itself with all subtypes of P defined in ∆. Note that LOS is defined
as a structurally recursive function and therefore its decidability is immediate.
Algorithmic disjointness. With LOS, an algorithmic formulation of disjointness is straightforward:
Definition 7. ∆ ⊢ A ∗a B ::= |A|∆ ∩ |B|∆ = {}.
The algorithmic formulation of disjointness in Definition 7 states that two types A and B are
disjoint under the context ∆ if they do not have any common lowest ordinary subtypes. In
other words, the set intersection of |A|∆ and |B|∆ is the empty set. Note that this algorithm
is naturally very close to Definition 6.
Soundness and completeness of algorithmic disjointness. Next, we show that disjointness algorithm is sound and complete with respect to disjointness specifications (The-52

3.3 λu with Intersections, Distributive Subtyping and Nominal Types
orem 3.17). Soundness and completeness of LOS are essential to prove Theorem 3.17. Bothof these properties are shown in Lemma 3.18 and Lemma 3.19 respectively.
Theorem 3.17 (Disjointness Equivalence). ∆ ⊢ A ∗a B if and only if ∆ ⊢ A ∗ B.
Lemma 3.18 (Soundness of |A|∆ ). ∀ well-formed ∆ and A and B that are well-formed under∆, if B ∈ |A|∆ , then ∆ ⊢ B <: A.
Lemma 3.19 (Completeness of |A|∆ ). ∀ A B ◦ , if ∆ ⊢ B ◦ <: A, then B ◦ ∈ |A|∆ , or B ◦ isan arrow type and ⊤ → ⊥ ∈ |A|∆ .

3.3.5 Typing, Semantics and Metatheory
Both typing and the operational semantics are parameterized by the nominal context ∆. The
typing rules are extended with a rule for nominal types rule ptyp-prim as shown:ptyp-prim

ok ∆

∆⊢P

∆; Γ ⊢ new P : P
The typing rule ptyp-prim states that under a well-formed context ∆ and well-formed
type P, new P has type P. No additional reduction rule is required because new P is a value.
However, the rules step-switchl and step-switchr require ∆ because they do a subtyping
check. We illustrate the updated rule step-switchl next:
nstep-switchl∆ ⊢ ⌊v⌋ <: A
∆ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]
Rule step-switchr is updated similarly as:
nstep-switchr∆ ⊢ ⌊v⌋ <: B
∆ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]
All the other rules are essentially the same as in Section 3.2, modulo the extra nominal context ∆.

53

3 Union Types with Disjoint Switches
Example. Assuming a context ∆ = Person ≤ ⊤, Student ≤ Person, Robot ≤ ⊤, y :Person | Robot and x : Student, we could write the following two switches:
switch(y)// Accepted!
(z : Person) → false(z : Robot)→ trueswitch (x)// Rejected!
(z : Person) → false(z : Student) → true

In the above code, the first switch, using y is accepted, while the second one (using x) is
rejected because the types overlap in that case.
Key Properties. We proved that λu with intersection types, nominal types and subtyping
distributivity preserves type soundness and determinism.
Theorem 3.20 (Type Preservation). If ∆; Γ ⊢ e : A and ∆ ⊢ e −→ e′ then ∆; Γ ⊢ e′ : A.
Theorem 3.21 (Progress). If ∆; · ⊢ e : A then either e is a value; or e can take a step to e′ .
Theorem 3.22 (Determinism). If ∆; Γ ⊢ e : A and ∆ ⊢ e −→ e1 and ∆ ⊢ e −→ e2 thene1 = e2 .

3.4 Switches with Disjoint Polymorphism and Empty Types
The calculus introduced in Section 3.2 is a simple foundational lambda calculus with union
types, similar to prior work on union types and their elimination forms [Benzaken et al. 2003;
Castagna et al. 2014a; Dunfield 2014]. In Section 3.3 we extend λu with various interesting
features including intersection types, nominal types and subtyping distributivity, inspired by
Ceylon, which has similar features. In this section we discuss two more practical extensions:• Disjoint Polymorphism: The first extension is an extension with a form of disjoint
polymorphism [Alpuim et al. 2017], which allows the specification of disjointness constraints for type variables. Although Ceylon supports polymorphism, it does not support disjoint polymorphism. The extension with disjoint polymorphism is inspired by
the work on disjoint intersection types, where disjoint polymorphism has been proposed to account for disjointness in a polymorphic language.
• A Special Subtyping Rule for Empty Types: The second extension that we discuss
is an alternative subtyping formulation with a special subtyping rule for empty types,
which follows the Ceylon approach.
Note that both extensions above have also been formalized in Coq and proved type-soundand deterministic. In addition, we also have a brief discussion about implementation considerations.

54

3.4 Switches with Disjoint Polymorphism and Empty Types

A, B, Ce::=::=

⊤ | ⊥ | Int | A → B | Null | A ∨ B | A ∧ B | P | α | ∀(α ∗ A).B
x | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | null | new P |e A | Λ(α ∗ A).ev::=

i | λx.e | null | new P | Λ(α ∗ A).e

Γ
∆
G

::=
::=
::=

· | Γ, x : A | Γ, α ∗ A· | ∆, P <: A
⊤ | ⊥ | Int | Null | A → B | G1 ∨ G2 | G1 ∧ G2 | ∀(α ∗ G).B
Lowest Ordinary Subtypes (LOS) |A|∆;Γ|⊤|∆;Γ
= {Int, ⊤ → ⊥, Null} ∪ dom ∆|⊥|∆;Γ
= {}|Int|∆;Γ= {Int}|A → B|∆;Γ= {⊤ → ⊥}|A ∨ B|∆;Γ= |A|∆;Γ ∪ |B|∆;Γ|A ∧ B|∆;Γ= |A|∆;Γ ∩ |B|∆;Γ|Null|∆;Γ= {Null}|P|∆;Γ
= {P} ∪ ∆(P)|∀(α ∗ G).B|∆;Γ = {∀(α ∗ ⊥).⊥}|α|∆;Γ
= (|⊤|∆;Γ ) - (|G|∆;Γ )where α ∗ G ∈ Γ

Figure 3.8: Syntax, additional typing, subtyping, and reduction rules for λu with polymorphism.

3.4.1 Disjoint Polymorphism
In this section we discuss an extension of λu with parametric polymorphism along with intersection and nominal types. The interesting aspect about this extension is the presenceof disjointness constraints. For example, in λu with polymorphism a polymorphic disjoint
switch such as: Γ, α ∗ Int ⊢ switch e {(x : Int) → true, (y : α) → false} is accepted. It
is safe to use Int and α in alternative branches in a switch in this example. The disjointness
constraint in the context (Γ, α ∗ Int) on type variable α ensures that α must only be instantiated with types disjoint to Int. Thus an instantiation of α with Null or A → B is allowed.
Whereas, an instantiation of α with Int is rejected by the type system.

Syntax. Figure 3.8 shows the extension in the syntax of λu with polymorphism. Types areextended with type variables α and disjoint quantifiers ∀(α ∗ G).B. The reader can think ofthis extension in the context of bounded quantification [Canning et al. 1989; Cardelli andWegner 1985] where bounded quantifiers (∀(α <: A).B) are replaced by disjoint quantifiers(∀(α ∗ G).B). Bounded quantification imposes a subtyping restriction on type variables,55

3 Union Types with Disjoint Switches
whereas disjoint quantification imposes disjointness restriction on type variables. Disjoint
quantification only allows the instantiation of disjoint types. For example, ∀(α <: Int ∨Bool).α allows α to be instantiated only with subtypes of Int ∨ Bool and restricts all othertypes. Whereas, ∀(α ∗ Int ∨ Bool).α restricts all the instantiations of α which share an
ordinary subtype with Int ∨ Bool. In other words, the permitted instantiations of α are thetypes disjoint to Int ∨ Bool. Null is a valid instantiation in this case, while Int is not a valid
instantiation.
Expressions are extended with type application e A and type abstraction Λ(α∗G).e. A typeabstraction is also a value. Additionally, context Γ now also contains type variables with theirrespective disjointness constraints. The disjointness constraint of type variables is restricted
to ground types (G), which includes all the types except type variables. Ground types areshown at the top left of Figure 3.8.
Subtyping. Figure 3.9 shows subtyping relation in the formalization of polymorphic λu .
Note that subtyping, typing, and reduction relations now have two contexts ∆ and Γ. Subtyping is extended for the two newly added types. The subtyping rule for type variables isa special case of reflexivity (rule polys-tvar)). Rule polys-all is interesting. It says that
input and output types of two disjoint quantifiers are covariant in the subtype relation. This
contrasts with calculi with bounded quantification and disjoint polymorphism [Alpuim et al.
2017], where the subtyping between the type bounds of the constraints is contravariant, andthe subtyping between the types in the universal quantification body is covariant. Note thatin the calculus that we formalized in Coq, we study parametric polymorphism without distributive subtyping rules. Subtyping for polymorphic λu is reflexive and transitive:
Lemma 3.23 (Subtyping reflexivity). ∆; Γ ⊢ A <: A
Lemma 3.24 (Subtyping transitivity). If ∆; Γ ⊢ A <: B and ∆; Γ ⊢ B <: C then ∆; Γ ⊢A <: C

Typing and Operational Semantics. Typing is extended to assign the type to two newly
added expressions and is shown in Figure 3.10. Rule ptyp-tap is for type applications andrule ptyp-tabs is for type abstractions. Similarly, Figure 3.11 shows reduction rules for polymorphic λu . Rule polystep-tappl is standard reduction rule for type application. Rule polysteptapp replaces α with type B in expression e.
Disjointness. Disjointness has to be updated to accommodate type variables and disjoint
quantifiers. The definition of algorithmic disjointness is roughly the same as discussed in56

3.4 Switches with Disjoint Polymorphism and Empty Types∆; Γ ⊢ A <: B

(Subtyping)polys-toppolys-int

polys-botpolys-unit∆; Γ ⊢ A <: ⊤∆; Γ ⊢ Int <: Int∆; Γ ⊢ ⊥ <: A∆; Γ ⊢ Null <: Nullpolys-arrow∆; Γ ⊢ B1 <: A1∆; Γ ⊢ A2 <: B2∆; Γ ⊢ A1 → A2 <: B1 → B2
polys-orb∆; Γ ⊢ A <: B∆; Γ ⊢ A <: B ∨ C
polys-andb∆; Γ ⊢ A <: C∆; Γ ⊢ A ∧ B <: Cpolys-ora∆; Γ ⊢ A <: C∆; Γ ⊢ B <: C∆; Γ ⊢ A ∨ B <: Cpolys-orc∆; Γ ⊢ A <: C∆; Γ ⊢ A <: B ∨ C

polys-anda∆; Γ ⊢ A <: B∆; Γ ⊢ A <: C∆; Γ ⊢ A <: B ∧ C

polys-andc∆; Γ ⊢ B <: C∆; Γ ⊢ A ∧ B <: Cpolys-tvar
ok ∆∆; Γ ⊢ α∆; Γ ⊢ α <: αpolys-all∆; Γ ⊢ G1 <: G2∆; Γ, α ∗ G2 ⊢ B1 <: B2∆; Γ ⊢ ∀(α ∗ G1 ).B1 <: ∀(α ∗ G2 ).B2

polys-preflok ∆∆; Γ ⊢ P∆; Γ ⊢ P <: P

polys-pinok ∆∆; Γ ⊢ P1
P2 ∈ ∆P1∆; Γ ⊢ P2 <: P1

Figure 3.9: Subtyping for polymorphic λu .

Section 3.3, except that it takes an additional argument Γ. Context Γ is also an argument of
LOS. LOS is extended to handle the additional cases of α and ∀(α ∗ G).B and is shown at the
top right of Figure 3.8. LOS returns ∀(α ∗ ⊥).⊥ as the least ordinary subtype of ∀(α ∗ G).B.
The type variable case is interesting. It returns the set difference of all ordinary subtypes and
LOS of the disjointness constraint of type variable. Note that the disjointness constraint oftype variables is restricted to ground types.
Definition 8 (Disjointness). ∆; Γ ⊢ A ∗ B ::= |A|∆;Γ ∩ |B|∆;Γ = {}.
Type-safety and Determinism. The extension with disjoint polymorphism retains theproperties of type-soundness and determinism. All the metatheory is formalized in Coq
theorem prover.
Theorem 3.25 (Type Preservation). If ∆; Γ ⊢ e : A and ∆ ⊢ e −→ e′ then ∆; Γ ⊢ e′ : A.
Theorem 3.26 (Progress). If ∆; · ⊢ e : A then either e is a value; or e can take a step to e′ .

57

3 Union Types with Disjoint Switches∆; Γ ⊢ e : A(Typing)ptyp-nullok ∆∆; Γ ⊢ null : Nullptyp-intok ∆∆; Γ ⊢ i : Int
ptyp-app∆⊢B

ptyp-var
ok ∆

ok ∆
∆⊢A
∆; Γ ⊢ e1 : A → B∆; Γ ⊢ e2 : A∆; Γ ⊢ e1 e2 : B∆⊢A
x:A ∈ Γ∆; Γ ⊢ x : A
ptyp-sub∆; Γ ⊢ e : A
A <: B∆; Γ ⊢ e : B

ptyp-abs
ok ∆
∆⊢A
∆⊢B
∆; Γ, x : A ⊢ e : B∆; Γ ⊢ λx.e : A → B

ptyp-and∆; Γ ⊢ e : A∆; Γ ⊢ e : B∆; Γ ⊢ e : A ∧ B

ptyp-switch∆; Γ ⊢ e : A ∨ B∆; Γ, x : A ⊢ e1 : C∆; Γ, y : B ⊢ e2 : C∆ ⊢ A ∗s B∆; Γ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
ptyp-tap∆; Γ ⊢ e : ∀(α ∗ G).C∆; Γ ⊢ G1 ∗ G∆; Γ ⊢ e G1 : C[ α ⇝ G1 ]ptyp-primok ∆∆⊢P∆; Γ ⊢ new P : P

ptyp-tabs∆; Γ, α ∗ G ⊢ e : B∆; Γ ⊢ Λ(α ∗ G).e : ∀(α ∗ G).B

Figure 3.10: Typing for polymorphic λu .

Theorem 3.27 (Determinism). If ∆; Γ ⊢ e : A and ∆ ⊢ e −→ e1 and ∆ ⊢ e −→ e2 thene1 = e2 .
Progress and determinism does not require significant changes for this extension. Typepreservation requires the preservation of disjointness after substitution and disjointness narrowing along with disjointness weakening. Disjointness substitution states that if two types
are disjoint before type substitution, they must be disjoint after type substitution as statedin Lemma 3.28. The disjointness narrowing relates disjointness and subtyping. It states that
it is safe to change the bounds of type variables from subtypes to supertypes as stated in
Lemma 3.29.
Lemma 3.28 (Disjointness Substitution). If ∆; Γ, α ∗ G1 ⊢ B ∗ C and ∆; Γ ⊢ G2 ∗ G1 then∆; Γ[ α ⇝ G2 ] ⊢ B[ α ⇝ G2 ] ∗ C[ α ⇝ G2 ]Lemma 3.29 (Disjointness Narrowing). If ∆; Γ, α ∗ G1 ⊢ B ∗ C and ∆; Γ ⊢ G1 <: G2 then∆; Γ, α ∗ G2 ⊢ B ∗ C

58

3.4 Switches with Disjoint Polymorphism and Empty Types∆; Γ ⊢ e −→ e′(Operational Semantics)

polystep-appl∆; Γ ⊢ e1 −→ e′1∆; Γ ⊢ e1 e2 −→ e′1 e2polystep-tappl∆; Γ ⊢ e −→ e′∆; Γ ⊢ e B −→ e′ Bpolystep-appr∆; Γ ⊢ e −→ e′∆; Γ ⊢ v e −→ v e′polystep-beta∆; Γ ⊢ (λx.e) v −→ e[x ⇝ v]

polystep-tapp∆; Γ ⊢ (Λ(α ∗ G).e) B −→ e[ α ⇝ B]

polystep-switch∆; Γ ⊢ e −→ e′∆; Γ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }polystep-switchl
value v∆; Γ ⊢ ⌊v⌋ <: A∆; Γ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]
polystep-switchr
value v∆; Γ ⊢ ⌊v⌋ <: B∆; Γ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]

Figure 3.11: Operational Semantics for polymorphic λu .

3.4.2 A More General Subtyping Rule for Bottom Types
As discussed in Section 3.3.3, Ceylon includes the following subtyping rule:s-disjA∗B
A ∧ B <: ⊥It is possible to support, and in fact generalize, such a rule in λu . The idea is to employ ourdefinition of lowest ordinary subtypes, and add the following rule to λu with intersection
types:s-los|A| = {}
A <: B
Rule s-los is an interesting addition in subtyping of λu . It says that if the LOS returns theempty set for some type A, then A is a subtype of all types. In other words, such type behaves
like a bottom-like type. Such rule generalizes the rule s-disj employed in Ceylon, since when
A is an intersection type of two disjoint types, we get the empty set. Moreover, adding rule slos makes rule s-bot redundant as well, since the LOS for the bottom type is also the empty59

3 Union Types with Disjoint Switches
set. It is trivial to prove a lemma which says that ⊥ is a subtype of all types. We drop rule sbot from the calculus discussed in Section 3.3 and prove Lemma 3.30 to show this property
instead:Lemma 3.30 (Bottom Type Least Subtype). ⊥ <: A.
A similar lemma can be proved to show that disjoint types are bottom-like (as in rule s-disj),
when rule s-los is added to subtyping:
Lemma 3.31 (Disjont Intersections are Bottom-Like). If A ∗ B then A ∧ B <: ⊥.
The use of rule s-los instead of rule s-disj also has the advantage that it does not create
a mutual dependency between disjointness and subtyping. We can have the definition of
disjointness, which depends only on subtyping and ordinary types, and the definition ofsubtyping, which depends on LOS but not on disjointness. Nevertheless, like rule s-disj,
rule s-los would not be an appropriate rule in calculi with a merge operator for the reasons
discussed in Section 3.3.
We have formalized and proved all the metatheory, including type soundness, transitivityof subtyping, soundness and completeness of disjointness and determinism for a variant of
λu with intersection types, nominal types, standard subtyping and rule s-los in Coq.

3.4.3 Implementation of Disjoint Switches
Ceylon code runs on the Java Virtual Machine (JVM). A Ceylon program compiles to JVM
bytecode. The final bytecode to which a Ceylon program is compiled to erase annotations
for types not supported in the JVM. In particular, union types such as String ∨ Null are erased
into Object. Disjoint switches are implemented by type casts. For each branch there is an
instanceof to test the type of the branch and select a particular branch. An implementationof the λu calculus could also use a similar approach for compilation. In essence the useof union types and disjoint switches provides an elegant alternative to type-unsafe idioms,
based on instanceof tests, that are currently widely used by Java programmers, while keeping
comparable runtime performance.

60

4 Revisiting Disjointness
Polymorphic λu discussed in Section 3.4.1 has a ground type restriction on type variablebounds. Ground types constitute of all the types except type variables. This means that a
type variable cannot be declared as a bound to another type variable. While this is a common approach in many polymorphic calculi [Dolan and Mycroft 2017], this approach limits
the expressiveness of calculus. For example, in our setting, two type variables cannot be declared disjoint in the presence of ground type restriction. This restrains us from writing thefollowing program:
Bool isFirstMatch [X * Y] (x : X | Y) = switch (x)(x:X) → true(y:Y) → false

Since the bound of type variable X is another type variable Y, therefore, this program will
not type-check in the presence of ground type restriction on type variable bound. Any type
except the type variable can be a bound of a type variable. In contrast, the following program
will type-check:
Bool isInteger [X * Int] (x : X | Int) = switch (x)(x:Int)(y:X)→ true→ falseNotice that the bound of type variable X in the program above is a base type i.e. Int. While
this approach with ground type restriction is useful in many scenarios, it restrains us fromwriting some valid programs. In this chapter we study a variant of λu with disjoint polymorphism without a ground type restriction on type variable bounds. This makes the current
calculus more expressive than the one discussed in Section 3.4 and isFirstMatch type-checks
in this calculus.
We develop a novel disjointness algorithm for intersection and union types by exploiting
union ordinary and union splittable types [Huang and Oliveira 2021]. We study two variantsof λu with the newly developed disjointness, one without polymorphism and another withpolymorphism. The first calculus establishes a connection with the calculi without polymorphism. We show that the disjointness in Section 3.3 is sound and complete with respect tothe disjointness in Section 4.1. The second calculus revisits disjoint polymorphism in Sec-61

4 Revisiting Disjointness

A, B, C
e
v
Γ

::=
::=
::=
::=

⊤ | ⊥ | Int | A → B | A ∨ B | A ∧ B | Nullx | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | nulli | λx.e | null
· | Γ, x : A

A <: B(Subtyping)

s-top

s-int

s-bot

s-nullA <: ⊤

Int <: Int⊥ <: A

Null <: Nulls-arrow
B1 <: A1A2 <: B2
A1 → A2 <: B1 → B2s-orb
A <: BA <: B ∨ Cs-orc
A <: CA <: B ∨ C

s-andaA <: BA <: CA <: B ∧ Cs-ora
A <: CB <: CA ∨ B <: Cs-andb
A <: CA ∧ B <: Cs-andcB <: CA ∧ B <: C

Figure 4.1: Syntax and subtyping for λu with intersection types.

tion 4.2 and proposes a revised disjointness algorithm without ground types. Appendix A
presents another variant of the disjointness algorithm based on Common Ordinary Subtypes(COST).

4.1 Disjointness with Intersection and Union Types
Recall that our first disjointness algorithm introduced in Section 3.2 did not work when we
add intersection types. In Section 3.3 we come up with another disjointness algorithm basedon Least Ordinary Subtypes (LOS) that accounts for intersection and union types. LOS is afunction that computes a set of least ordinary subtypes of the input type. The disjointness
algorithm discussed in Section 3.3 states that two types are disjoint if set intersection of LOSof two types is an empty set.
In this section we discuss another variant of the disjointness algorithm which is soundand complete with respect to the standard disjointness specifications discussed earlier in thisthesis. We also explain the reason where naive disjointness algorithm fails with intersection
and union types in detail in this section.
Syntax and subtyping. Syntax and subtyping for λu with intersection types is shown in
Figure 4.1. Types, expressions, values and typing context (Γ) stay the same as in Section 3.3.

62

4.1 Disjointness with Intersection and Union TypesA⊚(Union Ordinary Types)
uo-topuo-intuo-arrow

uo-unit⊤⊚Int⊚(A → B)⊚

Null⊚

B◁A▷C

uo-andA⊚
B⊚(A ∧ B)⊚(Union Splittable Types)

usp-orA◁A∨B▷B

usp-orandl
A1 ◁ A ▷ A2
A1 ∧ B ◁ A ∧ B ▷ A2 ∧ B

usp-orandr
B1 ◁ B ▷ B2
A ∧ B1 ◁ A ∧ B ▷ A ∧ B2

Figure 4.2: Syntax, union ordinary, and union splittable types.

We use conventional subtyping and drop distributive subtyping rules for function, intersection, and union types to emphasize the pivotal concept of disjointness for λu .

4.1.1 Disjointness, Union Ordinary, and Union Splittable Types
Union ordinary and union splittable types. Union ordinary and union splittable types
play an essential role in the formulation of the novel disjointness algorithm. These types areshown in Figure 4.2. ⊤, Int, A → B and Null are union ordinary types as shown by rules uotop, uo-int, uo-arrow, and uo-unit respectively. An intersection type is union ordinary
only if both of its parts are union ordinary types as shown in rule uo-and. For example,
Int ∧ ⊤ is a union ordinary type. Whereas, (Int ∨ A → B) ∧ ⊤ is not union ordinary because
left part of the intersection is not union ordinary i.e Int ∨ A → B.
Union types are never union ordinary types. On the contrary, union types are union splittable types by rule usp-or. Intersection types are union splittable if either of the componentof the intersection is union splittable by rules usp-andl and usp-andr. We illustrate union
splittable types with the help of following examples:• Int ∨ Bool : Int ∨ Bool is trivially union splittable into Int and Bool by rule usp-or.

Int ◁ Int ∨ Bool ▷ Bool

usp-or• (Int ∨ Bool) ∧ String : (Int ∨ Bool) ∧ String is splittable into Int ∧ String and Bool ∧String by rule usp-orandl and rule usp-or.

63

4 Revisiting Disjointness
A ∗a B(Disjointness)

ad-btmlad-btmrad-intlad-intrad-null-intl⊥ ∗a AA ∗a ⊥

Int ∗a A → B

A → B ∗a Int

Null ∗a Int

ad-null-intr

ad-null-funl

ad-null-funr

Int ∗a Null

Null ∗a A → B

A → B ∗a Nullad-orllA1 ◁ A ▷ A2
ad-andll
A1 ∗a BB⊚
(A1 ∧ A2 ) ∗a B

A1 ∗a BA ∗a Bad-orrr
B1 ◁ B ▷ B2

A2 ∗ a B

ad-andlss
A2 ∗a BB⊚
(A1 ∧ A2 ) ∗a B

A ∗a B1
A ∗a Bad-andrrA ∗a B1A⊚
A ∗a (B1 ∧ B2 )ad-emptyl
A ∗a B(A ∧ B) ∗a C

A ∗a B2

ad-andrss
A ∗a B2A⊚
A ∗a (B1 ∧ B2 )ad-emptyr
B ∗a CA ∗a (B ∧ C)

Figure 4.3: Disjointness based on splittable types for λu .

Int ◁ Int ∨ Bool ▷ Bool

usp-orInt ∧ String ◁ (Int ∨ Bool) ∧ String ▷ Bool ∧ String

usp-orandl• String ∧ (Int ∨ Bool) : String∧(Int∨Bool) is splittable into String∧Int and String∧
Bool by rule usp-orandr and rule usp-or.

Int ◁ Int ∨ Bool ▷ Bool

usp-orString ∧ Int ◁ String ∧ (Int ∨ Bool) ▷ String ∧ Bool

usp-orandr

Note that a type is either union ordinary or union splittable (Lemma 4.1):
Lemma 4.1 (Exclusivity of union ordinary and union splittable types). ∀ A, A is either union
ordinary or union splittable and never both.
Algorithmic Disjointness. The algorithmic disjointness based on union ordinary andunion splittable types is shown in Figure 4.3. Rules ad-btml, ad-btmr, ad-intl, ad-intr,

644.1 Disjointness with Intersection and Union Typesad-null-intl, ad-null-intr, ad-null-funl, and ad-null-funr are trivial disjointness
axioms. The novelty of the disjointness algorithm lies in the disjointness rules for intersection
and union types.
Rule ad-orll states that if A is union splittable into A1 and A2 then A is disjoint to B only
if A1 and A2 are disjoint to B. Rule ad-orrr is symmetric to rule ad-orll. Rules ad-andll
and ad-andlss state that an intersection type A1 ∧ A2 is disjoint to another type B when
B is union ordinary and either A1 or A2 is disjoint to B. Rules ad-andrr and ad-andrss
are symmetric to rules ad-andll and ad-andlss. Rules ad-emptyl and ad-emptyr areinteresting rules. They state that an intersection of two disjoint types is disjoint with anyother type. The intersection of two disjoint types forms an empty type or a bottom-like type,which is disjoint with any other type. The following example illustrates our novel disjointness
algorithm:• (Int ∨ Bool) ∗a String : Int ∨ Bool is disjoint to String by rule ad-orll.

usp-orad-orllInt ◁ Int ∨ Bool ▷ BoolInt ∗a String

Bool ∗a String(Int ∨ Bool) ∗a String

4.1.2 Essence of Union Ordinary Types
Union ordinary restriction. Note that the union ordinary premise in rules ad-andll,
ad-andlss, ad-andrr, and ad-andrss is optional. This premise only makes the rules lessoverlapping. It allows the application of rules ad-andll, ad-andlss, ad-andrr, and adandrss only if one type is an intersection type and the other type is a union ordinary type.
When the other type is not union ordinary type, the disjointness algorithm falls to the union
rules. The algorithm then splits the other type until it becomes union ordinary and thenapplies either of the rules ad-andll, ad-andlss, ad-andrr, and ad-andrss.
Next we explain the naive disjointness algorithm without union ordinary and union splittable types and then emphasize the significance of union ordinary and union splittable types.
Disjointness without union ordinary and union splittable types. The naive disjointness algorithm without union ordinary and union splittable types is shown in Figure 4.4.
The rules ad-btml, ad-btmr, ad-intl, ad-intr, ad-null-intl, ad-null-intr, ad-nullfunl, ad-null-funr, ad-emptyl, and ad-emptyr are straightforward and already explained.
The rules for the intersection types (ad-andla, ad-andlb, ad-andra, and ad-andrb) state65

4 Revisiting Disjointness
A ∗a B(Disjointness without union ordinary and union splittable types)

ad-btmlad-btmrad-intlad-intrad-null-intl⊥ ∗a AA ∗a ⊥

Int ∗a A → B

A → B ∗a Int

Null ∗a Int

ad-null-intr

ad-null-funl

ad-null-funr

Int ∗a Null

Null ∗a A → B

A → B ∗a Nullad-orrA ∗a BA ∗a CA ∗a B ∨ C

ad-andlaA ∗a CA ∧ B ∗a C

ad-andraA ∗a BA ∗a B ∧ C

ad-andlb
B ∗a CA ∧ B ∗a C

ad-emptyl
A ∗a B(A ∧ B) ∗a C

ad-orlA ∗a CB ∗a CA ∨ B ∗a C
ad-andrb
A ∗a CA ∗a B ∧ C

ad-emptyr
B ∗a CA ∗a (B ∧ C)

Figure 4.4: Disjointness without union ordinary and union splittable types for λu .

that an intersection type A ∧ B is disjoint to another type C if either A or B is disjoint to C.
Importantly, the rules for the intersection types no longer carry a premise with union ordinary restriction as in Figure 4.3. Similarly, rules for the union types (ad-orl and ad-orr)
do not depend on union splittable types. The rules ad-orl and ad-orr simply state that aunion type A ∨ B is disjoint to another type C if A disjoint to C and B disjoint to C holds.

Significance of union ordinary types. Recall that the union ordinary types play anessential role to make the rules for the intersection types in Figure 4.3 more algorithmic. We
illustrate this with the help of following example. Note that we apply the disjointness rules
from Figure 4.4 in the following derivation.

ad-orlInt ∗a String

Bool ∗a String(Int ∨ Bool) ∗a String

Int ∗a Int???

Bool ∗a Int(Int ∨ Bool) ∗a Int(Int ∨ Bool) ∗a (String ∨ Int)
(Int ∨ Bool) ∧ (Bool ∨ String) ∗a (String ∨ Int)ad-orlad-orrad-andla(Int ∨ Bool) ∧ (Bool ∨ String) and (String ∨ Int) are disjoint types and the algorithm in
Figure 4.4 may backtrack to classify them as disjoint types without union ordinary restriction.
In the derivation above we apply intersection rules first and then union rules. The derivation

664.1 Disjointness with Intersection and Union Types
proceeds by applying the rule ad-andla which generates a sub-problem of (Int ∨ Bool) ∗a(String ∨ Int). It then applies rule ad-orr which further generates two sub-problems (Int ∨Bool) ∗a String and (Int ∨ Bool) ∗a Int.
1. Applying rule ad-orl, (Int ∨ Bool) ∗a String can be solved since Int ∗a String and
Bool ∗a String holds.
2. (Int ∨ Bool) ∗a Int fails to derive. The only option that we have left at this stage is to
apply rule ad-orl which requires Int ∗a Int and Bool ∗a Int. The highlighted partin the derivation fails to hold i.e. Int ∗a Int does not hold. Therefore the current
derivation fails.
This example also fails to derive if we start by applying rule ad-andlb which finally will
result in String ∗a String . The algorithm will keep on backtracking with the application ofintersection rules at the very start. A solution to the successful derivation in current example is to apply union rules first and then intersection rules. We show a successful derivation
below and then explain how union ordinary restriction reduces backtracking and makes disjointness rules less overlapping. Note that we use initials as place-holders instead of full type
names for the sake of space. In particular, I stands for Int, B for Bool, C for Char and S for
String.

ad-orlad-andlaI ∗a SB ∗a SB ∗a IS ∗a I

ad-orl(I ∨ B) ∗a S

(B ∨ S) ∗a I

(I ∨ B) ∧ (B ∨ S) ∗a S

(I ∨ B) ∧ (B ∨ S) ∗a I

(I ∨ B) ∧ (B ∨ S) ∗a (S ∨ I)

ad-andlb
ad-orr

Notice that we start by applying rule ad-orr in the derivation above. The key at this point
is to apply union rules first and then intersection rules. This reduces the backtracking of
the disjointness algorithm. Therefore, we enforce an optional union ordinary restriction in
the intersection rules (ad-andll, ad-andlss, ad-andrr, and ad-andrss) in Figure 4.3 incontrast to the naive disjointness rules in Figure 4.4. The union ordinary premise restricts
the application of the intersection rules unless the union rules have been applied.

4.1.3 Essence of Union Splittable Types
Another issue with the naive disjointness algorithm shown in Figure 4.4 is that it is incomplete. We show the incompleteness with the following example:

67

4 Revisiting Disjointness

I∗C
ad-orrI∗S

I∗I

I ∗ (C ∨ I)

???

S∗S
ad-orr···I ∗ (S ∨ C ∨ I)???

(B ∨ S) ∗ (S ∨ C ∨ I)

(I ∨ B ∨ S) ∗ (S ∨ C ∨ I)

ad-orl(I ∨ B ∨ S) ∗ (S ∨ C ∨ I) ∧ (C ∨ I ∨ B)
(I ∨ B ∨ S) ∧ (B ∨ S ∨ C) ∗ (S ∨ C ∨ I) ∧ (C ∨ I ∨ B)

ad-andraad-andla(Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) and (String ∨ Char ∨ Int) ∧ (Char ∨ Int ∨ Bool)are clearly disjoint types but the algorithm in Figure 4.4 fails to classify them as disjoint types
without union splittable types. It does not matter whether we break the left intersection orright intersection first, we cannot make these two types disjoint. Importantly the two types
as a whole are disjoint. But if we drop any component from either of the intersection, thesmaller types are no longer disjoint as shown in the derivation above.
Rule ad-andla drops a part of intersection from the left type resulting in a sub-problem(Int ∨ Bool ∨ String) ∗ (String ∨ Char ∨ Int) ∧ (Char ∨ Int ∨ Bool) which does not holdbecause Int is a common ordinary subtype of (Int ∨ Bool ∨ String) and (String ∨ Char ∨Int) ∧ (Char ∨ Int ∨ Bool). The derivation finally results in the highlighted sub-problems
i.e Int ∗a Int and String ∗a String . Int is not disjoint to Int and so is not String to String.
Therefore the naive disjointness algorithm shown in Figure 4.4 fails and is not complete.
Union splittable types to the rescue. Union splittable types come to the rescue insuch cases and solve the incompleteness problem of the disjointness algorithm. Note that
union ordinary types are optional because union ordinary types just make the rules less overlapping. The disjointness algorithm stays sound and complete without union ordinary types.
But union splittable types are essential. The disjointness algorithm will not be complete without union splittable types. The algorithm in Figure 4.3 is sound and complete, whereas thealgorithm in Figure 4.4 is sound but incomplete.
Illustration of Completeness with union splittable types. We show how the union
splittable types solve the incompleteness problem of the disjointness algorithm in Figure 4.3
using the same example i.e (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ (String ∨ Char ∨Int) ∧ (Char ∨ Int ∨ Bool). Since both of the types:
1. A1 = (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char)68

4.1 Disjointness with Intersection and Union Types
2. A2 = (String ∨ Char ∨ Int) ∧ (Char ∨ Int ∨ Bool)are not union ordinary. Therefore we cannot apply intersection rules (ad-andll, ad-andlss,
ad-andrr, and ad-andrss) from Figure 4.3 at the very start of the derivation. We can apply
either of the rule ad-orll or rule ad-orrr since both of the types are union splittable.

··
··
··
S ∧ (C ∨ I ∨ B) ◁ (S ∨ C ∨ I) ∧ (C ∨ I ∨ B) ▷ (C ∨ I) ∧ (C ∨ I ∨ B)
(I ∨ B ∨ S) ∧ (B ∨ S ∨ C) ∗ (S ∨ C ∨ I) ∧ (C ∨ I ∨ B)

usp-orandl
ad-orrr

Splitting goes as follows:

usp-or······S ◁ (S ∨ C ∨ I) ▷ C ∨ I

··
··
··

S ∧ (C ∨ I ∨ B) ◁ (S ∨ C ∨ I) ∧ (C ∨ I ∨ B) ▷ (C ∨ I) ∧ (C ∨ I ∨ B)

usp-orandl

We split the right side in the disjointness derivation above. The choice of the right or the left
type does not affect the outcome of the disjointness algorithm. The union splitting algorithm
will keep on splitting the right type unless all the smaller types become union ordinary types.
When the splitting algorithm concludes splitting of (String∨Char∨Int)∧(Char∨Int∨Bool),the final result will be a list consisting of the following union ordinary types:• {String ∧ Char, String ∧ Int, String ∧ Bool, Char ∧ Char, Char ∧ Int, Char ∧ Bool, Int ∧Char, Int ∧ Int, Int ∧ Bool}.
The whole disjointness checking problem breaks into the following sub-problems:
1. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ String ∧ Char
2. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ String ∧ Int
3. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ String ∧ Bool
4. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Char ∧ Char69

4 Revisiting Disjointness
5. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Char ∧ Int
6. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Char ∧ Bool
7. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Int ∧ Char
8. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Int ∧ Int
9. (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Int ∧ Bool
Notice that 7 out of 9 sub-problems are trivially solvable by rule emptyr except sub-problems
4 and 8. This is because the right type in sub-problems 1, 2, 3, 5, 6, 7 and 9 is an intersectionof two disjoint types. For example, the derivation for sub-problem 1 is:String ∗ Char(Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ String ∧ Charad-emptyr

Next, we show the derivation trees to solve sub-problem 4 and 8.
Derivation for (4) (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Char ∧ Char :Bool ∗ Char

String ∗ Char

Bool ◁ Bool ∨ String ▷ String
Int ∗ Char

(Bool ∨ String) ∗ CharInt ◁ (Int ∨ Bool ∨ String) ▷ Bool ∨ String(Int ∨ Bool ∨ String) ∗ Char(Int ∨ Bool ∨ String) ∗ Char ∧ Charad-orllad-orllad-andrr(Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Char ∧ Charad-andll

The derivation for the sub-problem 4 initiates by applying the rule ad-andll which selects
the left part of the intersection type and reduces the problem to (Int∨Bool∨String)∗Char∧
Char. Rule ad-andrr further reduces the problem to (Int ∨ Bool ∨ String) ∗ Char. The
rule ad-orll finally generates the base cases for the derivation. All of the base cases triviallyhold which concludes successful derivation for (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨Char) ∗ Char ∧ Char.

70

4.1 Disjointness with Intersection and Union Types
Derivation for (8) (Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Int ∧ Int :The deriva-

tion for sub-problem 8 follows a similar path as of sub-problem 4. Complete derivation for
sub-problem 8 is shown below:

String ∗ Int

Char ∗ Int

String ◁ String ∨ Char ▷ Char
Bool ∗ Int(String ∨ Char) ∗ Int

ad-orlBool ◁ (Bool ∨ String ∨ Char) ▷ String ∨ Char
(Bool ∨ String ∨ Char) ∗ Int(Bool ∨ String ∨ Char) ∗ Int ∧ Int

ad-orlad-andrr(Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ Int ∧ Int

ad-andlss

Since the newly formulated disjointness algorithm solved all of the sub-problems, therefore(Int ∨ Bool ∨ String) ∧ (Bool ∨ String ∨ Char) ∗ (String ∨ Char ∨ Int) ∧ (Char ∨ Int ∨ Bool)holds. The successful derivation for this example shows that the novel disjointness algorithmwith union ordinary and union splittable types shown in Figure 4.3 is able to compute thedisjointness of the types on which naive algorithm shown in Figure 4.4 fails.
Soundness and completeness of disjointness. We prove that the novel disjointness
algorithm is sound and complete with respect to the disjointness specifications. The disjointness specifications are shown again in Definition 9 for readability.
A◦ , B ◦ , C ◦::=Int | Null | A → B

Definition 9 (∧-Disjointness). A ∗ B ::= ∄ C ◦ , C ◦ <: A and C ◦ <: B.
Lemma 4.2 (Soundness of disjointness algorithm). ∀ A B, A ∗a B → A ∗ B.
Lemma 4.3 (Completeness of disjointness algorithm). ∀ A B, A ∗ B → A ∗a B.

4.1.4 Metatheory with Union Ordinary and Union Splittable Types
Typing and operational semantics. Subtyping, typing and operational semantics essentially stay the same and are shown in Figure 4.5. This calculus preserves the standard
properties of subtyping, type-safety and determinism as shown below:
Lemma 4.4 (Subtyping Reflexivity). A <: A71

4 Revisiting Disjointness
Γ⊢e:A(Typing)

typ-int

typ-nullΓ ⊢ i : IntΓ ⊢ null : Nulltyp-subΓ⊢e:A
A <: B
Γ⊢e:B

typ-varx:A ∈ ΓΓ⊢x:A
typ-abs
Γ, x : A ⊢ e : BΓ ⊢ λx.e : A → B

typ-appΓ ⊢ e1 : A → BΓ ⊢ e2 : AΓ ⊢ e1 e2 : B
typ-andΓ⊢e:A
Γ⊢e:B
Γ⊢e:A∧B

typ-switch

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C
Γ, y : B ⊢ e2 : CA∗BΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C

e −→ e′(Operational Semantics)
step-appl
e1 −→ e′1
e1 e2 −→ e′1 e2

step-appre −→ e′v e −→ v e′

step-beta(λx.e) v −→ e[x ⇝ v]

step-switche −→ e′switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }step-switchl⌊v⌋ <: A
switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]

step-switchr⌊v⌋ <: B
switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]

Figure 4.5: Typing and operational semantics for λu .

Lemma 4.5 (Subtyping Transitivity). If A <: B and B <: C then A <: C
Theorem 4.6 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then Γ ⊢ e′ : A.
Theorem 4.7 (Progress). If Γ ⊢ e : A then either e is a value; or e can take a step to e′ .
Theorem 4.8 (Determinism). If Γ ⊢ e : A and e −→ e1 and e −→ e2 then e1 = e2 .

72

4.2 Redesigning Disjoint Polymorphism

A, B, C
e

::=
::=

v
Γ
∆

::=
::=
::=

⊤ | ⊥ | Int | A → B | Null | A ∨ B | A ∧ B | P | α | ∀(α ∗ A).B
x | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | null |new P | e A | Λ(α ∗ A).ei | λx.e | null | new P | Λ(α ∗ A).e
· | Γ, x : A | Γ, α ∗ A· | ∆, P <: AA⊚

(Union Ordinary Types)

uo-topuo-intuo-arrow

uo-unit⊤⊚Int⊚(A → B)⊚Null⊚

uo-andA⊚
B⊚(A ∧ B)⊚uo-alluo-nom∀(α ∗ A).B⊚

P⊚

B◁A▷C
usp-orA◁A∨B▷B

uo-tvarα⊚(Union Splittable Types)
usp-orandl
A1 ◁ A ▷ A2
A1 ∧ B ◁ A ∧ B ▷ A2 ∧ B

usp-orandr
B1 ◁ B ▷ B2
A ∧ B1 ◁ A ∧ B ▷ A ∧ B2

Figure 4.6: Syntax, union ordinary, and union splittable types for polymorphic λu .

4.2 Redesigning Disjoint Polymorphism
In Section 4.1 we discuss a novel disjointness algorithm by exploiting union ordinary andunion splittable types. We show that the disjointness algorithm is sound and complete withrespect to the disjointness specifications. In this section we extend the calculus from Section 4.1 with disjoint polymorphism. Importantly, we show that the ground type restriction
on type variable bounds is no longer needed with the novel disjointness algorithm.

Syntax, union ordinary, and union splittable types. The syntax for polymorphic λu
is shown at the top in Figure 4.6. Types are extended with the nominal types P, type variables
α, and disjoint quantifiers ∀(α ∗ A).B. The syntactic category of expressions now include a
new expression (new P) to construct instances of type P. It also includes type applicationse A and type abstractions Λ(α ∗ A).e. Expressions new P and Λ(α ∗ A).e are also values.
Typing context Γ also has entries for type variables Γ, α ∗ A. A new context ∆ keeps a list
and bounds of nominal types.

73

4 Revisiting Disjointness
Union ordinary types are shown in the middle of Figure 4.6. Union ordinary types areextended with type variables (rule uo-tvar), disjoint quantifiers (rule uo-all) and nominal
types (rule uo-nom). Union splittable types stay the same as in Figure 4.2 and are shown at
the bottom of Figure 4.6.

4.2.1 Disjointness
The disjointness algorithm with polymorphism is shown in Figure 4.7. In addition to theprior axioms, we also add axioms for universal types and the nominal types. Universal types
are disjoint to all the base types and so are the nominal types. Type variables are disjoint to
all the subtypes of its bound as shown in rules adp-varr and adp-varl. For example in acontext [Γ, α ∗ ⊤], α is essentially disjoint to all the types because all the types are subtypeof ⊤. In another context [Γ, α ∗ Int ∨ Bool], α is disjoint with all the subtypes of Int ∨ Bool
including Int and Bool but is not disjoint to String.
Note that we scrap the optional union ordinary premise from rules adp-andl, adp-andls,
adp-andr, and adp-andrs. This simplifies the metatheory with the type variables. We
also drop rules adp-emptyl and adp-emptyr from disjointness algorithm in Figure 4.7.
Dropping rules adp-emptyl and adp-emptyr restricts writing some programs but all thepractical programs still type-check. Generally, it does not allow writing empty intersection
types in branches. For example, the following program will no longer type-check because of
the empty type in the first branch i.e Int ∧ Bool.
Bool isInt (x : Int | Bool) = switch (x)(x:Int&Bool) → true
(y:Int)→ true(z:Bool)→ falseSince we cannot construct a value of type Int∧Bool in contemporary system, the first branchin the above program has no practical significance. Therefore not allowing such empty intersections does not affect the programs in practice.

Disjointness for nominal types. The disjointness rule for nominal types (rule adpnom) is interesting. It states that two nominal types P1 and P2 are disjoint if the intersectionof their subtypes is an empty set. Nominal subtypes (∆(A)) is a function that finds the subtypes of type A in ∆ and returns a list. Note that nominal subtypes is a transitive closure.
Nominal subtypes function is shown next:744.2 Redesigning Disjoint Polymorphism∆; Γ ⊢ A ∗ax B(Disjointness Axioms)

adpa-botadpa-intarr

adpa-intnull∆; Γ ⊢ ⊥ ∗ax A∆; Γ ⊢ Int ∗ax A → B∆; Γ ⊢ Int ∗ax Nulladpa-intall

adpa-nullarr

adpa-nullall∆; Γ ⊢ Int ∗ax ∀(α ∗ A).B∆; Γ ⊢ Null ∗ax A → B∆; Γ ⊢ Null ∗ax ∀(α ∗ A).B

adpa-arrall

adpa-pint

adpa-parr∆; Γ ⊢ C → D ∗ax ∀(α ∗ A).B∆; Γ ⊢ P ∗ax Int∆; Γ ⊢ P ∗ax A → B

adpa-pnull

adpa-pall∆; Γ ⊢ P ∗ax Null∆; Γ ⊢ P ∗ax ∀(α ∗ A).B

adpa-sym∆; Γ ⊢ A ∗ax B∆; Γ ⊢ B ∗ax A∆; Γ ⊢ A ∗ B(Disjointness)

adp-varrα ∗A ∈ Γ∆; Γ ⊢ B <: A∆; Γ ⊢ B ∗ αadp-orl
A1 ◁ A ▷ A2

adp-varlα ∗A ∈ Γ∆; Γ ⊢ B <: A∆; Γ ⊢ α ∗ B

adp-orr
B1 ◁ B ▷ B2∆; Γ ⊢ A2 ∗ B∆; Γ ⊢ A ∗ B1∆; Γ ⊢ A ∗ B∆; Γ ⊢ A ∗ B2

adp-andls∆; Γ ⊢ A2 ∗ B∆; Γ ⊢ (A1 ∧ A2 ) ∗ B

adp-andl∆; Γ ⊢ A1 ∗ B∆; Γ ⊢ (A1 ∧ A2 ) ∗ B
adp-andrs∆; Γ ⊢ A ∗ B2∆; Γ ⊢ A ∗ (B1 ∧ B2 )∆; Γ ⊢ A1 ∗ B∆; Γ ⊢ A ∗ B

adp-nom
P1 :: ∆(P1 ) ∩ P2 :: ∆(P2 ) = {}∆; Γ ⊢ P1 ∗ P2

adp-andr∆; Γ ⊢ A ∗ B1∆; Γ ⊢ A ∗ (B1 ∧ B2 )adp-axiom∆; Γ ⊢ A ∗ax B∆; Γ ⊢ A ∗ B

Figure 4.7: Disjointness with union splittable types for polymorphic λu .

Nominal Subtypes ∆(A)·(A)(∆′ , P ≤ B)(A)

=




=



{}
{P} ∪ ∆′ (A) if P ≤ A ∈ ∆∆′ (A) otherwise

For example, in a context ∆ = {Person <: ⊤, Student <: Person, GradStudent <: Student,
Robot <: ⊤, OptimumPrime <: Robot}:75

4 Revisiting Disjointness• Person is disjoint to Robot as per rule adp-nom, because the set intersection of thesubtypes of Person and Robot is empty i.e {Person, Student, GradStudent} ∩ {Robot,
OptimumPrime} = {}.
• Whereas, Person is not disjoint to GradStudent, because the set intersection of thesubtypes of Person and GradStudent is not empty i.e {Person, Student, GradStudent}∩ {GradStudent} = {GradStudent}.
Contravariance of disjointness. Contravariance of disjointness (Lemma 4.9) statesthat if two types A and B are disjoint, then the subtypes of A are also disjoint with B. In
general subtypes of disjoint types are disjoint as well. For example if A → B is disjoint toInt ∨ Null, then A → B is disjoint to both Int and Null among other subtypes of Int ∨ Bool.
Similarly if a type A is disjoint to ⊤, then A is disjoint with all the types because all the types
are subtypes of ⊤.
Lemma 4.9 (Contravariance of disjointness). If ∆; Γ ⊢ A ∗ B and ∆; Γ ⊢ C <: A then∆; Γ ⊢ C ∗ B.
Expressiveness of disjointness. The novel disjointness algorithm allows writing the programs that are not allowed in the polymorphic λu discussed in Section 3.4.1 due to the groundtype restriction. We can write the programs by declaring type variables as bounds of othertype variables:
Bool isFirstMatch [X * Y] (x : X | Y) = switch (x)(x:X) → true(y:Y) → false

4.2.2 Subtyping, typing, and operational semantics
Subtyping, typing and operational semantics are altered to lift the ground type restriction ontype variable bounds and are shown in Figures 4.8 to 4.10 respectively. These relations havealready been explained in Section 3.4. We highlight the major changes next.
Modifications in metatheory. The subtyping changes are reflected by rule polys-alldisj
in Figure 4.8. Note that first premise does not have ground type restriction. A1 and A2 canbe any types. Typing changes are shown in rules ptyp-tapdisj and ptyp-tabsdisj in Figure 4.9. Similarly, changes for operational semantics are shown in rule polystep-tappdisj
in Figure 4.10. Importantly, we no longer use syntactic category of ground types and thebound of a type variable can be any other type.

76

4.2 Redesigning Disjoint Polymorphism∆; Γ ⊢ A <: B

(Subtyping)polys-toppolys-int

polys-botpolys-unit∆; Γ ⊢ A <: ⊤∆; Γ ⊢ Int <: Int∆; Γ ⊢ ⊥ <: A∆; Γ ⊢ Null <: Nullpolys-arrow∆; Γ ⊢ B1 <: A1∆; Γ ⊢ A2 <: B2∆; Γ ⊢ A1 → A2 <: B1 → B2
polys-orb∆; Γ ⊢ A <: B∆; Γ ⊢ A <: B ∨ C
polys-andb∆; Γ ⊢ A <: C∆; Γ ⊢ A ∧ B <: Cpolys-ora∆; Γ ⊢ A <: C∆; Γ ⊢ B <: C∆; Γ ⊢ A ∨ B <: Cpolys-orc∆; Γ ⊢ A <: C∆; Γ ⊢ A <: B ∨ C

polys-anda∆; Γ ⊢ A <: B∆; Γ ⊢ A <: C∆; Γ ⊢ A <: B ∧ C

polys-andc∆; Γ ⊢ B <: C∆; Γ ⊢ A ∧ B <: Cpolys-tvar
ok ∆∆; Γ ⊢ α∆; Γ ⊢ α <: αpolys-alldisj∆; Γ ⊢ A1 <: A2∆; Γ, α ∗ A2 ⊢ B1 <: B2∆; Γ ⊢ ∀(α ∗ A1 ).B1 <: ∀(α ∗ A2 ).B2

polys-preflok ∆∆; Γ ⊢ P∆; Γ ⊢ P <: P

polys-pinok ∆∆; Γ ⊢ P1
P2 ∈ ∆P1∆; Γ ⊢ P2 <: P1

Figure 4.8: Subtyping for λu .

Type safety and determinism. Polymorphic λu with updated disjointness preserves standard properties of subtyping, type-safety and determinism.
Lemma 4.10 (Subtyping Reflexivity). ∆; Γ ⊢ A <: A
Lemma 4.11 (Subtyping Transitivity). If ∆; Γ ⊢ A <: B and ∆; Γ ⊢ B <: C then ∆; Γ ⊢A <: C
Theorem 4.12 (Type Preservation). If ∆; Γ ⊢ e : A and ∆; Γ ⊢ e −→ e′ then ∆; Γ ⊢ e′ : A.
Theorem 4.13 (Progress). If ∆; · ⊢ e : A then either e is a value; or e can take a step to e′ .
Theorem 4.14 (Determinism). If ∆; Γ ⊢ e : A and ∆; Γ ⊢ e −→ e1 and ∆; Γ ⊢ e −→ e2then e1 = e2 .
Substitution lemmas. Type preservation depends on type substitution (Lemma 4.15)
which in turn depends on subtyping substitution (Lemma 4.16) and disjointness substitution(Lemma 4.17). Lemma 4.15 states that if in a context where α is disjoint to A1 , an expression77

4 Revisiting Disjointness∆; Γ ⊢ e : A
ptyp-intok ∆∆; Γ ⊢ i : Int
ptyp-app∆⊢B(Typing)ptyp-nullok ∆∆; Γ ⊢ null : Nullptyp-var
ok ∆

ok ∆
∆⊢A
∆; Γ ⊢ e1 : A → B∆; Γ ⊢ e2 : A∆; Γ ⊢ e1 e2 : B∆⊢A
x:A ∈ Γ∆; Γ ⊢ x : A
ptyp-sub∆; Γ ⊢ e : A
A <: B∆; Γ ⊢ e : B

ptyp-abs
ok ∆
∆⊢A
∆⊢B
∆; Γ, x : A ⊢ e : B∆; Γ ⊢ λx.e : A → B

ptyp-and∆; Γ ⊢ e : A∆; Γ ⊢ e : B∆; Γ ⊢ e : A ∧ B

ptyp-switch∆; Γ ⊢ e : A ∨ B∆; Γ, x : A ⊢ e1 : C∆; Γ, y : B ⊢ e2 : C∆ ⊢ A ∗s B∆; Γ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
ptyp-tapdisj∆; Γ ⊢ e : ∀(α ∗ A).C∆; Γ ⊢ B ∗ A∆; Γ ⊢ e B : C[ α ⇝ B]

ptyp-primok ∆∆⊢P∆; Γ ⊢ new P : P

ptyp-tabsdisj∆; Γ, α ∗ A ⊢ e : B∆; Γ ⊢ Λ(α ∗ A).e : ∀(α ∗ A).B

Figure 4.9: Operational Semantics for λu .

e has type B and A2 is disjoint to A1 , then e has type B after substituting α with A2 in thecontext Γ, expression e and type B.
Lemma 4.16 states that if in a context where α is disjoint to A1 , type B is subtype of type C
and A2 is disjoint to A1 , then B stays subtype of C after substituting α with A2 in context Γ,type B and type C. Similarly, Lemma 4.17 states that if in an environment where type variable
α is disjoint to A1 , type B is disjoint to type C and A2 is disjoint to A1 , then B stays disjoint toC after substituting α with A2 in environment Γ, type B and type C. Notice that substitution
lemmas no longer depend upon ground types.
Lemma 4.15 (Typing Substitution). If ∆; Γ, α ∗ A1 ⊢ e : B and ∆; Γ ⊢ A2 ∗ A1 then∆; Γ[ α ⇝ A2 ] ⊢ e[ α ⇝ A2 ] : B[ α ⇝ A2 ]Lemma 4.16 (Subtyping Substitution). If ∆; Γ, α ∗ A1 ⊢ B <: C and ∆; Γ ⊢ A2 ∗ A1 then∆; Γ[ α ⇝ A2 ] ⊢ B[ α ⇝ A2 ] <: C[ α ⇝ A2 ]Lemma 4.17 (Disjointness Substitution). If ∆; Γ, α ∗ A1 ⊢ B ∗ C and ∆; Γ ⊢ A2 ∗ A1 then∆; Γ[ α ⇝ A2 ] ⊢ B[ α ⇝ A2 ] ∗ C[ α ⇝ A2 ]78

4.2 Redesigning Disjoint Polymorphism∆; Γ ⊢ e −→ e′(Operational Semantics)

polystep-appl∆; Γ ⊢ e1 −→ e′1∆; Γ ⊢ e1 e2 −→ e′1 e2polystep-appr∆; Γ ⊢ e −→ e′∆; Γ ⊢ v e −→ v e′polystep-tappl∆; Γ ⊢ e −→ e′∆; Γ ⊢ e B −→ e′ Bpolystep-beta∆; Γ ⊢ (λx.e) v −→ e[x ⇝ v]

polystep-tappdisj∆; Γ ⊢ (Λ(α ∗ A).e) B −→ e[ α ⇝ B]

polystep-switch∆; Γ ⊢ e −→ e′∆; Γ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }polystep-switchl
value v∆; Γ ⊢ ⌊v⌋ <: A∆; Γ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]
polystep-switchr
value v∆; Γ ⊢ ⌊v⌋ <: B∆; Γ ⊢ switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]

Figure 4.10: Operational Semantics for λu .

Narrowing lemmas. Narrowing lemmas for polymorphic λu are interesting to discuss.
Typing narrowing, subtyping narrowing, and disjointness narrowing are stated as Lemma 4.18,
Lemma 4.19, and Lemma 4.20 respectively. Narrowing lemmas essentially explain the relation between disjointness and subtyping. In general, they state that it is safe to replace
the bound of a type variable with a supertype of it’s existing bound. Typing narrowing(Lemma 4.18) states that if in a context where α is disjoint to A1 , an expression e has type B
and A1 is subtype of A2 , then e still has type B after replacing the bound of α with A2 .
Subtyping narrowing (Lemma 4.19) states that if in a context where α is disjoint to A1 ,type B is a subtype of type C and A1 is subtype of A2 , then B stays subtype of C after replacing
the bound of α with A2 . Similarly, disjointness narrowing (Lemma 4.20) states that if we
update the bound of a type variable with a subtype of its current bound, then it does notaffect disjointness. Types A and B stay disjoint if we update the bound of type variable α
from A1 to its subtype A2 .
Lemma 4.18 (Typing narrowing). If ∆; Γ, α ∗ A1 ⊢ e : B and ∆; Γ ⊢ A1 <: A2 then∆; Γ, α ∗ A2 ⊢ e : B
Lemma 4.19 (Subtyping narrowing). If ∆; Γ, α ∗ A1 ⊢ B <: C and ∆; Γ ⊢ A1 <: A2 then∆; Γ, α ∗ A2 ⊢ B <: C79

4 Revisiting Disjointness
Lemma 4.20 (Disjointness narrowing). If ∆; Γ, α ∗ A1 ⊢ B ∗ C and ∆; Γ ⊢ A1 <: A2 then∆; Γ, α ∗ A2 ⊢ B ∗ C
Weakening lemmas. Weakening lemmas for polymorphic λu are stated as typing weakening (Lemma 4.21), subtyping weakening (Lemma 4.22), and disjointness weakening (Lemma 4.23).
Weakening lemmas state that if a relation is valid in a smaller context, then it stays valid
in an enlarged context given that the enlarged context is well-formed. Typing weakening(Lemma 4.21) states that if in a smaller context [Γ1 , Γ2 ], an expression e has type B and an
enlarged context [Γ1 , Γ3 , Γ2 ] is well-formed, then e still has type B in the extended context[Γ1 , Γ3 , Γ2 ].
Subtyping weakening (Lemma 4.22) states that if in a smaller context [Γ1 , Γ2 ], type B is asubtype of type C and an enlarged context [Γ1 , Γ3 , Γ2 ] is well-formed, then B stays subtype
of C in the extended context [Γ1 , Γ3 , Γ2 ]. Similarly, disjointness weakening (Lemma 4.23)
states that if in a smaller context [Γ1 , Γ2 ], type B is disjoint to type C and an enlarged context[Γ1 , Γ3 , Γ2 ] is well-formed, then B stays disjoint to C in the extended context [Γ1 , Γ3 , Γ2 ].
Lemma 4.21 (Typing weakening). If ∆; Γ1 , Γ2 ⊢ e : B and ok Γ1 , Γ3 , Γ2 then ∆; Γ1 , Γ3 , Γ2
⊢e:B
Lemma 4.22 (Subtyping weakening). If ∆; Γ1 , Γ2 ⊢ B <: C and ok Γ1 , Γ3 , Γ2 then ∆; Γ1 ,
Γ3 , Γ2 ⊢ B <: C
Lemma 4.23 (Disjointness weakening). If ∆; Γ1 , Γ2 ⊢ B ∗ C and ok Γ1 , Γ3 , Γ2 then ∆; Γ1 ,
Γ3 , Γ2 ⊢ B ∗ C
More auxiliary lemmas. We discuss a few more interesting auxiliary lemmas in thisparagraph. Lemma 4.24, Lemma 4.25, Lemma 4.26 and Lemma 4.27 are essential in proving
the metatheory and are shown next. Lemma 4.24 states that if a union ordinary type A⊚
is a subtype of union splittable type B (B1 ◁ B ▷ B2 ), then A is either subtype of B1 or B2 .
Lemma 4.25 states that if a union splittable type A (A1 ◁ A ▷ A2 ) is well-formed, then both
A1 and A2 are well-formed. Lemma 4.26 states disjointness symmetry. Finally, Lemma 4.27
states that if A and B are disjoint types, then this is not the case that a value v checks against
both A and B.
Lemma 4.24 (Subtyping inversion of union ordinary and union splittable types). If ∆; Γ ⊢A <: B and B1 ◁ B ▷ B2 and A⊚ then ∆; Γ ⊢ A <: B1 ∨ ∆; Γ ⊢ A <: B2 .
Lemma 4.25 (Well-formedness inversion of union splittable types). If ∆; Γ ⊢ A and A1 ◁A ▷ A2 then ∆; Γ ⊢ A1 ∧ ∆; Γ ⊢ A2 .

80

4.2 Redesigning Disjoint Polymorphism
Lemma 4.26 (Disjointness symmetry). If ∆; Γ ⊢ A ∗ B then ∆; Γ ⊢ B ∗ A.
Lemma 4.27 (Exclusivity of Disjoint Types). If ∆; Γ ⊢ A ∗ B then ∄ v such that both ∆; Γ ⊢v : A and ∆; Γ ⊢ v : B holds.

81

5 Togetherness: Switches and Merges
5.1 Overview
λu and its extensions discussed so far mainly revolve around deterministic type-based elimination of union types. Section 3.2 discusses a simple λu calculus and introduces the key
ideas including disjointness. Section 3.3 enriches λu with more advance features including
subtyping distributivity, nominal types, and intersection types. Section 3.4.1 and Chapter 4
further study λu with disjoint polymorphism. The restriction of overlapping types in alternative branches of a type-based switch expression by employing disjointness has been an
integral part of the study so far.
We study λu with intersection types in Section 3.3, but so far, none of the calculi in thisthesis studies a respective term level construct to introduce intersection types. In this chapter
we extend λu with a so called merge operator[Dunfield 2014; Reynolds 1988], which acts as aterm to introduce intersection types. The resulting calculus is called λum i.e. union calculus
with the merge operator. Intersection types are useful without the merge operator but themerge operator increases term level expressiveness of the calculus. The merge operator has
been studied by various researchers in literature [Dunfield 2014; Huang and Oliveira 2020;
Oliveira et al. 2016; Reynolds 1988]. Calculi with the merge operator are known to havewide practical applications. The merge operator together with the intersection types is naturally able to encode various advanced programming features such as nested composition[Bi et al. 2018b], multi-field records from single-field records [Reynolds 1988], and function
overloading [Castagna et al. 1995; Reynolds 1988] among others.
Essence of the merge operator. The typical introduction rule for the intersection types
without the merge operator is:
typ-and

Γ⊢e:A

Γ⊢e:B

Γ⊢e:A∧B
Rule typ-and allows to construct certain terms of intersection types, such as:83

5 Togetherness: Switches and Merges

1 : Int

1 : Int

1 : Int ∧ Int

typ-andor

1 : Int

1:⊤

1 : Int ∧ ⊤typ-and

Perhaps a more useful program with typical introduction rule for intersection types is:

λx.x : Int → Int

λx.x : Bool → Bool

λx.x : Int → Int ∧ Bool → Bool

typ-and

However, this rule does not have enough expressiveness to type-check a program consistingof non-overlapping types, for example, Int and Bool. A part of such a program is an integer,
and the other part is a boolean. Theoretically, since intersection types correspond to producttypes, in our work a pair expresses such program with product types as:(1, true) : (Int, Bool)
Such a pair of non-overlapping product types suggests the need of an equivalent expression
in programming languages to introduce non-overlapping intersection of types i.e. Int∧Bool.
The merge operator (e1 ‚‚e2 ) [Dunfield 2014; Oliveira et al. 2016; Reynolds 1988] is capableof expressing such programs with intersection types. The introduction rule for intersection
types in the presence of merge operator is:
typ-mergaΓ ⊢ e1 : AΓ ⊢ e2 : BΓ ⊢ e1 ‚‚e2 : A ∧ B
The expression e1 ‚‚e2 is called the merge operator. Notice that the two expressions in thepremise of rule typ-merga could be two different expressions. This is in contrast to therule typ-and where the expression stays the same. Therefore the introduction rule for intersection types in the presence of the merge operator allows to construct an expression of
non-overlapping types, such as:84

5.2 λum Calculus
Type
Expr

A, B, C
e

::=
::=

v
Γ

::=
::=

Value
Context⊤ | ⊥ | Int | A → B | A ∨ B | A ∧ B
x | i | e : A | λx.e : A → B | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } |e1 ‚‚e2 | fix x.e : A | ⊤i | λx.e : A → B | v1 ‚‚v2 | ⊤· | Γ, x : A

A <: B(Subtyping)

s-top

s-int

A <: ⊤

Int <: Ints-orb
A <: BA <: B ∨ Cs-arrow
B1 <: A1A2 <: B2
A1 → A2 <: B1 → B2

s-orc
A <: CA <: B ∨ C

s-andaA <: BA <: CA <: B ∧ C

s-bot⊥ <: As-ora
A <: CB <: CA ∨ B <: Cs-andb
A <: CA ∧ B <: Cs-andcB <: CA ∧ B <: C

Figure 5.1: Syntax and subtyping for λum .

1 : Int

true : Bool1‚‚true : Int ∧ Bool

typ-merga

Elimination form for pairs and merges. Note that the pairs and merges diverge in theirelimination form. Pairs are eliminated by explicit elimination constructs such as fst andsnd or explicit tags. Whereas the merge operator has an implicit elimination form usually

via subtyping. The direct operational semantics for the merge operator is type-dependent[Huang and Oliveira 2020]. This will be further discussed in later sections.

5.2 λum Calculus
5.2.1 Syntax, subtyping, and typing
Syntax and subtyping. Figure 5.1 shows the syntax and subtyping for λum . The notabledifference from λu is the addition of the merge operator (e1 ‚‚e2 ) in the syntactic category of
expressions. Apart from that, lambda expressions are annotated with the input and output
types i.e. λx.e : A → B. We also add a fix point operator (fix x.e : A) and a top term (⊤) in theexpressions. Values are constituted of integers (i), lambda expressions (λx.e : A → B), top
expression (⊤) and a merge operator consisting of all the values is also a value (v1 ‚‚v2 ). Types
and the context stay the same as in λu with intersection types. Subtyping is also standard85

5 Togetherness: Switches and Merges
Γ⊢e:A
typ-varx:A ∈ ΓΓ⊢x:A

(Typing)
typ-intΓ ⊢ i : Int

typ-absann
Γ, x : A ⊢ e : BΓ ⊢ (λx.e : A → B) : A → B
typ-annΓ⊢e:AΓ ⊢ (e : A) : A

typ-subΓ⊢e:A
A <: B
Γ⊢e:B

typ-appΓ ⊢ e1 : A → BΓ ⊢ e2 : AΓ ⊢ e1 e2 : B

typ-switcha
Γ⊢e:A∨BΓ, x : A ⊢ e1 : C
Γ, y : B ⊢ e2 : CΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
typ-mergaΓ ⊢ e1 : AΓ ⊢ e2 : BΓ ⊢ e1 ‚‚e2 : A ∧ B

typ-fixΓ⊢e:AΓ ⊢ (fix x.e : A) : A

Figure 5.2: Typing for λum .

for a calculus with intersection and union types. Note that subtyping does not deal with
distributivity rules. Rules s-top, s-int, s-arrow, s-bot, s-ora, s-orb, and s-orc are alreadyexplained. Rules s-and, s-andb, and s-andc deal with intersection types. The subtyping
relation for λum is reflexive and transitive.
Lemma 5.1 (Subtyping reflexivity). A <: A
Lemma 5.2 (Subtyping transitivity). If A <: B and B <: C then A <: CTyping. Figure 5.2 shows the typing rules for λum . A notable difference is the addition ofrules typ-merga, typ-fix, and typ-ann. Rule typ-merga is the typing rule for the newly
added merge operator. This rule states that if an expression e1 has type A and an expression
e2 has type B, then the merge of e1 and e2 has type A ∧ B. Rule typ-fix is a standard typing
rule for the fix point operator. Similarly rule typ-ann is the standard typing rule for type
annotations. The rest of the rules are standard and have already been discussed.
Unlike disjoint intersection types [Oliveira et al. 2016], we do not impose a disjointness
restriction in rule typ-merga. Notice that the disjointness restriction has also been scrappedin rule typ-switcha. This is because the addition of the merge operator makes determinism non-trivial. Therefore we present a simple calculus without disjointness restriction andthe calculus is not deterministic. Determinism and its challenges are further discussed in
Section 5.4. Using rule typ-merga we can construct the following program which may be asource of non-determinism.

865.2 λum Calculus

1 : Int

2 : Int1‚‚2 : Int ∧ Int

typ-merga

When applying succ function to the argument 1‚‚2 the result may either be 2 or 3. Anothersource of non-determinism in this calculus is:
Bool isInt (x : Int|Bool) = switch (x)(x:Bool)(y:Int)→ false→ true

The above program may return different value depending on the order of the branches if avalue of type Int ∧ Bool is passed, such as 1‚‚true. Moreover, even though Bool and Int are
non-overlapping types, the program may fall either in the first or the second branch. Thisis because the type of x (when x is 1‚‚true) is a subtype of both Bool and Int. By employing
rule typ-sub x can be treated as value of both types i.e. Bool and Int.

5.2.2 Type castingType casting lies at the core of the calculi with direct operational semantics for the merge
operator [Huang and Oliveira 2020]. Generally speaking, type casting makes an expression
consistent with the type under which that expression is cast. For example when an expression
1‚‚true casts under type Int it gives 1 and true when it casts under the type Bool. In summary,type casting enables extracting the value of a specific type from the merge operator. For
example:1‚‚true −→Int 1 (applying rule cst-mergl)
In this example (1‚‚true −→Int 1) when we cast a merge of 1‚‚true under the type Int it resultsin 1. This is because of the fact that the only integer we can get from a merge of 1‚‚true is 1.
Similarly true‚‚1 also yields 1 when casts under the type Int:true‚‚1 −→Int 1 (applying rule cst-mergr)

Type casting relation. The type casting relation is shown in Figure 5.3. The relationv −→A v′ shows casting of a value v under type A to another value v′ . Note that the type
casting is only applicable to values. We elaborate each rule next.
When casting an expression under ⊤ type it results in ⊤ expression as stated in rule csttop. Casting an integer under Int returns the same integer. Casting rules for union types

87

5 Togetherness: Switches and Merges
Ord A(Ordinary Types)
ord-int

ord-arrow

Ord Int

Ord A → B

v −→A v′

(Type Casting)

cst-top

cst-intv −→⊤ ⊤

i −→Int i

cst-orlv −→A v′
v −→A∨B v′

cst-arrow
A1 → B1 <: A2 → B2
λx.e : A1 → B1 −→A2 →B2 λx.e : A1 → B1
cst-mergr
Ord A
v2 −→A v′2v1 ‚‚v2 −→A v′2

cst-orrv −→B v′
v −→A∨B v′
cst-mergl
Ord A
v1 −→A v′1
v1 ‚‚v2 −→A v′1

cst-mergv −→A v1
v −→B v2
v −→A∧B v1 ‚‚v2

Figure 5.3: Type casting for λum .

are interesting. If an expression casts under a part of the union type then that expression
casts under whole union type as stated in rules cst-orl and cst-orr. Casting a lambda
expression under a function type returns the same lambda expression as stated in rule cstarrow. A merge operator casts under an ordinary type if either part of the merge operator
casts under that ordinary type (rules cst-mergl and cst-mergl). Casting an expression
under an intersection type results in a merge (rule cst-merg). Type casting preserves thestandard properties of type preservation and progress:• Type casting preservation (Theorem 5.3), which states that if an expression v type casts
under type A to some expression v′ , then v′ has type A.
• Type casting progress (Theorem 5.4), which states that if an expression v has type A,
then v type casts under A to some v′ .
Theorem 5.3 (Type Casting Preservation). If v : A and v −→A v′ then Γ ⊢ v′ : A.
Theorem 5.4 (Type Casting Progress). If · ⊢ v : A then v −→A v′
Theorems 5.3 and 5.4 are vital in proving the type-safety of the calculus. Notice that thetype casting relation is non-deterministic which results in non-deterministic semantics. For
the illustration purpose, think for a moment what will be the result of the following type cast?

88

5.2 λum Calculus
true‚‚1 −→Int∨Bool ??
It can either result in 1 or true depending upon the type casting rule we apply:
1‚‚true −→Int∨Bool 1 (applying rule cst-orl)
1‚‚true −→Int∨Bool true (applying rule cst-orr)

5.2.3 Operational semantics
The operational semantics for λum is shown in Figure 5.4. Rules step-appl and step-appr
are standard reduction rules. Rule step-abeta is the beta reduction rule. This rule first type
casts the argument under the input type and then substitutes the argument in the body of thelambda expression. Type casting drops the unnecessary part from the argument and makes
the input value consistent with the input type of applied function. For example:(λx.true‚‚x : Int → Bool) (1‚‚true)
A merge of 1‚‚true is the input being passed to lambda expression annotated with an input
type of Int. Since the dynamic type of 1‚‚true is Int ∧ Bool which is a subtype of Int. The
above application type-checks. But before passing 1‚‚true to the lambda body it will be castunder the input type i.e Int to make input value and the input type consistent:
1‚‚true −→Int 1
The application (λx.true‚‚x : Int → Bool) (1‚‚true) will result in true‚‚1 : Bool after betareduction. We show the partial derivation next:

1‚‚true −→Int 1

cst-mergl(λx.true‚‚x : Int → Bool) (1‚‚true) −→ true‚‚1 : Bool

step-abeta

Note that the expression true‚‚1 : Bool is not a value. Rule step-annv reduces such annotated values. The expression (true‚‚1 : Bool) takes one more step following the rule stepannv and results in true. This is illustrated by the following derivation:true −→Bool true
1‚‚true −→Bool true

cst-mergrtrue‚‚1 : Bool −→ truestep-annv

89

5 Togetherness: Switches and Merges
e −→ e′(Small-step operational semantics)

step-appl

step-appr

step-abeta

e1 e2 −→ e′1 e2v e −→ v e′

(λx.e : A → B) v −→ (e[x ⇝ v′ ]) : B

e1 −→ e′1e −→ e′v −→A v′step-dispatch

step-mergl

step-mergr

step-ann(v1 ‚‚v2 ) v −→ e′e1 ‚‚e2 −→ e′1 ‚‚e2v‚‚e −→ v‚‚e′e : A −→ e′ : A(v1 ‚‚v2 ) v −→d e′

e1 −→ e′1

step-annv

e −→ e′e −→ e′v −→A v′step-fixv : A −→ v′

fix x.e : A −→ e[x ⇝ fix x.e : A]

step-switche −→ e′switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }step-switchlm

v −→A v′

switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v′ ]step-switchrm

v −→B v′

switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v′ ]Figure 5.4: Operational semantics for λum .

Rules step-mergl and step-mergr reduce the left and right part of the merge operator.
Rule step-anno reduces an annotated expression. Rules stepswitch, step-switchl, andstep-switchr are the standard reduction rules as in λu and are already discussed. Finally,rule step-fix is a standard reduction rule for the fix point operator. It replaces the fix pointwith x in its own body. The rule step-dispatch requires detailed explanation and is discussed next.

5.2.4 Applicative dispatch and rule step-dispatch
Sometimes lambda expression maybe hidden inside a merge. The rule step-dispatch deals
with such cases. The rule step-dispatch uses another relation called applicative dispatch[Xue et al. 2022]. Applicative dispatch relation is shown at the upper part of Figure 5.5. Essentially, applicative dispatch selects the appropriate function for application from the merge90

5.2 λum Calculus
e −→d e′(Applicative dispatch)apd-mleft⌊v⌋ <: ⌊v1 ⌋λ¬⌊v⌋ <: ⌊v2 ⌋λ(v1 ‚‚v2 ) v −→d v1 v
apd-both⌊v⌋ <: ⌊v1 ⌋λapd-mright¬⌊v⌋ <: ⌊v1 ⌋λ⌊v⌋ <: ⌊v2 ⌋λ(v1 ‚‚v2 ) v −→d v2 v
⌊v⌋ <: ⌊v2 ⌋λ(v1 ‚‚v2 ) v −→d v1 v‚‚v2 v
Dynamic Type ⌊v⌋⌊i⌋⌊⊤⌋⌊λx.e : A → B⌋
⌊v1 ‚‚v2 ⌋

=
=
=
=

Input Type ⌊v⌋λ

Int
⊤
A→B
⌊v1 ⌋ ∧ ⌊v1 ⌋⌊λx.e : A → B⌋λ⌊v1 ‚‚v2 ⌋λ
⌊i⌋λ
⌊⊤⌋λ

=
=
=
=

A
⌊v1 ⌋λ ∨ ⌊v1 ⌋λ⊥⊥Figure 5.5: Dynamic dispatch, dynamic type and input type relation for λum .

depending upon the argument type. Moreover, it provides the adequacy of function overloading. For example:((λx.succ : Int → Int)‚‚true) 1
The above application is valid and type-checks. We need to extract (λx.succ : Int → Int)out of this merge and then apply it to the argument 1. Note that it is essential to choose(λx.succ : Int → Int) in this case. The calculus will not be type preserving otherwise.
This is due to the fact that (true 1) is not a valid application. The problem gets worst if themerge contains multiple functions. Therefore special care needs to be taken to choose theappropriate function for application. Applicative dispatch compares the argument type with
the input type of each expression in the merge. It then applies all of the overlapping functions.
The derivation for ((λx.succ : Int → Int)‚‚true) 1 is shown below:

apd-mleftstep-dispatch

Int <: Int¬(Int <: ⊥)(λx.succ : Int → Int‚‚true) −→ (λx.succ : Int → Int) 1(λx.succ : Int → Int‚‚true) 1 −→ (λx.succ : Int → Int) 1

The expression ((λx.succ : Int → Int) 1) becomes a standard application with lambda expression at the left side and reduces by following the standard beta reduction.

91

5 Togetherness: Switches and Merges
Applicative dispatch relation. Rule adp-mleft applies the left part of the merge to theargument. It consist of two premises. The first premise states that the dynamic type of theargument is a subtype of the input type of left part of merge. While second premise states thatthe dynamic type of the argument is not a subtype of the input type of right part of the merge.
If both of the conditions succeed, this rule applies left part of the merge to the argument. For
example consider the following application:((λx.succ : Int → Int)‚‚(λx.not : Bool → Bool)) 1
The functional merge consists of two functions, succ and not. Input type of succ function
is Int and the input type of not function is Bool. Note that the dynamic type of argument 1 isInt. It is evident that the only function that can be applied to 1 among succ and not functions
is succ. Rule adp-mleft checks that the dynamic type of 1 is a subtype of Int, and is not a
subtype of Bool. Therefore succ is applied to 1. Rule adp-mright repeats the same for theright part of the merge.
Rule adp-both is interesting. It applies both the left and the right part of the merge tothe argument and returns a merge of the output from both functions. It is applicable in cases
where the dynamic type of the argument is a subtype of the left as well as the right part ofthe merge. Both the succ as well as the pred functions are applicable to 1 in the following
application.
((λx.succ : Int → Int)‚‚(λx.pred : Int → Int)) 1
Dynamic type and input type relations. The dynamic type and the input type relations
are shown in Figure 5.5. Dynamic type returns the principal type of a value and has alreadybeen discussed. The input type relation returns the input type of a functional value. The
functional value is either a lambda expression or a merge containing at least one lambda
expression. The first case of the input type deals with lambda expressions. In this case theinput type simply returns the input type of the given lambda expression. The second casedeals with functional merges. In this case the output is the union of the input types of all thelambda expressions in the merge. The other values cannot appear on the left side of a valid
application. Therefore they cannot have an input type. We simply return ⊥ in such cases.

5.2.5 Metatheory of λum
The standard properties of type preservation and progress hold in this system. Type preservation (Theorem 5.5) states that the types are preserved during reduction. The progress (Theorem 5.6) states that a well-typed expression is either a value or it reduces until it becomes92

5.3 λum and Dunfield’s systema value. Note that the operational semantics is type dependent i.e. operational semantics
depends upon the casting relation. Therefore type preservation depends upon type castingpreservation (Theorem 5.3) and the progress depends upon type casting progress (Theorem 5.4).
Theorem 5.5 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then Γ ⊢ e′ : A.
Theorem 5.6 (Progress). If · ⊢ e : A then either e is a value; or e −→ e′ for some e′ .
Compelling properties of λum .

We show a few interesting properties of λum in thissection. In particular, Lemma 5.7 is essential in λum . It states that if a value v casts under afunction type then the resulting value must be a lambda expression. Lemma 5.8 states that if
a value v checks against a function type A → B then the input type from the function type(A) is a subtype of the result from input type relation i.e. A <: ⌊v⌋λ . Lemma 5.9 states that
if the dynamic type of value v1 is a subtype of the input type of v i.e. ⌊v1 ⌋ <: ⌊v⌋λ then thevalue v checks against a function type such that the dynamic type of v is the input type ofthe function. Lemmas 5.8 and 5.9 are essential for the type preservation of λum . Similarly
Lemma 5.10 is essential for the progress. It states that a well-typed application of the merge
operator must take a step.
Lemma 5.7 (Casting under a function type). If v −→A→B v′ , then ∃ e A1 B1, v′ = λx.e :A1 → B 1 .
Lemma 5.8 (Covariance of input type). If · ⊢ v : A → B then A <: ⌊v⌋λ .
Lemma 5.9 (Applicative dispatch preservation). If · ⊢ v : A and ⌊v1 ⌋ <: ⌊v⌋λ then · ⊢ v :⌊v1 ⌋ → ⊤Lemma 5.10 (Applicative dispatch progress). If · ⊢ v1 ‚‚v2 : A → B and · ⊢ v : A then ∃e′(v1 ‚‚v2 ) v −→ e′ .

5.3 λum and Dunfield’s system
Dunfield [2014] studies a calculus with intersection types, union types, and the merge operator. The merge operator acts as an introduction form for intersection types and elimination
form for union types in their calculus. They adopt an elaboration semantics and elaborate
the source language to a target language. Dunfield elaborates intersection types to producttypes and union types to sum types. Similarly they elaborate the merge operator to a pair.
The target language is a standard extension of the simply typed lambda calculus with thepairs, sum types, and the product types.

93

5 Togetherness: Switches and Merges
We will explain the Dunfield’s type system and prove its completeness with respect to λum
in this section. In particular, we show that all the programs that Dunfield’s system typechecks, are type-checked by λum . In contrast to Dunfield’s system, we propose a direct operational semantics which significantly simplifies the theoretical complexity of the system.
Moreover, contrary to the Dunfield [2014] source semantics, the direct operational semantics studied in λum is type-sound.

5.3.1 Dunfield’s calculus
Syntax. Syntax for Dunfield’s system is shown in the upper part of Figure 5.6. Types consist of ⊤, ⊥, Int, A → B, A ∨ B and A ∧ B. Note that the type ⊥ has not been studied inoriginal Dunfield’s calculus. Expressions consist of variables, literals, abstractions, applications, merge operator and the fix point operator. Note that lambda abstractions (λx.e) do
not carry types. Similarly, variables, literals, abstractions and a merge of values constitutevalues. The typing context is standard. Finally E represents the evaluation context.
Type system. The Dunfield’s type system is shown in Figure 5.6. We suggest the reader
to ignore the highlighted part in the rules for the sake of simplicity at this stage. We will
explain the highlighted part later. Rules dtyp-int, dtyp-sub, dtyp-var, dtyp-app, anddtyp-abs are already explained modulo curly arrow and the highlighted part in each rule.
Rules dtyp-merga and dtyp-mergb type-check a merge operator. A notable difference inrule dtyp-abs is that the lambda expression no longer carries type annotations. Rule dtypand is an introduction rule for intersection types. It states that if an expression e has type A
and type B then e has type A ∧ B. This rule is useful to type unannotated lambdas as a variantof function overloading, such as:

dtyp-and

λx.e : Int → Int

λx.e : Bool → Bool

λx.e : Int → Int ∧ Bool → Bool

Rules dtyp-andl and dtyp-andr are for intersection elimination. They state that if an
expression e has type A ∧ B, then the expression e can have either type A or type B. Similarly
rules dtyp-orl and dtyp-orr are for union introduction. They state that an expression e canhave type A∨B if the expression e has either type A or type B. Note that the rules dtyp-andl,
dtyp-andr, dtyp-orl, and dtyp-orr can be subsumed by subsumption rule. Rule dtypswitch is for the union elimination. Finally, rule dtyp-fix type-checks fix points. Interested
readers may refer to the original paper [Dunfield 2014] for details.

945.3 λum and Dunfield’s system
Type
Expr
Value
Context
Evaluation Context

A, B, Cev
ΓE

⊤ | ⊥ | Int | A → B | A ∨ B | A ∧ B
x | i | λx.e | e1 e2 | e1 ‚‚e2 | fix x.e
x | i | λx.e | v1 ‚‚v2
· | Γ, x : A[] | E e | v E | E ‚‚e | e‚‚E

::=
::=
::=
::=
::=

Γ ⊢d e : A ⇝ e′

(Dunfield’s type system)
dtyp-subΓ ⊢d e : A ⇝ e′

dtyp-intΓ ⊢d e : B ⇝ e′

Γ ⊢d i : Int ⇝ idtyp-appΓ ⊢d e1 : A → B ⇝ e′1

A <: BΓ ⊢d e2 : A ⇝ e′2

Γ ⊢d e1 e2 : B ⇝ e′1 e′2dtyp-abs
Γ, x : A ⊢d e : B ⇝ e′
Γ ⊢d λx.e : A → B ⇝ λx.e′ : A → B

dtyp-mergaΓ ⊢d e1 : A ⇝ e′1

dtyp-mergbΓ ⊢d e2 : B ⇝ e′2

e′1

e′2

Γ ⊢d e1 ‚‚e2 : A ⇝dtyp-var
x:A ∈ ΓΓ ⊢d x : A ⇝ xΓ ⊢d e1 ‚‚e2 : B ⇝dtyp-andΓ ⊢d e : A ⇝ e1

Γ ⊢d e : B ⇝ e2

Γ ⊢d e : A ∧ B ⇝ e1 ‚‚e2

dtyp-andlΓ ⊢d e : A ∧ B ⇝ e′

dtyp-andrΓ ⊢d e : A ∧ B ⇝ e′

dtyp-orlΓ ⊢d e : A ⇝ e′

Γ ⊢d e : A ⇝ e′

Γ ⊢d e : B ⇝ e′

Γ ⊢d e : A ∨ B ⇝ e′

dtyp-orrΓ ⊢d e : B ⇝ e′

dtyp-switch
Γ, x : A ⊢d E [x] : C ⇝ e1
Γ ⊢d e : A ∨ B ⇝ e′Γ, y : B ⊢d E [y] : C ⇝ e2

Γ ⊢d e : A ∨ B ⇝ e′

Γ ⊢d E [e′ ] : C ⇝ switch e′ {(x : A) → e1 , (y : B) → e2 }dtyp-fixΓ ⊢d e : A ⇝ e′
Γ ⊢d fix x.e : A ⇝ fix x.e′ : A

Figure 5.6: Source syntax and source typing of Dunfield’s calculus.

A note on Dunfield’s type system. The Dunfield’s type system allows certain (hidden)
ill-typed parts of the programs. For example the program 1‚‚succ true : Int type-checks following the rule dtyp-merga. It is important to note that a part of this program i.e. succ trueis ill-typed. A closer observation reveals that such ill-typed parts of the programs have nopractical effects and are not essential. Therefore we do not account for such ill-typed parts95

5 Togetherness: Switches and Mergesof the programs in our calculus. We prove completeness upto the point where all the partsof a program are well-typed.

5.3.2 Completeness with respect to Dunfield’s calculus
The type system of λum is complete with respect to Dunfield’s type system, meaning that all
the programs that type-check in Dunfield’s system, can also be encoded in λum . We prove
completeness by elaborating programs that type-check in Dunfield’s system to λum . The
elaboration of the well-typed programs from the Dunfield’s system to λum is explained next.

Elaboration to λum . The highlighted part of rules in Figure 5.6 shows the elaborated
program in λum . Rules dtyp-int and dtyp-var state that an integer from the Dunfield’ssystem elaborates to an integer, and a variable to a variable in λum respectively. Rule dtypabs is of significant interest. This rule elaborates an un-annotated lambda expression from
Dunfield’s calculus to a type annotated lambda expression in λum i.e. it elaborates λx.e to
λx.e : A → B. Rule dtyp-and is also interesting. It elaborates an expression that checks
against an intersection of two types into a merge of the same expressions. The case of unannotated lambda expressions is of particular interest with rule dtyp-and. The Dunfield’s system has unannotated lambda expression and the typing rule dtyp-and is able to encode thefollowing program:

dtyp-and

λx.e : Int → Int

λx.e : Bool → Bool

λx.e : Int → Int ∧ Bool → Bool

Whereas λum cannot encode the above program in its current form. Therefore we elaborate
this program into an equivalent program in λum and preserve the completeness with respect
to the Dunfield’s system. The elaboration for such a program is shown next:

λx.e : Int → Int ∧ Bool → Bool ⇝ (λx.e : Int → Int)‚‚(λx.e : Bool → Bool)The expression λx.e type-checks against Int → Int ∧ Bool → Bool in the Dunfield’s systemand elaborates to (λx.e : Int → Int)‚‚(λx.e : Bool → Bool) in λum . The elaborated program
type-checks against Int → Int ∧ Bool → Bool in λum .

96

5.4 Stumbling block: non-determinism

typ-merga

λx.e : Int → Int : Int → Int

λx.e : Bool → Bool : Bool → Bool

(λx.e : Int → Int)‚‚(λx.e : Bool → Bool) : Int → Int ∧ Bool → Bool

Completeness. Lemma 5.11 states that if an expression e type-checks in the Dunfield’ssystem against type A and elaborates to e′ in λum then e′ has type A in λum .
Lemma 5.11 (Completeness with respect to Dunfield’s system). ∀ Γ e A e′ , If Γ ⊢d e : A ⇝e′ then Γ ⊢ e′ : A
Comparison of λum with Dunfield’s calculus. The direct operational semantics proposed by Dunfield [2014] are not type preserving. Therefore Dunfield adopts an elaboration
semantics in her calculus which significantly increases the theoretical complexity of the calculus. A reader has to understand both the source semantics and the target semantics to get
the intuition of the programs. Moreover, elaboration itself is essential to understand. On thecontrary, we propose a direct operational semantics for the source language. Direct operational semantics eliminates the need of elaboration and target language. This leaves only thesource operational semantics without any intermediary steps.

5.4 Stumbling block: non-determinism
Determinism is an important property of a calculus in theory. But the calculus (λum ) proposed in this chapter is non-deterministic. The non-determinism comes from two sourcesin λum . One source is the merge operator and the other is switch expression. We explain
both of the sources next. A few proposals to deal with the non-determinism are discussed in
Chapter 8.

5.4.1 Non-determinism in the presence of merge operator
Recall that λum drops the disjointness in merges and in switches. This allows to construct
the merges of overlapping types such as 1‚‚2. When we construct a merge of 1‚‚2 and try to
extract an integer out of it using type directed semantic, we may get either 1 or 2.
1‚‚2 −→Int 1 rule cst-mergl
or97

5 Togetherness: Switches and Merges
1‚‚2 −→Int 2 rule cst-mergr
The operational semantics depends upon the type casting. Therefore the evaluation of an
expression to two different values results in a non-deterministic calculus.
Disjoint intersection types. Disjoint intersection types [Oliveira et al. 2016] address
non-determinism in the context of intersection types and the merge operator. The disjoint
intersection types forbid the construction of a merge with overlapping types. The typing rule
for the merge operator with disjoint intersection types is:
typ-mergb

Γ ⊢ e1 : AΓ ⊢ e2 : BA∗BΓ ⊢ e1 ‚‚e2 : A ∧ B
The third premise in the rule allows merges of only disjoint types or non-overlapping types.
For example a merge of 1‚‚true is allowed. Whereas 1‚‚2 is not allowed.

5.4.2 Non-determinism in the presence of switch expression
The second source of non-determinism in λum is the type-based switch expression. For
example a naive evaluation of the following program is not deterministic:
switch (x : Int ∨ ⊤) {(x : Int) → true, (y : ⊤) → false}
The above switch expression may evaluate either first or the second branch in a naive implementation of a switch expression. This is due to the fact that the type of scrutinee i.e 1
overlaps with both Int and ⊤. Therefore it may fall in either of the two branches. Such nondeterministic programs type-check by exploiting the following unconstrained typing rule forthe switch expression:
typ-switcha

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C

Γ, y : B ⊢ e2 : CΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
Disjoint switches. The λu (Chapter 3) proposes a deterministic solution for such typebased switch expressions. Similar to disjoint intersection types, λu allows two branches of a
type-based switch expression to have only the non-overlapping types. The typing rule for aswitch expression in λu is:98

5.4 Stumbling block: non-determinism
typ-switch

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C

Γ, y : B ⊢ e2 : CA∗BΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C
Note the last premise in rule typ-switch. It says that the branches of a switch expression
must not have overlapping types. Therefore the following program does not type-check in
λu because Int and ⊤ are overlapping types:
switch (x : Int ∨ ⊤) {(x : Int) → true, (y : ⊤) → false}
Whereas the following program type-checks because Int and Bool are non-overlapping or
disjoint types:
switch (x : Int ∨ Bool) {(x : Int) → true, (y : Bool) → false}
Note that the disjointness in disjoint intersection types and disjoint switches diverge. Twotypes are disjoint in disjoint intersection types when they do not share a common supertype. Whereas two types are disjoint in disjoint switches when they do not share a common
subtype.

5.4.3 Non-determinism with merge operator and switch expression
While the non-determinism for merges and switches has been studied separately, combining
disjoint intersection types and disjoint switches is non-trivial. The integration of disjoint
intersection types and disjoint switches poses novel challenges.
(λx.(switch x {(x : Int) → true, (y : Bool) → false}) : Int ∨ Bool → Bool) (1‚‚true)
The above program can be encoded in λum . It is an application of a lambda expression i.e(λx.(switch x {(x : Int) → true, (y : Bool) → false}) : Int ∨ Bool → Bool) to a merge i.e(1‚‚true). The input type of lambda expression is Int ∨ Bool. It is safe to pass a term of type
Int ∧ Bool as Int ∨ Bool using the subsumption rule.

Int <: Int
typ-merga
typ-subΓ ⊢ 1 : IntΓ ⊢ true : BoolΓ ⊢ 1‚‚true : Int ∧ Bool

Int <: Int ∨ Bools-orb

Int ∧ Bool <: Int ∨ Bools-andbΓ ⊢ 1‚‚true : Int ∨ BoolIn a more human readable language the same program can be written as:99

5 Togetherness: Switches and Merges
Bool isInt (x : Int | Bool) = switch (x)(x:Int) → true
(y:Bool) → false
isInt(1,,true)

Notice that the merge independently is deterministic i.e Int and Bool are disjoint types.
The switch independently is also deterministic i.e Int and Bool are disjoint types. But themerge value 1‚‚true can fall either in first branch or in second branch. This is because thevalue of type Int ∧ Bool can safely be used as value of type Int and Bool at the same time.

typ-merga
typ-subΓ ⊢ 1 : IntΓ ⊢ true : BoolΓ ⊢ 1‚‚true : Int ∧ Bool

Int <: Int
Int ∧ Bool <: Int

s-andbΓ ⊢ 1‚‚true : Int

and so as:

typ-merga
typ-subΓ ⊢ 1 : IntΓ ⊢ true : BoolΓ ⊢ 1‚‚true : Int ∧ Bool

Bool <: BoolInt ∧ Bool <: Bools-andcΓ ⊢ 1‚‚true : Bool

Therefore a naive composition of disjoint intersection types and disjoint switches leads tonon-determinism. We discuss a few proposals to deal with non-determinism in Chapter 8.

100

Part III
Duality of Intersection and Union Types101

6 The Duality of Subtyping(Duotyping)This chapter examines the duality of subtyping with intersection and union types. Duality
is a common concept in logic and many features and their duals have been studied. For
example, disjunctions are dual to conjunctions, universal quantifiers are dual to existential
quantifiers. Surprisingly, duality has not formally been studied in the context of subtyping inprogramming languages. We start with an overview of duality in subtyping, then we discuss
several calculi with duality and traditional subtyping. Finally, we conclude with the help ofcase studies that by employing duality in subtyping one can achieve significant benefits overtraditional subtyping.

6.1 Overview
This section gives an overview of Duotyping. We show how to design subtyping relations
employing Duotyping, and discuss the advantages of a design with Duotyping instead of a
traditional subtyping formulation in more detail.

6.1.1 Subtyping with union and intersection types
To motivate the design of Duotyping relations we first consider a traditional subtyping relation with union and intersection types, as well as top and bottom types. We choose a systemwith union and intersection types because these features are nowadays common in various
OOP languages, including Scala [Odersky et al. 2004], TypeScript [Bierman et al. 2014], Ceylon [King 2013] or Flow [Chaudhuri 2015]. Therefore union and intersection types are ofpractical interest. Furthermore union and intersection types are simple, intuitive and goodfor showing duality between concepts.
The types used for the subtyping relation include the top type ⊤, the bottom type ⊥, integer type Int, function type A → B, intersection type A ∧ B and the union type A ∨ B:

Types

A, B

::=

⊤ | ⊥ | Int | A → B | A ∧ B | A ∨ B

103

6 The Duality of Subtyping (Duotyping)A <: B(Traditional Subtyping)

s-top

s-bot

s-int

A <: ⊤⊥ <: A

Int <: Int

s-andb
A <: CA ∧ B <: Cs-andcB <: CA ∧ B <: Cs-arrow
B1 <: A1A2 <: B2
A1 → A2 <: B1 → B2s-ora
A <: CB <: CA ∨ B <: Cs-andaA <: BA <: CA <: B ∧ Cs-orb
A <: BA <: B ∨ Cs-orc
A <: CA <: B ∨ C

Figure 6.1: Subtyping for union and intersection types.

Traditional Subtyping. A simple subtyping relation accounting for union and intersection types is given in Figure 6.1. Rule s-top defines that every type is a subtype of ⊤, andrule s-bot states that every type is a supertype of ⊥. Rule s-int is for integers, and states that
Int is a subtype of itself. Rule s-arrow is the traditional subtyping rule for function types.
Rules s-anda, s-andb, and s-andc are subtyping rules for intersection types. Rules s-ora,s-orb, and s-orc are subtyping rules for union types. The rules that we employ here arequite common for systems with union and intersection types. For instance they are the same
rules used in various DOT-calculi [Amin et al. 2012] (which model the essence of Scala). For
simplicity we do not account for distributivity rules, which also appear in some type systems
and calculi [Barendregt et al. 1983; Bessai et al. 2015; Zappa Nardelli et al. 2018].

6.1.2 Subtyping Specifications using Duotyping
In the subtyping relation presented in Figure 6.1, it is quite obvious that many rules lookalike. Some rules are essentially a “mirror image” of other rules. The rules for top and bottom types are an example of this. Another example are the rules s-andb and s-orb. Although informally humans can easily observe the similarity between many of the rules, thissimilarity/duality is not expressed directly in the formalism. For example, there is nothing
preventing us from designing rules that are not duals. Duotyping aims at capturing duality
in the rules themselves, and expressing duality as part of the formalism, rather than just leaving duality informally observable by humans. This can prevent, for instance, designing rules
for dual concepts that do not really dualize. Therefore Duotyping can enforce consistency of
dual rule designs.
To illustrate how Duotyping rules are designed and relate to the traditional subtyping rules,
lets refactor the traditional rules in a few basic steps. Firstly, lets assume that we have asecond relation A :> B that captures the supertyping between a type A and B. Supertyping104

6.1 Overview
is nothing but the subtyping relation with its arguments flipped. So, the rules of supertyping
could be simply obtained by taking all the rules in Figure 6.1 and deriving corresponding
rules where all the arguments are flipped around. We skip that boring definition here. With
both supertyping and subtyping, the top and bottom rules can be presented as follows:
s-top

sup-bot

A <: ⊤

A :> ⊥

Similarly the rules rule s-andb and rule s-orb, can be presented as:s-andbsup-orb

A <: CA :> CA ∧ B <: CA ∨ B :> C

This simple refactoring shows that the only difference between dual rules is the relation itself,
and the (dual) language constructs. Apart from that everything else is the same.
Duotyping. With Duotyping we can provide a single unified rule, which captures thetwo distinct subtyping rules, instead. The Duotyping relation is parameterized by a mode ♢:
Mode

♢

::=

<: | :>

which can be subtyping (<:) or supertyping (:>). Thus the Duotyping relation is of the form:
A♢B
The mode ♢ is a (third) parameter of the relation (besides A and B). With this mode inplace, we can readily capture the two refactored rules for supertyping of bottom types andsubtyping of top types as two Duotyping rules. However this still requires us to write two
distinct rules. To unify those rules into a single one, we introduce a function ⌉♢⌈ that chooses
the right bound depending on the mode being used:⌉ <: ⌈ = ⊤⌉ :> ⌈ = ⊥If the mode is subtyping the upper bound of the relation is the top type, otherwise it is thebottom type. With ⌉♢⌈ we can then write a single unified rule that captures the upper boundsof subtyping and supertyping, and which generalizes both rule s-top and rule sup-bot:
gds-topbotA ♢ ⌉♢⌈

105

6 The Duality of Subtyping (Duotyping)A♢B(Declarative Duotyping)gds-topbot

gds-intA ♢ ⌉♢⌈

Int ♢ Int

gds-arrow
A1 ♢ A2
B1 ♢ B2
A1 → B1 ♢ A2 → B2

gds-lleftA♢C(A ♢? B) ♢ C

gds-rrightB♢C(A ♢? B) ♢ C

gds-dual
B♢AA♢B

gds-bothA♢B
A♢C
A ♢ (B ♢? C)

Figure 6.2: The Duotyping relation for a calculus with union and intersection types.

The Duality rule. The Duotyping rule above captures the 2 rules that were refactored
above. However there are 4 rules in total for top and bottom types (two for subtyping andtwo for supertyping). The two missing rules are:s-bot

sup-top⊥ <: A⊤ :> ATo capture these missing rules, the Duotyping relation includes a special duality rule:
gds-dual

B♢A
A♢B
which simply inverts the mode and flips the arguments of the relation. The definition of ♢is, unsurprisingly:<: = :>:> = <:
With the duality rule it is clear that the two missing rules are now derivable from the Duotyping rule for bounds and the duality rule. In essence this is the overall idea of the designof Duotyping rules.
Complete set of rules. Figure 6.2 shows the complete version of declarative Duotyping rules for a system with union and intersection types. Rule gds-topbot defines the rulebounds (which generalizes the rules for top and bottom types). Rule gds-int is a simple rule
for integers. Int is subtype and supertype of Int. Rule gds-arrow is an interesting case. In
the first premise A ♢ B we invert the mode instead of flipping the arguments of the relation,

1066.1 Overview
as done in rule s-arrow. One side-effect of this change is that it keeps the rule fully covariant, which contrasts with subtyping relations where for arrow types we need contravariancefor subtyping of the inputs. This apparently innocent change has important consequences
and plays a fundamental role to simplify transitivity proofs as we shall see in Section 6.1.5.
Rules gds-lleft, gds-rright, and gds-both each generalize two rules in the traditional
formulation of subtyping. Rule gds-lleft generalizes rules s-andb and s-orb. Rule gdsrright generalizes rules s-andc and s-orc. Rule gds-both generalizes rules s-anda ands-ora. In the three rules an operation A ♢? B is used:
A <:? B = A ∧ B
A :>? B = A ∨ BThis operation is used to choose between intersection or union types depending on the mode.
If the Duotyping mode is subtyping then we get a rule for intersection types, otherwise we
get a dual rule for union types.
Uniform and dual rules. In the context of Duotyping it is useful to distinguish between
two different kinds of rules: uniform rules and dual rules.
Uniform rules are those that are essentially the same for supertyping and subtyping. Rules gdsint and gds-arrow are uniform rules. In those rules the arguments of the relation are exactly the same no matter which mode is being used (subtyping or supertyping).
Dual rules are those that employ dual constructs, like the rules for top and bottom or therules for union and intersections. Rules gds-topbot, gds-lleft, gds-rright, and gdsboth are dual rules. The interesting point in these rules is that they use different (dual)
constructs depending on the mode. For example, when instantiated with subtyping and supertyping, respectively, the rule gds-topbot results in:

A <: ⊤

A :> ⊥

6.1.3 Implementations using Duotyping
Fig. 6.2 showed the declarative Duotyping rules for a calculus with union and intersection
types. All the rules are syntax directed, except for the duality rule (rule gds-dual). This rule
flips the mode and arguments to generate a formulation using the dual mode: i.e. it flips
subtyping to provide the equivalent supertyping formulation and vice versa. A benefit ofusing a formulation with the duality rule is that it enables a short specification of Duotyping.

107

6 The Duality of Subtyping (Duotyping)data Op = And | Ordata Typ = TInt | TArrow Typ Typ | TOp Op Typ Typ | TBot | TTopdata Mode = Sub | Sup
duo :: Bool -> Mode -> Typ -> Typ -> Bool
duo f m TInt TInt
= Trueduo f m _ b | b == mode_to_sub m= Trueduo f m (TArrow a b) (TArrow c d)=duo True (flip m) a c && duo True m b d
duo f m (TOp op a b) c | choose m == op = duo True m a c || duo True m b c
duo f m a (TOp op b c) | choose m == op = duo True m a b && duo True m a c
duo True m a b= duo False (flip m) b a
duo _ _ _ _= FalseFigure 6.3: Haskell code for implementing an algorithmic formulation of Duotyping rules.

Unfortunately the duality rule is not algorithmic, because the duality rule can always be applied indefinitely. In other words naively translating the rules into an program would easily
result in a non-terminating procedure. Therefore to obtain an algorithmic formulation some
additional work is needed.
Fortunately, for declarative formulations of Duotyping, there is a simple technique thatcan be used to obtain an algorithmic formulation. A key observation is that Duotyping only
needs to be flipped (with the duality rule) at most one time. Flipping the relation two or moretimes simply gets us back to the starting point. To capture this idea we can use a (boolean)
flag that keeps track of whether the procedure has already employed the duality rule or not.
To make such an idea concrete, Figure 6.3 shows Haskell code that implements a procedure
duo for determining Duotyping for two types. The code is based on the rules in Fig. 6.2, but
it uses a boolean flag to prevent the dual rule (the second to last case in duo) from being
applied indefinitely. The boolean is true in the initial call or recursive calls to structurally
smaller arguments. If the algorithm fails for the first five cases (which are basically a directtranslation of the rules gds-topbot, gds-int, gds-arrow, gds-lleft, and gds-rright),
then the algorithm simply flips the boolean flag, mode and arguments to run over a dual
formulation. This is the second to last line of the algorithm.
For example, if the algorithm is called with the mode set to subtyping and it is not able tofind any matching case with the first 5 rules, then it flips the boolean flag to False, subtyping
to supertyping and the arguments to check the equivalent supertyping formulation. If again
it fails to find a matching rule, False will be returned and the algorithm will terminate.
This illustrates that it is enough to flip the boolean flag once to exploit Duotyping. In all our
Coq formulations of Duotyping we have developed an alternative algorithmic formulation

1086.1 Overview
of Duotyping which uses an extra boolean flag and is shown to be sound and complete to thedeclarative formulations with the duality rule. In short there is an easy, general and provably
sound and complete way to implement algorithms based on the idea of Duotyping, while atthe same time retaining the benefits of reuse of the logic for rules for dual constructs.

6.1.4 Discovering new features
Duotyping can provide interesting extra features essentially for free. For example, the hallmark feature of the well-known F<: calculus (a polymorphic calculus with subtyping) [Canning et al. 1989] is bounded quantification, which is a feature used in most modern OOP
languages (such as Scala or Java). In F<: , bounded quantification allows type variables to be
defined with upper bounds. For example, the following Scala program illustrates the use ofsuch upper bounds:
class Person {def name: String = "person"}class Student extends Person {override def name: String = "student"
def id: String = "id"}class StudentsCollection[S <: Student](obj: S) {def student: S = obj}

The Scala program shown above uses the upper bounds for the class StudentsCollection written as S <: Student. This upper bound restricts StudentsCollection to be instantiated with
Student and its subtypes. Since the upper bound is Student, any class that is supertype ofStudent like Person cannot be instantiated in StudentsCollection.
However lower bounds are also useful, and indeed the Scala language allows them (thoughJava does not). One example of a program with lower bounds in Scala is:class GraduateStudent extends Student {def degree: String = "graduate degree"}class ResearchStudent extends GraduateStudent {override def degree: String = "research degree"}class CollectionExcludingResearchStudents[S >: GraduateStudent](obj: S) {109

6 The Duality of Subtyping (Duotyping)def student: S = obj}

In contrast to the upper bounds, the Scala program shown above uses the lower bounds forthe class CollectionExcludingResearchStudents written as S >: GraduateStudent. This lower
bound restricts CollectionExcludingResearchStudents to be instantiated with GraduateStudent and its supertypes. Since the lower bound is GraduateStudent. Any class that is a subtypeof GraduateStudent (such as ResearchStudent) cannot be instantiated in CollectionExcludingResearchStudents. But any supertype of GraduateStudent like Student and Person (including
GraduateStudent) can be instantiated in CollectionExcludingResearchStudents.
One can think of universal quantification with lower bounds as a dual to universal quantification with upper bounds. While there is no extension of F<: that we know of that
presents universal quantification with lower bounds in the literature, applying a Duotyping design to F<: gives us, naturally, the two features at once (lower and upper bounded
quantification).
Bounded quantification in F<: . The traditional subtyping rule of System kernel F<:
with upper bounded quantification is:
s-forallkfs

Γ, α <: A ⊢ B <: CΓ ⊢ (∀α <: A.B) <: (∀α <: A.C)
In the premise of this rule, we add the type variable α to the context with an upper bound
A. If under the extended context the bodies of the universal quantifier (B and C) are in a
subtyping relation then the universal quantifiers are also in a subtyping relation.
To add lower bounded quantification the obvious idea is to add a second rule:s-forallkfsb

Γ, α :> A ⊢ B <: CΓ ⊢ (∀α :> A.B) <: (∀α :> A.C)
However this alone is not quite right because the environment is also extended with a lower
bound (X >: A), which does not exist in F<: contexts. Therefore some additional care is alsoneeded for the variable cases of F<: extended with lower bounded quantification. When an
upper bounded constraint is found in the environment, the variable case needs to deal withthe upper bound appropriately. Since there are two rules dealing with the variable case in
F<: , one possible approach is to add two more rules for dealing with upper bounds:1106.1 Overview
s-TVarbs-ReflTvar

α :> A ∈ ΓΓ ⊢ B <: A

α :> A ∈ ΓΓ ⊢ B <: α

Γ ⊢ α <: αHowever such a design feels a little unsatisfactory. We need a total of 6 rules to fully deal
with lower and upper bounded quantification (instead of 3 rules in F<: ). At the same time
the rules are nearly identical, differing only on the kind of bounds that is used. Furthermore
the metatheory of F<: also needs to be significantly changed. In particular narrowing has tobe adapted to account for the lower bounds and transitivity has to be extended with several
new cases. Since both narrowing and transitivity proofs for F<: are non-trivial, this extension
is also non-trivial and adds further complexity to already complex proofs.
A variant of Kernel F<: with Duotyping. We now reconsider the design of Kernel
F<: from scratch employing the Duotyping methodology. In the subtyping rule for universal quantification, it is important to note that the subtyping relation between two universal
quantifiers in the conclusion is the same as the relation between types B and C in premise.
Similarly, the (subtyping/supertyping) bounds of type variable α in the conclusion are thesame as the bounds of type variable α in premise. In a design with Duotyping, we would liketo generalize the two uses of subtyping. Therefore, we can design a single unified rule withthe help of two modes:
gs-forallkfs

Γ, α ♢1 A ⊢ B ♢2 C
Γ ⊢ (∀α♢1 A.B) ♢2 (∀α♢1 A.C)
Section 6.3.1 explains the Duotyping rules of our Duotyping kernel F<: variant with union∧∨ in detail. Rule gs-forallkfs is the interesting case, capturing
and intersection types Fk♢

both upper and lower bounded quantification in an elegant way. This rule states that if ina well-formed context, type variable α has a ♢1 relation with type A and if type B has ♢2
relation with type C, then the universal quantification with body B has a ♢2 relation with theuniversal quantification with body C. Correspondingly there are also two Duotyping rules
for variables:
gs-TVaraα♢A ∈ Γgs-ReflTvar

Γ ⊢ A♢BΓ ⊢ α♢Bα♢1 A ∈ Γ
Γ ⊢ α♢αIn short, the design of a variant of F<: with Duotyping leads to a system that naturally accounts for both upper and lower bounded quantification. Moreover, the metatheory, and111

6 The Duality of Subtyping (Duotyping)A♢B(Algorithmic Duotyping)gs-topbtma

gs-topbtmb

gs-intA ♢ ⌉♢⌈⌉♢⌈ ♢ AInt ♢ Int

gs-arrow
A1 ♢ A2
B1 ♢ B2
A1 → B1 ♢ A2 → B2

Figure 6.4: The Duotyping relation for simply typed lambda calculus calculus.

in particular the proofs of narrowing and transitivity are not more complex than the corresponding original F<: proofs. In fact the proof of transitivity is significantly simpler, because
Duotyping enables novel proof techniques as we discuss next.

6.1.5 New proof techniques
Transitivity proofs are usually a challenge for systems with subtyping. This is partly because
subtyping relations often need to deal with some contravariance. For instance, the rule sarrow (in Figure 6.1) is contravariant on the input types. Such contravariance causes problems in certain proofs, including transitivity. To illustrate the issue more concretely, let’s
distill the essence of the problem by considering a simple lambda calculus with subtyping
called λ<: , where the types are:
Types

A, B

::=

⊤ | Int | A → B

and the subtyping rules for those types are just the relevant subset of the rules in Figure 6.1.
The transitivity proof for this simple calculus is:
Lemma 6.1 (λ<: Transitivity). If A <: B and B <: C then A <: C.
Proof. By induction on type B.
• Case ⊤ and case Int are trivial to prove by destructing the hypothesis in context.
• Case B1 → B2 requires inversion of the two hypotheses to discover that A can only bea function type, while C is either a function type or ⊤.

In the arrow case, we need to invert both hypotheses to discover more information about
A and C. For this very simply language this double inversion is not too problematic, but
as the language of types grows and the subtyping relation becomes more complicated, suchinversions become significantly harder to deal with.

1126.1 Overview
At this point one may wonder if the transitivity proof could be done using a different
inductive argument to start with, and thus avoid the double inversions. After all there arevarious other possible choices. Perhaps the most obvious choice is to try induction on thesubtyping relation itself (A <: B), rather than on type B. However this does not work
because of the contravariance for arrow types, which renders one of induction hypothesisin the arrow case useless (and thus do not allow the case to be proved). Other alternative
choices for an inductive argument (such as type A or C) do not work for similar reasons.
Developing metatheory with Duotyping. In order to develop metatheory with Duotyping it is convenient to use an equivalent formulation of Duotyping that eliminates the duality rule (which is non-algorithmic and makes inversions more difficult). For λ♢ , which isa Duotyping version of λ<: , this would lead to the set of rules in Figure 6.4. This alternativealgorithmic version eliminates the duality rule. Rules gs-topbtma, gs-int, and gs-arrow
are similar to the rules we discussed in Section 6.1.2. Rule gs-topbtmb is the dual rule ofrule gs-topbtma. With Rule gs-topbtmb, the duality rule is unnecessary.
Transitivity with Duotyping.

Now we turn our attention to the proof of transitivity:

Lemma 6.2 (λ♢ Transitivity). If A♢B and B♢C then A♢C.
Proof. By induction on A♢B.
• All cases are trivial to prove by destructing ♢ and inversion of the second hypothesis(B♢C).

Transitivity of systems with Duotyping can often be proved by induction on the subtyping relation itself. This has the nice advantage that all the cases essentially become trivial toprove (for λ♢ ) and only a single inversion is needed for arrow types. A key reason why suchapproach works in the formulation with Duotyping is that we can keep case for arrow types
covariant. Instead we only flip the mode. Another important observation is that when we
prove a transitivity lemma with Duotyping we are, in fact, proving two lemmas simultaneously: one lemma for transitivity of subtyping, and another one for transitivity of supertyping. When we use the induction hypothesis we have access to both lemmas (by choosing theappropriate mode).
The proof of the transitivity lemma by induction on the Duotyping relation can scale up
to more complex subtyping/Duotyping relations. This includes subtyping relations withadvanced features such as intersection types, union types, parametric polymorphism and113

6 The Duality of Subtyping (Duotyping)

Types
Terms
ValuesContext
Mode

A, B
e
v
Γ
♢

::=
::=
::=
::=
::=

⊤ | ⊥ | Int | A → B | A ∧ B | A ∨ B
x | i | λx : A.e | e1 e2
i | λx : A.e· | Γ, x : A<: | :>A♢B(Algorithmic Duotyping)gs-topbtma

gs-topbtmb

gs-intA ♢ ⌉♢⌈⌉♢⌈ ♢ AInt ♢ Int

gs-leftb
A♢B

gs-righta
B♢C(A ♢? B) ♢ C

gs-rightb
A♢CA ♢ (B ♢? C)

gs-arrow
B1 ♢ B2A1 ♢ A2
A1 → B1 ♢ A2 → B2

gs-lefta
A♢C(A ♢? B) ♢ C

gs-bothaA♢B
A♢CA ♢ (B ♢? C)

gs-bothbA♢C
B♢C

A ♢ (B ♢? C)(A ♢? B) ♢ C

Figure 6.5: Syntax and Duotyping relation for union and intersection types.

bounded quantification. All of these can follow the same strategy (induction on the Duotyping relation) to simplify the transitivity proof, as we shall see in Section 6.4.

6.2 The λ∧∨♢ calculus
In Section 6.1 we gave an overview and discussed advantages of using the Duotyping relation.
In this section we introduce a lambda calculus with union and intersection types that is basedon Duotyping. We aim at showing that developing calculi and metatheory using Duotyping
is simple, requiring only a few small adaptations compared with more traditional formulations based on subtyping. Our main aim is to show type soundness (subject-reduction andpreservation) for λ∧∨♢ .

6.2.1 Syntax and Duotyping
Syntax. Fig. 6.5 shows the syntax of the calculus. The types for λ∧∨♢ were already introduced in Section 6.1. Terms include all the constructs for the lambda calculus (variables
x, functions λx : A.e and applications e1 e2 ) and integers (i). Values are a subset of terms,
consisting of abstractions and integers only. The mode ♢ is used to choose the mode of therelation: it can be either subtyping (<:) or supertyping (:>). Typing contexts Γ are standard
and used to track the types of the variables in a program. Finally, a well-formedness relationΓ ⊢ ok ensures that typing contexts are well-formed.

114

6.2 The λ∧∨♢ calculus
Duotyping for λ∧∨♢ .

The Duotyping rules for λ∧∨♢ were already partly presented in Fig-ure 6.2. In addition to the rules in the λ♢ , we also need extra rules for union and intersection
types. These extra rules are presented in Figure 6.5. Rules gs-lefta, gs-righta, and gsbotha are also similar to the rules gds-lleft, gds-rright, and gds-both presented in
Figure 6.2. Since we eliminate the duality rule in the algorithmic version, we add dual subtyping rules. Rules gs-leftb, gs-rightb, and gs-bothb are the dual versions of rules gslefta, gs-righta, and gs-botha respectively. This formulation is shown to be sound andcomplete with respect to the formulation with the duality rule in Figure 6.2. As explained in
Section 6.1 this variant of the rules makes some proofs easier, thus we employ it here. The
Duotyping relation is reflexive and transitive:
Theorem 6.3 (Reflexivity). A♢A.
Proof. By induction on type A. Reflexivity is trivial to prove by applying subtyping rules.
Theorem 6.4 (Transitivity). If A♢B and B♢C then A♢C.
Proof. By induction on subtyping relation.
• Cases rule gs-topbtma, rule gs-int, rule gs-lefta, rule gs-righta and rule gsbotha are trivial to prove.
• Case rule gs-topbtmb requires an additional Lemma 6.5.
• Case rule gs-arrow requires induction on hypothesis and subtyping rules.
• Cases rule gs-leftb and rule gs-rightb requires an additional Lemma 6.6 to be applied on hypothesis in context.
• Case rule gs-bothb requires induction on the hypothesis. This case also requires
rule gs-leftb, rule gs-rightb, and rule gs-botha subtyping rules.

We used the following auxiliary lemmas to prove transitivity.
Lemma 6.5 (Bound Selection). If ⌉♢⌈♢B then A♢B.
This lemma captures the upper and lower bounds with respect to relation between two
types. If the mode is subtyping, then it states that any type that is supertype of ⊤ is supertypeof all the other types. If the mode is supertyping, then it states that any type that is subtypeof ⊥ is subtype of all the other types. In essence the lemma generalizes the following twolemmas (defined directly over subtyping and supertyping):

115

6 The Duality of Subtyping (Duotyping)Γ⊢e:A(Typing)

typ-varx:A ∈ Γ
Γ⊢x:A

typ-intΓ ⊢ i : Int

typ-abs
Γ, x : A ⊢ e : BΓ ⊢ λx.e : A → B

typ-appΓ ⊢ e1 : A → BΓ ⊢ e2 : AΓ ⊢ e1 e2 : B

typ-subΓ⊢e:A
A <: BΓ⊢e:Be1 −→ e2

(Reduction)
step-appl
e1 −→ e′1
e1 e2 −→ e′1 e2

step-appre −→ e′v e −→ v e′

step-dbeta(λx : A.e) v −→ e[x ⇝ v]

Figure 6.6: Typing and reduction for λ∧∨♢ .

• If ⊤ <: B then A <: B
• If ⊥ :> B then A :> B
Lemma 6.6 (Inversion for rule GDS-Both). If C ♢ (A ♢? B) then (C ♢ A) and (C ♢ B).
This lemma captures the relation between types with respect to the duality of union andintersection types. It is the general form of two lemmas:
Lemma 6.7 (Inversion for Union types). If (A ∨ B) <: C then (A <: C) and (B <: C).
Lemma 6.8 (Inversion for Intersection types). If C <: (A ∧ B) then (C <: A) and (C <: B).
Finally there is also a duality lemma, which complements reflexivity and transitivity:
Lemma 6.9 (Duality). A♢B = B♢A.
This lemma captures the essence of duality, and enables us to switch the mode of the relation by flipping the arguments as well. Furthermore, the duality lemma plays a crucial role
when proving soundness and completeness with respect to the declarative version of Duotyping, which has duality as an axiom instead. All of these lemmas are used in later proofs
for type soundness.

6.2.2 Semantics and type soundness
Typing. The first part of Fig. 6.6 presents the typing rules of λ∧∨♢ . The rules are standard.
Note that rule g-sub is the subsumption rule: if an expression e has type B and B is a subtype116

6.2 The λ∧∨♢ calculusof A then e has type A. Noteworthy, B <: A is the Duotyping relation being used with thesubtyping mode.
Reduction. At the bottom of Fig. 6.6 we show the reduction rules of λ∧∨♢ . Again, thereduction rules are standard. Rule step-dbeta is the usual beta-reduction rule, which substitutes a value v for x in the lambda body e. Rule step-appl and rule step-appr are thestandard call-by-value rules for applications.

Type soundness. The proof for type soundness relies on the usual preservation and progress
lemmas:Lemma 6.10 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then: Γ ⊢ e′ : A.
Proof. By induction on the typing relation and with the help of Lemma 6.9.
Lemma 6.11 (Progress). If Γ ⊢ e : A then:
1. either e is a value.
2. or e can take a step to e′ .
Proof. By induction on the typing relation.

6.2.3 Summary and Comparison
Besides λ∧∨♢ , which employs the Duotyping relation, we have also formalized a lambda calcu-

lus with union and intersection types using the traditional subtyping relation (λ∧∨<: ). Most of

the metatheory is similar with a great deal of theorems being almost the same. The main differences are in the metatheory for subtyping which has to be generalized. For example bothreflexivity and transitivity have to be generalized to operate in the Duotyping relation instead.
The formalization with Duotyping only has two additional lemmas (the duality lemma andthe bound selection lemma), which have no counterparts with subtyping. The number of∧∨lines of code for the formalization of λ∧∨<: is 596 whereas for λ♢ is 630. The total number of∧∨∧∨lemmas required for λ∧∨<: are 23 and 25 for λ♢ . Following two lemmas in λ<: are captured

as one lemma in λ∧∨♢ (Lemma 6.6):117

6 The Duality of Subtyping (Duotyping)
Inversion for Union Types. This lemma is stated as Lemma 6.12: it is the inversion of
the subtyping rule for the union types in the traditional subtyping relation. The lemma statesthat if the union of two types A and B is the subtype of a type C, then both types A and B are
subtypes of type C.
Lemma 6.12 (Inversion for Union types). If (A ∨ B) <: C then (A <: C) and (B <: C).
Inversion for Intersection Types. This lemma, which corresponds to Lemma 6.13, isthe inversion of the subtyping rule for the intersection types with the traditional subtyping
relation. It states that if a type C is the subtype of the intersection of two types A and B, then
the type C is a subtype of both types A and B.
Lemma 6.13 (Inversion for Intersection types). If C <: (A ∧ B) then (C <: A) and (C <: B).
∧∨
6.3 The Fk♢
calculus

In Section 6.2 we introduced a simple calculus with union and intersection types using Duotyping. This section extends that calculus with bounded quantification based on kernel F<: .
∧∨ . The main aim of this section is toThis new variant also employs Duotyping and is called Fk♢

show that sometimes we can get interesting and novel dual features come for free. In addition∧∨ provides lower bounded quantification
to upper bounded quantification of F<: , System Fk♢∧∨ .
as well. Additionally, we also show the type soundness of Fk♢

6.3.1 Syntax and Duotyping∧∨ . Types ⊤, ⊥, Int, A → B, A ∧ B,
Syntax. Fig. 6.7 shows the syntax of the calculus Fk♢

A ∨ B are already introduced in Section 6.1. Type variable α and a universal quantifier on∧∨ . Terms x, i, λx : A.e, e e aretype variables ∀(α♢A).B are the two additional types in Fk♢
1 2

already discussed in Section 6.2.1. Type abstraction Λ(α♢A).e and type application e A are∧∨ . Values are a subset of terms, consisting of term abstraction,
two additional terms in Fk♢

type abstraction and integers.
∧∨ .
Duotyping for Fk♢

Duotyping rules for a calculus with union and intersection types∧∨ has two significant differences in its Duotyping rules in comare presented in Fig. 6.4. Fk♢parison to Fig. 6.4, which are presented in Figure 6.7. The first one is the addition of a typing
context in the Duotyping rules. This is important to ensure that type variables are bound.
∧∨ is now of the form Γ ⊢ A ♢ B. The second difference is that there
Thus, Duotyping for Fk♢

are four more rules, three of them (rules gs-ReflTvara, gs-TVara, and gs-forallkfs)

118

∧∨ calculus
6.3 The Fk♢

Types
Terms
ValuesContext
Mode

A, B
e
v
Γ
♢

::=
::=
::=
::=
::=

⊤ | ⊥ | Int | A → B | A ∧ B | A ∨ B | α | ∀(α♢A).B
x | i | λx : A.e | e1 e2 | Λ(α♢A).e | e A
i | λx : A.e | Λ(α♢A).e· | Γ, x : A | Γ, α ♢ A<: | :>∧∨(Fk♢
Duotyping)Γ ⊢ A♢B
gs-topbtmap

gs-topbtmbp

gs-intpΓ ⊢ A ♢ ⌉♢⌈Γ ⊢⌉♢⌈ ♢ A

Γ ⊢ Int ♢ Int

gs-leftapΓ ⊢ A♢CΓ ⊢ (A ♢? B) ♢ C

gs-leftb
A♢B

gs-TVaraα♢A ∈ ΓΓ ⊢ A♢BΓ ⊢ α♢B

gs-bothbpΓ ⊢ A♢CΓ ⊢ B♢AΓ ⊢ B♢αΓ ⊢ A ♢ (B ♢? C)
gs-ReflTvaraΓ ⊢ okα♢1 A ∈ Γ
Γ ⊢ α ♢2 αΓ ⊢ B♢CΓ ⊢ (A ♢? B) ♢ C
gs-TVarbα♢A ∈ Γgs-rightbp
Γ ⊢ A♢C

gs-rightap
Γ ⊢ B♢CΓ ⊢ (A ♢? B) ♢ C

A ♢ (B ♢? C)

gs-bothapΓ ⊢ A♢BΓ ⊢ A♢CΓ ⊢ A ♢ (B ♢? C)

gs-arrowpΓ ⊢ B1 ♢ B2
Γ ⊢ A1 ♢ A2
Γ ⊢ A1 → B1 ♢ A2 → B2

gs-forallkfs
Γ, α ♢1 A ⊢ B ♢2 C
Γ ⊢ (∀α♢1 A.B) ♢2 (∀α♢1 A.C)∧∨
Figure 6.7: Syntax and additional rules for Duotyping in Fk♢.

were already explained in Section 6.1.4. Rule gs-TVarb is the dual of rule gs-TVara. We
introduce this rule to eliminate the duality rule.
∧∨ is reflexive and transitive as well:
The Duotyping relation for Fk♢

Theorem 6.14 (Reflexivity). Γ ⊢ A♢A.
Proof. By induction on type A.
Theorem 6.15 (Transitivity). If Γ ⊢ A♢B and Γ ⊢ B♢C then Γ ⊢ A♢C.
Proof. By induction on Γ ⊢ A♢B.
• Cases rule gs-topbtma, rule gs-topbtmb, rule gs-int, rule gs-ReflTvar, rule gsTVara, rule gs-lefta, rule gs-righta, rule gs-botha are trivial to prove.
• Case rule gs-arrow is proved using the induction hypotheses.
• Case rule gs-TVarb can be proved using Lemma 6.18.

119

6 The Duality of Subtyping (Duotyping)• Case rule gs-forallkfs is proved using the induction hypotheses.
• Case rule gs-leftb can be proved using an additional Lemma 6.17.
• Case rule gs-rightb also uses Lemma 6.17.
• Case rule gs-bothb is proved using the induction hypotheses.

The auxiliary lemmas for transitivity are described next and are essentially the same as
in Section 6.2.1.
Lemma 6.16 (Bound Selection). If Γ ⊢⌉♢⌈♢B then Γ ⊢ A♢B.
Lemma 6.17 (Inversion for rule GDS-Both). If Γ ⊢ C ♢ (A ♢? B) then Γ ⊢ (C ♢ A) and (C♢ B).
There is also a duality lemma:
Lemma 6.18 (Duality). Γ ⊢ A ♢ B = Γ ⊢ B ♢ A.
Finally, We also proved weakening and the narrowing lemmas for Duotyping calculus.
∧∨ and F ∧∨ :Here we briefly compare the narrowing lemma for Fk<:k♢∧∨ Narrowing Lemma). If Γ ⊢ A <: B and Γ, α <: B, Γ ⊢ C <: D thenLemma 6.19 (Fk<:1Γ, α <: A, Γ1 ⊢ C <: D∧∨ Narrowing Lemma). If Γ ⊢ A ♢ B and Γ, X♢ B, Γ ⊢ C♢ D thenLemma 6.20 (Fk♢1
1
12

Γ, X♢1 A, Γ1 ⊢ C♢2 D
Lemma 6.19 exploits only the subtyping relation while Lemma 6.20 exploits our Duotyping
relation. Lemma 6.20 illustrates how lower and upper bounds are captured under a unified
mode relation in narrowing. Like the transitivity statement using a Duotyping formulation,
one can think of the Duotyping narrowing lemma as actually two distinct lemmas: one fornarrowing of upper bounds and another for narrowing of lower bounds. Also, it is important
to note that Lemma 6.20 is using two modes ♢1 and ♢2 . ♢1 is the relation between types A,
B and the type variable X. Whereas, ♢2 is the relation between type C and type D. Those
two relations do not need to be the same.

120∧∨ calculus
6.3 The Fk♢Γ⊢e:A(Typing)g-var
Γ ⊢ okx:A ∈ Γ
Γ⊢x:A

g-int
Γ ⊢ okΓ ⊢ i : Intg-app
Γ ⊢ e1 : A1 → A2
Γ ⊢ e2 : A1
Γ ⊢ e1 e2 : A2
g-tabsΓ, α ♢ A ⊢ e : BΓ ⊢ Λ(α♢A).e : ∀(α♢A).B

g-abs
Γ, x : A1 ⊢ e2 : A2
Γ ⊢ λx : A1 .e2 : A1 → A2
g-subsΓ⊢e:BΓ ⊢ B <: AΓ⊢e:Ag-tappΓ ⊢ e : ∀(α♢A).BΓ ⊢ C♢AΓ ⊢ e C : B[ α ⇝ C]

e1 −→ e2

(Reduction)

gred-AppAbs(λx : A1 .e1 ) v2 −→ e1 [x ⇝ v2 ]gred-TAppTAbs(Λ(α♢A).e1 ) B −→ e1 [ α ⇝ B]

gred-Fune1 −→ e′1
e1 e −→ e′1 egred-Arg
e1 −→ e′1v e1 −→ v e′1
gred-TFun
e1 −→ e′1
e1 A −→ e′1 A

Figure 6.8: Typing and reduction of the duotyped kernel F<: .

6.3.2 Semantics and type soundness∧∨ . The first five rules are
Typing. The first part of Fig. 6.8 presents the typing rules of Fk♢

standard and are already explained in Section 6.1.1. Rules g-tabs and g-tapp are the two∧∨ . Rule g-tabs is similar to the standard rule for type abstractions
additional rules in Fk♢

in F<: except that it generalizes the subtyping bound to a ♢ bound, which could either besubtyping or supertyping. Rule g-app again differs from the rule for type applications in F<:
by using a ♢ bound instead of just a subtyping bound. These two rules rules are noteworthy
because they also illustrate an advantage of using Duotyping in the typing relation. Without
Duotyping we would need multiple typing rules to capture different variations of the bounds.

Reduction. The last part of Fig. 6.8 presents the reduction rules of our calculus. Again, reduction rules are standard except for the rule gred-TAppTAbs. In rule gred-TAppTAbs theduality relation captures both upper and the lower bounds. Rule gred-TFun is the standard
reduction rule for the type applications.

121

6 The Duality of Subtyping (Duotyping)Type Soundness. We proved the type soundness for our calculus. All the proofs are formalized in Coq theorem prover.
Lemma 6.21 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then: Γ ⊢ e′ : A.
Proof. By induction on the typing relation.
• Case rules g-var, g-int, g-abs, g-tabs, and g-subs are trivial to solve.
• Case rule g-app uses Theorem 6.14 and Lemma 6.18.
• Case rule g-tapp uses Theorem 6.14.

Lemma 6.22 (Progress). If Γ ⊢ e : A then:
1. either e is value.
2. or e can take step to e′ .
Proof. By induction on the typing relation.
• Case rules g-var, g-int, g-abs, g-tabs, and g-subs are trivial to solve.
• Case rule g-app requires canonical forms.
• Case rule g-tapp requires canonical forms.

6.3.3 Summary and Comparison∧∨ , which employs the Duotyping relation, we have also formalized a calculus
Besides Fk♢∧∨ : an extension of kernel FFk<:<: (only with upper bounded quantification) with union andintersection types using the traditional subtyping relation. The essential differences are similar to what we already discussed in Section 6.2.3. The formalization with Duotyping only
has two additional lemmas (the duality lemma and the bound selection lemma), besides a∧∨ is
few minor auxiliary lemmas. The number of lines for proof for the formalization of Fk<:∧∨ is 1770. The total lemmas required for F ∧∨ are 74 and 81 for F ∧∨ .
1648 whereas for Fk♢k<:k♢∧∨ and F ∧∨ is the additional lower
We emphasize that one significant difference between Fk<:k♢∧∨ . This is an extra feature which comes essentiallybounded quantification provided by Fk♢

for free with Duotyping.

1226.4 A Case Study on Duotyping

6.4 A Case Study on Duotyping
In this section we present an empirical case study, which we conducted to validate some of thebenefits of Duotyping. Overall, the results of our case study indicate that: Duotyping does
allow for compact specifications; the complexity of developing formalization with Duotyping is comparable to similar developments using traditional subtyping relations; transitivity
proofs are often significantly simpler; and Duotyping is a generally applicable technique.

6.4.1 Case Study
We formalized a number of different calculi using Duotyping. All the proofs and metatheory
are mechanically checked by the Coq theorem prover. We also formalized a few traditional
subtyping systems for comparison. Table 6.1 shows a brief overview of various systems that∧∨
we formalized. λ<: , λ∧∨<: , Fk<: , Fk<: and FF <: are the traditional subtyping systems. The

Coq formalizations for the traditional subtyping systems are based on existing Coq formalizations from the locally nameless representation with cofinite quantification tutorial
and repository (https://www.chargueraud.org/softs/ln/) by Charguéraud [Char∧∨guéraud 2011]. The formalizations of λ♢ , λ∧∨♢ , Fk♢ , Fk♢ and FF ♢ are their respective Duo-typing formulations, and modify the original ones with traditional subtyping. Subscript <:represents a calculus with traditional subtyping whereas ♢ represents a calculus with Duotyping. Superscript ∧∨ is the notation for a system with intersection and union types. Subscript k corresponds to the kernel version of a variant of F<: , while subscript F corresponds
to the corresponding full version. We also formalized a simple polymorphic system withoutbounded quantification using Duotyping. We have two Duotyping variants for this polymorphic type system without bounded quantification. One without union and intersection
types (F♢ ) and another with union and intersection types (F♢∧∨ ).
In Table 6.1, the last column (Transitivity) summarizes the proof technique used in eachsystem to prove transitivity. Recall the transitivity lemma (using the Duotyping formulation):
Theorem 6.23 (Transitivity). If A♢B and B♢C then A♢C.
Induction on the middle type means induction on type B (or well-formed type B for polymorphic systems), whereas induction on the Duotyping relation means induction on A♢B.
Research Questions. Section 1.2 discussed benefits of using Duotyping. This section
attempts to quantify some of these benefits. More concretely, we answer the following questions in this section:• Does Duotyping provide shorter specifications?

1236 The Duality of Subtyping (Duotyping)
Name
λ<:
λ♢

Description
STLC with subtyping
STLC with Duotyping

SLOC
537
583

λ∧∨
<:

STLC with subtyping, union
types and intersection types
STLC with Duotyping, union
types and intersection types
Simple polymorphic system with
Duotyping and without bounded
quantification
Simple polymorphic system with
Duotyping, union types andintersection types and withoutbounded quantification
System F<: kernel

595

1579

FF <:

System F<: kernel with Duotyping
System F<: kernel with subtyping, union types and intersection
types
System F<: kernel with Duotyping, union types and intersection
types
System full F<:

FF ♢System full F<: with Duotyping

1786

λ∧∨
♢
F♢
F♢∧∨

Fk<:
Fk♢
∧∨
Fk<:

∧∨
Fk♢

623
1466

Transitivity
By induction on the middle type.
By induction on the Duotyping
relation.
By induction on the middle type.
By induction on the Duotyping
relation.
By induction on the Duotyping
relation.

1546

By induction on the Duotyping
relation.

1542

By induction on the (wellformed) middle type.
By induction on the Duotyping
relation.
By induction on the (wellformed) middle type.

1648

1770

By induction on the Duotyping
relation.

1518

By induction on the (wellformed) middle type.
By induction on the (wellformed) middle type.

Table 6.1: Description of all systems.

• Does Duotyping increase the complexity of the formalization and metatheory of thelanguage?
• Does Duotyping make transitivity proofs simpler?
• Is Duotyping a generally applicable technique?
We follow an empirical approach to answer these questions and address each question in
a separate (sub)section. Obviously a precise measure for complexity/simplicity is hard to124

6.4 A Case Study on Duotyping
obtain. We use SLOC for the formalization and proofs as an approximation. All the formalizations are written in the same Coq style to ensure that the comparisons are fair.

6.4.2 Does Duotyping provide shorter specifications?
This section answers our first question. In short our case study seems to support this conclusion. The declarative Duotyping rules of all the systems that we formalized are shown in
Table 6.2. Please note that the formulation also contains the duality rule. λ♢ has the basic setof Duotyping rules. These rules are common in all of the systems. λ∧∨♢ has the subtyping rules
for intersection types and union types in addition to the rules from λ♢ . F♢ contains two morerules (rules gds-ReflTvarp and gds-forallfsp) in addition to the rules from λ♢ . F♢∧∨ has
all the rules from λ♢ , λ∧∨♢ and F♢ . Fk♢ has three additional subtyping rules gds-ReflTvar,∧∨ has all the rules fromgds-TVar, and gds-forallkfs in addition to the rules from λ♢ . Fk♢

λ♢ , λ∧∨♢ , and Fk♢ . FF ♢ has an additional subtyping rule gds-forallffs.

Comparison with systems using traditional subtyping. Table 6.3 shows the numberof rules and features for different calculi formulated with subtyping and Duotyping. In ourformulation, λ<: has 3 types ⊤, Int, and A → B. This requires 3 subtyping rules to capture
the subtyping relation of these 3 types. If we wanted to support the ⊥ type in λ<: we would
need to add 1 more subtyping rule. In the table we express the extra rules required for extra
features as (+n), where n is the number of extra rules. Duotyping supports ⊥ for free by
exploiting the dual nature of ⊤ with the help of duality rule. Systems with more rules follow
the same approach for traditional systems i.e more types require more subtyping rules. If
we wanted to support the ⊥ type in λ∧∨<: we also need 1 additional rule. To further extend
our discussion to the polymorphic systems with bounded quantification, we would need 4
additional rules in Fk<: (1 for ⊥ type and 3 for lower bounded quantification). Similarly wewould need 4 additional rules to support lower bounds and lower bounded quantification in
∧∨ .
Fk<:

In summary, in the systems that we compared Duotyping has a similar number of rules tosystems with subtyping, but it comes with extra features. If we wanted to add those features
to systems with traditional subtyping, then that would generally result in more rules for the
traditional versions compared to Duotyping. This would also have an impact in the SLOC ofthe metatheory, increasing the metatheory for those systems considerably.

125

6 The Duality of Subtyping (Duotyping)
Name Duotyping Rulesλ♢A♢B(λ♢ Duotyping)gds-arrowgds-topbot

gds-intA ♢ ⌉♢⌈

Int ♢ IntA1 ♢ A2

B1 ♢ B2

A1 → B1 ♢ A2 → B2

gds-dual

B♢A

A♢B

λ∧∨
♢
(λ∧∨
♢ Duotyping plus all rules from λ♢ )A♢B
gds-lleft

gds-rright(A ♢? B) ♢ C(A ♢? B) ♢ C

A♢C

gds-both

B♢C

A♢B

A♢C

A ♢ (B ♢? C)

F♢
A♢B(F♢ Duotyping plus all rules from λ♢ )gds-forallfsp

gds-ReflTvarp

A♢B

α♢α

(∀α.A) ♢ (∀α.B)

F♢∧∨(F♢∧∨ Duotyping plus all rules from λ♢ , λ∧∨♢ and F♢ )A♢BFk♢Γ ⊢ A♢B(Fk♢ Duotyping plus all rules from λ♢ )gds-ReflTvar

Γ ⊢ okα♢1 A ∈ Γ

gds-TVarα♢A ∈ ΓΓ ⊢ α ♢2 αΓ ⊢ A♢BΓ ⊢ α♢B

gds-forallkfs

Γ, α ♢1 A ⊢ B ♢2 C

Γ ⊢ (∀α♢1 A.B) ♢2 (∀α♢1 A.C)∧∨Fk♢

Γ ⊢ A♢B∧∨ Duotyping plus all rules from λ , λ∧∨ and F )
(Fk♢
♢ ♢
k♢

FF ♢
Γ ⊢ A♢B(FF ♢ Duotyping plus all rules from Fk♢ excluding
rule gs-forallkfs and union/intersection rules)
gds-forallffs

Γ ⊢ A | ♢1 | ♢2 B
e 2 B) ⊢ A1 ♢2 B1
Γ, α ♢1 (A ♢

Γ ⊢ (∀α♢1 A.A1 ) ♢2 (∀α♢1 B.B1 )126
Table 6.2: Declarative Duotyping rules of all systems.

6.4 A Case Study on Duotyping
System Subtyping
rules countλ<:3 (+1)
λ∧∨9 (+1)<:
Fk<:
5 (+4)

System Duotyping
rules count
λ♢
4
λ∧∨
7
♢
Fk♢
7

∧∨
Fk<:

∧∨
Fk♢

11 (+4)10

Duotyping extra features
lower bounds in λ♢lower bounds in λ∧∨♢lower bounds and lower
boundedquantificationinFk♢lower bounds and lower
boundedquantification
in
∧∨
Fk♢

Table 6.3: Comparing the features and number of rules with subtyping and Duotyping.

Subtyping
System
λ<:
λ∧∨
<:
Fk<:
∧∨
Fk<:

SLOC
537
595
1542
1648

Duotyping
System
λ♢
λ∧∨
♢
Fk♢
∧∨
Fk♢

SLOC
583
623
1579
1770

Table 6.4: SLOC of traditional subtyping and Duotyping systems.

6.4.3 Does Duotyping increase the complexity of the formalizationand metatheory of the language?

At first, one may think that Duotyping increases the complexity of formalization and metatheory of the language, since it provides interesting extra features and generalizations normallycome at a cost. Interestingly, Duotyping does not add significant extra complexity in theformalization and metatheory of the language. Table 6.4 shows the SLOC for formalizations
using traditional subtyping and Duotyping systems. The lines of code for λ∧∨<: are 595 and∧∨the lines of code for λ∧∨♢ are 623. Similarly, the lines of code for Fk<: are 1648 and 1770 for∧∨ . Although SLOC for Duotyping systems are slightly more than traditional subtyping
Fk♢systems, the Duotyping systems come with extra features. Nevertheless the mechanization
effort is roughly the same for version with and without Duotyping. Also, as illustrated in
Sections 6.2 and 6.3, the vast majority of the lemmas/metatheory for calculi with Duotyping
are similar to traditional systems with subtyping.

127

6 The Duality of Subtyping (Duotyping)
Subtyping
System
λ<:
λ∧∨
<:
Fk<:
∧∨
Fk<:

Transitivity
SLOC
7
13
26
38

Duotyping
System
λ♢
λ∧∨
♢
Fk♢
∧∨
Fk♢

Transitivity
SLOC
4
11
13
18

Table 6.5: SLOC for transitivity proofs.

6.4.4 Does Duotyping make transitivity proofs simpler?
Transitivity is often the most difficult property to prove in the metatheory of a language with
subtyping. Table 6.1 highlights a brief comparison between the techniques for the transitivity
proof of various systems. Transitivity of systems with Duotyping is generally proved by induction on the Duotyping relation. One exception is FF ♢ where induction on the Duotyping
does not work. As discussed in Section 6.1.5 Duotyping allows us to simplify the transitivity
proof by using a different inductive argument.
Table 6.5 shows the SLOC for transitivity proofs of various systems. The SLOC for λ<: transitivity∧∨ transitivity
proof are 7 and the SLOC for λ♢ transitivity proof are 4. Similarly, the SLOC for Fk<:∧∨ . This evaluation shows that Duotyping
proof are 38 and 18 for the transitivity proof of Fk♢

always allows us to reduce the size of the transitivity proof. Again, it is important to notethat Duotyping also provides extra features of lower bound and lower bounded quantification. Despite these additional features in Duotyping systems, their transitivity proofs areshorter than the traditional systems with subtyping.
However we could not employ this proof technique in our Duotyping version of full F<:(FF ♢ ). The problem is related to narrowing, which in FF ♢ is closely coupled with transitivity.
Despite that we could still apply the technique to most systems with Duotyping, and evenfor FF ♢ we can still prove transitivity using the same technique as in the traditional F<: (i.e.
using the middle type as the inductive argument).

6.4.5 Is Duotyping a generally applicable technique?
Our case studies indicate that Duotyping is a generally applicable technique. In all the systems that we have tried to use Duotyping, we have managed to successfully apply it. Furthermore we believe that Duotyping can be essentially applied to any system with a traditional subtyping relation. The most complex system where we have employed Duotyping
is FF ♢ . In FF ♢ universal quantification allows Duotyping between the bounds, generalizing

1286.4 A Case Study on Duotyping
the universal quantification presented in Section 6.3. Rule gds-forallffs in FF ♢ employse B:
two operations |♢1 |♢ and A ♢2|♢1 |♢2| <: |♢2 = ♢2eBA♢

| :> |♢2 = ♢2
f: B = B
A<
A :f
>B= A

|♢1 |♢2 takes two modes ♢1 and ♢2 as input, and flips ♢2 if ♢1 is subtyping, otherwise it
returns ♢2 . This operation chooses the mode to check the relationship between the boundseBof the two universal quantifiers being compared for Duotyping. The second operation A ♢selects the bounds to use in the environment when checking the Duotyping of the bodiesof the universal quantifiers. It takes a mode ♢ and two types A B as inputs, and returns thesecond type if the mode is subtyping, otherwise it returns the first type.

129

Part IV
Related Work131

7 Related Work
7.1 Union Types
Union types were first introduced by MacQueen et al. [1984]. They proposed a typing rulethat eliminates unions implicitly. The rule breaks type preservation under the conventional
reduction strategy of the lambda calculus. Barbanera et al. [1995] solved the problem byreducing all copies of the same redex in parallel. Dunfield and Pfenning [2003] and Dunfield[2014] took another approach to support mutable references. They restricted the elimination
typing rule to only allow a single occurrence of a subterm with a union type when typing
an expression. Pierce [1991] proposed a novel single-branch case construct for unions. As
pointed by Dunfield and Pfenning, compared to the single occurrence approach, the only
effect of Pierce’s approach is to make elimination explicit.
Union types and elimination constructs based on types are widely used in the context of
XML processing languages [Benzaken et al. 2003; Hosoya and Pierce 2003], and have inspired proposals for object oriented languages [Igarashi and Nagira 2006]. Generally speaking, the elimination constructs in those languages offer a first-match semantics, where cases
can overlap and reordering the cases may change the semantics of the program. This is incontrast to our approach. Union types have also been studied in the context of XDuce programming language [Hosoya and Pierce 2003]. XDuce employs regular expression types.
Pattern matching can be on expressions and types in XDuce. Expressions are considered as
special cases of types. CDuce [Benzaken et al. 2003] is an extension of XDuce. Work on the
more foundational aspects of CDuce, and in particular on semantic subtyping [Frisch et al.
2002] and set-theoretic types, also employs a form of first-match semantics elimination construct, though in a different form. In particular, work by Castagna and Frisch [2005] andCastagna and Lanvin [2017] propose a conditional construct that can test whether a value
matches a type. If it matches then the first branch is executed and the type for the value isrefined. Otherwise, the second branch is executed and the type of the value is refined to be
the negation of the type (expressing that the value does not have such type). Union types
are also studied in the context of semantic subtyping and object-oriented calculi [Ancona
and Corradi 2014, 2016; Dardha et al. 2013] which focus on designing subtyping algorithms

133

7 Related Work
to employ semantic subtyping in OOP. In contrast, we study a deterministic and type-safe
switch construct for union elimination.
Muehlboeck and Tate [2018] give a general framework for subtyping with intersection
and union types. They illustrate the significance of their framework using the Ceylon programming language. The main objective of their work is to define a generic framework forderiving subtyping algorithms for intersection and union types in the presence of various
distributive subtyping rules. For instance, their framework could be useful to derive an algorithmic formulation for the subtyping relation presented in Figure 3.5. They also briefly
cover disjointness in their work. As part of their framework, they can also check disjointness given some disjointness axioms. For instance, for λu , such axioms could be similar torule ad-btmr or rule ad-intl in Figure 3.2. However, they do not have a formal specification of disjointness. Instead they assume that some sound specification exists and that theaxioms respect such specification. If some unsound axioms are given to their framework(say Int ∗a Int) this would lead to a problematic algorithm for checking disjointness. In ourwork we provide specifications for disjointness together with sound and complete algorithmic formulations. In addition, unlike us, they do not study the semantics of disjoint switch
expressions.
Occurrence Typing. Occurrence typing or flow typing [Tobin-Hochstadt and Felleisen2008] specializes or refines the type of variable in a type test. An example of occurrence
typing is:Integer occurrence (Integer | String val) {if (val is Integer) { return val+1; }else{ return toInt(val)+2; }}In such code, val initially has type Int ∨ String. The conditional checks if the val is of type Int. If
the condition succeeds, it is safe to assume that val is of type Int, and the type of val is refinedin the branch to be Int. Otherwise, it is safe to assume that val is of type String, in the otherbranch (and the type is refined accordingly). The motivation to study occurrence typing was
to introduce typing in dynamically typed languages. Occurrence typing was further studied by Tobin-Hochstadt and Felleisen [2010], which resulted into the development of Typed
Racket. Variants of occurrence typing are nowadays employed in mainstream languages suchas TypeScript, Ceylon or Flow. Castagna et al. [2019] extended occurrence typing to refine
the type of generic expressions, not just variables. They also studied the combination with
gradual typing. Recently, Castagna et al. [2022] show that the classical union and intersection
types along with a type-based switch construct encompasses occurrence typing. Occurrence1347.2 Disjoint Intersection Types
typing in a conditional construct, such as the above, provides an alternative means to eliminate union types using a first-match semantics. That is the order of the type tests determines
the priority.
Nullable Types. Nullable types are types which may have the null value. Recently, Nieto
et al. [2020] proposed an approach with explicit nulls in Scala using union types. The Ceylon language has implemented a similar approach for a few years now. However our’s andCeylon’s approaches are based on disjoint switches to test for nullability, while Nieto et al.
[2020]’s approach is based on a simplified form of occurrence typing.
Various approaches have been proposed to deal with nullability such as T? in Kotlin [Kotlin
2021], Swift [Apple 2021] and Flow [Chaudhuri et al. 2017]. The Checker Framework [Papiet al. 2008] is another line of related work to detect null pointer deferences in Java programs.
Banerjee et al. [2019] proposes an approach to explicitly associate nullable and non-nullable
properties with expressions in Java. However, differently from our work, in those approaches
nullable types are not encoded with union types. Blanvillain et al. [2022] study a notion ofmatch types for type level programming. They also employ a notion of disjointness in match
types and can encode nullable types. However, they provide match types at the type leveland do not use them for union elimination. Furthermore, they do not study intersection
and union types formally. In contrast, we provide a term level switch construct for union
elimination.

7.2 Disjoint Intersection Types
Disjoint intersection types were first studied by Oliveira et al. [2016] in the λi calculus to givea coherent calculus for intersection types with a merge operator. The notion of disjointness
used in λu , discussed in Section 3.2, is inspired by the notion of disjointness of λi . In essence,
disjointness in λu is the dual notion: while in λi two types are disjoint if they only have toplike supertypes, in λu two types are disjoint if they only have bottom-like subtypes. Disjoint
polymorphism [Alpuim et al. 2017] has been studied for calculi with disjoint intersection
types.
None of calculi with disjoint intersection types [Alpuim et al. 2017; Bi et al. 2018b, 2019;
Oliveira et al. 2016] in the literature includes union types. One interesting discovery of ourwork is that the presence of both intersections and unions in a calculus can affect disjointness. In particular, as we have seen in Section 3.3, adding intersection types required us tochange disjointness. The notion of disjointness that was derived from λi stops working in
the presence of intersection types. Interestingly, a similar issue happens when union types

135

7 Related Workare added to a calculus with disjoint intersection types. If disjointness of two types A and B isdefined to be that such types can only have top-like types, then adding union types immediately breaks such definition. For example, the types Int and Bool are disjoint but, with union
types, Int ∨ Bool is a common supertype that is not top-like. We conjecture that, to add union
types to disjoint intersection types, we can use the following definition of disjointness:
Definition 10. A ∗ B ::= ∄ C ◦ , A <: C ◦ and B <: C ◦ .
which is, in essence, the dual notion of the definition presented in Section 3.3. Under thisdefinition Int and Bool would be disjoint since we cannot find a common ordinary supertype(and Int ∨ Bool is a supertype, but it is not ordinary). Furthermore, there should be a dual
notion to LOS, capturing the greatest ordinary supertypes. Moreover, if a calculus includes
both disjoint switches and a merge operator, then the two notions of disjointness must coexist
in the calculus. This will be an interesting path of exploration for future work.

7.3 Overloading and Dynamic Dispatch
Overloading. Union and intersection types also provide a form of function overloadingor ad-hoc polymorphism using the switch and type-based case analysis. A programmer may
define the argument type to be a union type. By using type-based case analysis, it is possible
to execute different code for each specific type of input. Intersection types have also beenstudied for function overloading. For example, a function with type Int → Int ∧ Bool → Boolcan take input values either of type Int or Bool. In such case, it returns either Int or Bool depending upon the input type. Function overloading [Cardelli and Wegner 1985; Castagna
et al. 1995; Wadler and Blott 1989] has been studied in detail in the literature. Wadler andBlott [1989] studied type classes as an alternative way to provide overloading based on parametric polymorphism. Recently, Rioux et al. [2023] studied function overloading with intersection types, merge operator, and the union types in a calculus called F▷◁ . Their calculus
is type-safe and deterministic. However, the merge operator in their calculus is restricted tofunctions. Moreover, the F▷◁ calculus is proposed after Rehman et al. [2022].
Dynamic dispatch. A straightforward definition of dynamic dispatch [Bourdoncle andMerz 1997; Castagna et al. 1995, 2014b; Clifton et al. 2006] is the runtime function overloading. In contrast to the static overloading, a specific function implementation is selectedbased on the the dynamic or runtime type of the one (usually left) or more argument(s) in
dynamic dispatch. This division categorizes the dynamic dispatch into single and multiple
dispatch respectively. Single dispatch comes naturally with most of the OOP languages such
as C++ [Stroustrup 1986] and Smalltalk [Goldberg and Robson 1983]. Multiple dispatch

136

7.4 Duality in Logic and Programming Language Theory[Bourdoncle and Merz 1997; Castagna et al. 1995, 2014b], on the other hand, requires special care. Normally, types overlap in multiple dispatch and the language implementation
selects the best matching function implementation based on the argument type such as in
Julia [Zappa Nardelli et al. 2018]. Recently, Park et al. [2019] study symmetric dynamic dispatch with multiple inheritance, parametric polymorphism, and variance. However, this lineof work differs from ours in a way that we do not allow overlapping types in λu in the alternative branches of a switch expression. Thus λu provides a less expressive but deterministic
variant of dynamic dispatch.

7.4 Duality in Logic and Programming Language Theory
Apart from informally observing duality of type system features, as far as we known, formally
exploiting duality in subtyping relations has not been investigated in the past. However thereis plenty of work on uses of duality in programming language theory. Furthermore there isrelated work on type systems that exploit various generalizations for added expressive poweror economy in metatheory and implementation. We discuss these next.
In type theory [Andrews 1986] and/or category theory [Bird and de Moor 1996; Lane1998] duality occurs in various forms. For instance, the duality between sum and producttypes is well-known in both type and category theory. Properties about such types often explicitly acknowledge duality. Many properties about sum types are presented as dual properties of corresponding properties on product types and vice-versa. Our Lemma 6.6 is an
example of a property that applies to both union and/or intersection types. In this property
duality is not only acknowledged, but directly exploited in the lemma itself to provide a generalized property that can be specialized to one construct and its dual. Various other dualities
between constructs are known and exploited in various ways in type and/or category theory.
For example, existential and universal quantification can be captured by an encoding by one
through the other. The type ∃α. A can be encoded as ∀β.(∀α. A → β) → β, which requiresa kind of CPS translation [Danvy and Filinski 1992] of the corresponding terms. Similar
encodings exist for sums and products.
In the field of proof-theoretic semantics [Gentzen 1934] and in natural deduction the concept of harmony is used to describe introduction and elimination rules that are in some sensedual. For instance, the usual rules for introduction and elimination of conjunction are in perfect harmony. The inversion principles by Prawitz [1979] are a general procedure to associate
to any arbitrary collection of introduction rules a specific collection of elimination rules. The
elimination rules are in harmony with the given collection of introduction rules. Prawitz inversion principles attempt to capture harmony in a more precise way, directly expressing it

137

7 Related Work
formally. Therefore inversion principles have similar considerations to Duotyping in termsof expressing some form of duality directly in a formalism. However inversion principles
focus on introduction and/or elimination rules, while Duotyping is focused on subtyping.
Nevertheless in future work we are interested in exploiting the use of duality in the typing
relation more. We believe that the notion of harmony and inversion principles could be quite
helpful in such work.
Double-line rules [Došen 1989] are deduction rules that can be read both from top to bottom (as usual) and also from bottom to top. In other words they express two standard (dual)
deduction rules in a single double-line rule. Like Duotyping, double-line rules aim at expressing a form of duality in a single rule. Unlike Duotyping, double-line rules are concernedwith (dual) rules where the premises and conclusions of one rule become the conclusions andpremises of the other rule, respectively.
Bernardi et al. [2014] explain duality relations in the context in session types. Binary session types have two endpoints connected through one communication channel. In session
types, connected endpoints should have a dual relation in their session types. The duality
relation in session types is related to types and may have various interpretations. In contrast
Duotyping is about subtyping (or supertyping).
The duality between data and codata is well-known in programming language theory [Birdand de Moor 1996]. Data types and codata types are duals in the sense that data types aredefined in terms of constructors while codata types are defined in terms of destructors. Morerecently, such duality has been exploited in language design [Binder et al. 2019; Ostermann
and Jabs 2018] to provide an automatic way to switch between programs defined on datatypes
and equivalent programs defined on codata types. The use of duality in this line of work isquite different from ours.

7.5 Generalizations in Type Systems and Type Theory
Pure type systems (PTSs) [Adams 2006; Jutting 1993; McKinna and Pollack 1993; Severi andPoll 1994; van Benthem Jutting et al. 1993; Zwanenburg 1999] capture a generalization ofvarious type systems (F, F ω , λP). Typing rules of multiple type systems are expressed in puretype systems via parameterization. PTSs are parameterized by three sets: a set of sorts; a setof axioms; and a set of rules. Concrete type systems (such as System F ), are recovered withconcrete instantiations of those sets. Pure type systems with subtyping [Zwanenburg 1999]
are a variant of pure type systems that captures a family of type systems with subtyping.
This variant captures only the upper bounds. It does not provide subtyping generalization
with both upper and the lower bounded quantification like our Duotyping generalizations

1387.5 Generalizations in Type Systems and Type Theory
of F<: . Pure subtype systems [Hutchins 2010] is a family of calculi based on subtyping only
(and without a typing relation). This system eliminates the need of typing and presents analternative to typing using subtyping only. Pure subtype systems support upper bounded
quantification, but no lower bounded quantification.
Modal Type Theory. Modal type theory [Nanevski et al. 2008] is an extension of type theory which provides type rules using modalities. Modal type theory can represent a proposition as types which may be proved based upon the deduction rules in a given context. Modal
type theory also employs modes, for instance possibility and necessity [Nanevski et al. 2008;
Simpson 1994]. There are many type systems that use modes to generalize typing relations.
One can view Duotyping as a simple instance of a relation with a mode. In Duotyping themode is either subtyping or supertyping.
Bi-directional type checking.

Bi-directional type checking [Dunfield and Krishnaswami

2019; Pierce and Turner 2000] also employs a mode, but in the typing relation instead. Bidirectional type checking is a common technique, used in implementations of programming
languages, that can eliminate redundant type annotations. Bi-directional type-checking is
also employed is several type systems, especially those where full type inference is undecidable [Dunfield and Krishnaswami 2013; Pierce and Turner 2000]. In such cases only partial
inference methods are feasible in practice, which means that some type annotations are necessary. Bi-directional type checking is useful in such cases, allowing the type information tobe easily propagated without requiring further (redundant) annotations. The modes in bidirectional type-checking are checking or synthesis. Checking checks a given term against agiven type, whereas the synthesis infers the type based upon the available information in thecontext.
Unified Subtyping. Unified subtyping [Yang and Oliveira 2017] is a technique that canbe used in dependently typed systems supporting unified syntax to model typing and subtyping in a single relation. The single unified subtyping relation generalizes both typing andsubtyping. Like Duotyping, unified subtyping can also help reducing language metatheory
and duplication. However unified subtyping is orthogonal to Duotyping and does not exploit
duality of features. We believe that both techniques can complement each other.
Bounded quantification and generalizations. System F<: [Cardelli et al. 1994] isextensively studied due to its feature of bounded quantification. F-bounded quantification[Canning et al. 1989] is a generalization of bounded quantification to handle recursive types.

139

7 Related Work
Although we are not aware of an extension of F<: with lower bounded quantification, such
notion has appeared before in some calculi. For instance, Igarashi and Viroli [2002] havepointed out correspondence between use-site variance and existential types and, in order tocapture contravariance, they introduced lower-bounded existential types.
One generalization of F<: is studied by Amin and Rompf [2017], which formalizes typebounds in Scala. Type bounds is an interesting feature in Scala as elaborated by the following
code (code extended from Section 6.1.4):class TypeBoundsCollection[S >: GraduateStudent <: Student](obj: S) {def student: S = obj}

While in our variants of F<: we support either lower bounded quantification or upper bounded
quantification (but not both at once), Scala’s type bounds allow both upper and lower bounds
at once. This is clearly more expressive than what we have, but it comes with its own problems. Formalisms with Scala-like type bounds often need to include a transitivity axiom (andthus are non-algorithmic) and they have to deal with the bad bounds problem. In contrast
our simpler extension of type bounds is comparable in complexity to F<: ’s upper bounded
quantification, and there is a set of algorithmic subtyping rules without a built-in transitivity
axiom.
Duality in Subtyping of Intersection and Union Types. We exploit the duality ofunion and intersection types to illustrate Duotyping. Our Duotyping calculi manages tocapture the six common rules for unions and intersections using three rules only (plus theduality rule), which provides a simple illustrative example of the use of duality. None of
the calculi with intersection and union types discussed so far study the duality of subtyping
formally as we do.

140Part V
Epilogue141

8 Conclusion and Future Work
8.1 Conclusion
The integration of the intersection and union types is known to be non-trivial in theory. This
thesis examines the integration of intersection and union types in various settings. Chapter 3 (λu ) discusses the deterministic elimination of union types with a type-based switch
construct. The disjointness plays the essential role in making such a construct deterministic.
The disjointness ensures that no two types in alternative branches overlap i.e. do not share acommon subtype. Therefore, the type of the scrutinee matches with one branch at the most.
Later sections in Chapter 3 further enrich λu with advance features including intersection
types, nominal types, subtyping distributivity and disjoint polymorphism. Chapter 4 discusses an expressive version of the disjointness algorithm with disjoint polymorphism. Allof the calculi discussed in Chapter 3 and Chapter 4 preserve the standard properties of typesafety and determinism.
Chapter 5 studies a calculus (λum ) with an elimination construct for the union types andan introductory construct for the intersection types, the so called merge operator. λum is
type-safe but lacks determinism. We also prove the completeness of λum with respect to
Dunfield [2014]. Chapter 6 discusses Duotyping, a novel technique studied to unify thesubtyping rules for the dual features such as intersection types and union types. Duotyping comes with certain benefits of reduced subtyping rules, easier proofs and metatheory,and extra features such as lower bounded quantification. We study Duotyping with various
calculi to show that it is a practically applicable technique.

8.2 Future Work8.2.1 Determinism for λum
Future work includes making the calculus discussed in Chapter 5 (λum ) deterministic. We
discuss a few proposals to make λum deterministic. Approaches include restricting subsumption rule to prohibit certain ambiguous upcasts, first-match semantics, and parallel application.

143

8 Conclusion and Future Work
Proposal 1: restricting ambiguous upcasts
The meticulous observation concludes that the origin of non-determinism is primarily due
to multiple upcast paths. For example, 1‚‚true can follow two paths to upcast to Int ∨ Bool.
One is via Int and the other is via Bool. Our first proposal to deal with non-determinism isto reject the programs when there are multiple upcast paths involved. In this approach thefollowing program will be rejected:
Bool isInt (x : Int | Bool) = switch (x)(x:Int) → true
(y:Bool) → false
isInt(1,,true) //rejected

We propose restricting subsumption rule with an extra condition so that it allows only unambiguous upcasts along with disjointness in merges and switches. Such a calculus will havethree measures for determinism. One is the disjointness in switches, another is the disjointness in merges, and finally an extra restriction in subsumption rule to reject ambiguous upcasts. The revised subsumption rule is shown next:Γ⊢e:A

A <: BA <u BΓ⊢e:B

typ-sub-res

Where, A <u B indicates type A is unambiguous to type B. Meaning that there is at mostone path to upcast from A to B. Ideally, this relation allows upcasting 1 : Int to 1 : Int ∨ Bool.
But it does not allow upcasting 1‚‚true : Int ∧ Bool to 1‚‚true : Int ∨ Bool. This is because
1 : Int upcasts to 1 : Int ∨ Bool via only one path i.e.:Γ ⊢ 1 : Int

Int <: Int ∨ BoolΓ ⊢ 1 : Int ∨ Bool

typ-sub

Whereas, 1‚‚true has two paths to upcast to Int ∨ Bool, one via Int and the other via Bool.

1) First path: Upcast of 1‚‚true to Int ∨ Bool via Int:144

8.2 Future WorkInt <: Int
typ-merga
typ-sub

Int ∧ Bool <: Int
1‚‚true : Int ∧ Bools-andbInt ∧ Bool <: Int ∨ Bools-orb

Γ ⊢ 1‚‚true : Int ∨ Bool

2) Second path: Upcast of 1‚‚true to Int ∨ Bool via Bool:

Bool <: Bool
typ-merga
typ-sub

Int ∧ Bool <: Bool1‚‚true : Int ∧ Bools-andcInt ∧ Bool <: Int ∨ Bools-orc

Γ ⊢ 1‚‚true : Int ∨ Bool

Therefore the upcast of 1‚‚true to Int ∨ Bool is rejected with the restricted subsumption rule.
The essence of restricted subsumption rule is to reject the programs that may be a cause ofnon-determinism in switch expression.

Challenges with unambiguous relation. Restricting multiple upcasts with unambiguous relation may assist to make the calculus deterministic but unambiguous relation
itself is non-trivial in metatheory. Specifically, it becomes challenging with multiple type
annotations with functions. For example, λx.e : Int → Int ∧ Bool : Int → Int : Int →Int ∨ Bool is deterministic as long as we carry the middle type i.e Int → Int. As soon as
we drop the middle type, application of such lambdas may become non-deterministic. The
contravariance of function input type adds further complexities. Possible remedies in suchsituations is to carry a list of type annotations with functions.

Proposal 2: first-match semantics
A natural approach to select a particular branch of a switch expression is to follow the firstmatch semantics, meaning that first branch that matches the type of scrutinee will be selected.
In this remedy we propose to keep disjointness in merges but eliminate disjointness fromswitches. Merges will be deterministic as in disjoint intersection types Oliveira et al. [2016].
Whereas, the switches will follow the first-match semantics.

145

8 Conclusion and Future WorkFor example, with this approach, first branch will be selected in the following code. This isbecause 1‚‚true can be considered as a value of type Int and the type of scrutinee i.e Int∧Bool
matches (is a subtype of) Int.
Bool isInt (x : Int | Bool) = switch (x)(x:Int) → true
(y:Bool) → false
isInt (1,,true)

However, first-match semantics selects first branch in following code as well because of thesame reason but with type Bool in first branch.
Bool isInt (x : Int | Bool) = switch (x)(x:Bool)(y:Int)→ false→ trueisInt (1,,true)

The semantics of the program may change by reordering the branches in this approach. The
first code snippet returns true, whereas the second code snippet returns false.

Proposal 3: parallel application
Another proposal is to adopt the parallel application. In this approach, the code in all of
the branches will be executed to which the type of scrutinee matches. Final result will be amerge of the values from all of the (match) branches. For example, the result of the following
program will be (false‚‚true).
Bool isInt (x : Int | Bool) = switch (x)(x:Bool)(y:Int)→ false→ trueisInt (1,,true)

Challenge with parallel application. However, on of the challenges in naive implementation of this approach is that it may generate ill-typed programs as in the above example.
The return value (false‚‚true) is an ill-typed program in disjoint intersection types. This isbecause (false‚‚true) is of type Bool ∧ Bool and Bool is not disjoint to Bool. If we allow
type-checking such programs, the overall calculus will still be non-deterministic due to theambiguous merge operator. We further propose two approaches to deal with such an issue.

1468.2 Future Work
Disjoint return type of branches. One approach to solve this problem is to employ
disjointness in the return type of alternative branches. For example, the following problematic program will be rejected in this case:
Bool isInt (x : Int | Bool) = switch (x)(x:Bool)(y:Int)→ false→ trueisInt (1,,true)

This is because both of the branches return values of type Bool and Bool is not disjoint toBool. Whereas, the following program will be accepted:
Int | Bool notSucc (x : Int | Bool) = switch (x)(x:Bool)(y:Int)→ not x→ succ x

notSucc (1,,True)

Note that the first branch returns Bool and the second branch returns Int. Since Int is disjoint
to Bool, therefore, it is safe to accept such programs.
Record type for explicit disjointness. The second approach is to use record types with
distinct labels as a return type of alternative branches to enforce disjointness. The return typeof a switch in this case will always be sound. Therefore, the calculus will not generate ill-typed
programs at runtime.
{l1:Bool, l2:Bool} isInt (x : Int | Bool) = switch (x)(x:Int) → {l1:true}(y:Bool) → {l2:false}
isInt (1,,true)

8.2.2 Multiple Interface InheritanceMultiple interface inheritance is a prominent feature available in many modern programming languages including Java and Scala. This feature is essential for the extensibility andscalability of software development. The calculi discussed in this thesis do not support multiple interface inheritance. Another line of future work is to allow multiple interface inheritance in λu .
Specifically, λu with nominal types can further be enriched to support multiple inheritance. The lack of multiple interface inheritance is primarily due to the fact that only the ⊤147

8 Conclusion and Future Work
type or a nominal type can be declared as a supertype of another nominal type in ∆. For example, in a nominal type environment with ∆ = Person <: ⊤, Robot <: ⊤, the following
new declaration is allowed:∆ = Person <: ⊤, Robot <: ⊤, Student <: Person
We add a new nominal type named Student and extend it with Person. Notice that only the⊤ type or a nominal type is declared as a parent type in ∆. An attempt to declare multiple parents of a nominal type is rejected. For example, the following extension of ∆ is notallowed:∆ = Person <: ⊤, Robot <: ⊤, Hybrid <: Person , Hybrid <: Robot
This restricts multiple inheritance due to the fact that only one type can be declared asa parent type of another nominal type. It is essential for the multiple interface inheritancethat a nominal type may define multiple parent types. Multiple inheritance can be achievedby allowing intersection types to be a supertype of nominal types in ∆. Specifically, allowing intersections as parent types in ∆ will allow the following declaration, which naturally
provides multiple interface inheritance:∆ = Person <: ⊤, Robot <: ⊤, Hybrid <: Person ∧ Robot

8.2.3 Explicit Disjointness of Nominal Types
The Ceylon language employs an of construct to explicitly declare the disjointness of nominal
types. We will elaborate this using the following Ceylon code:
abstract class Student() of PG | UG {}class PG() extends Student() {}class UG() extends Student() {}The first line of the code creates a Student class and declares two subtypes of Student, PG and
UG using the of construct. This declarations marks PG and UG disjoint. It is safe to use PG and UG

in alternative branches of a switch expressions. On the other hand, it is prohibited to createa class that extends both PG and UG to retain determinism. The class PG and the class UG must
later be defined in the code. The formalization of such a construct is also an interesting lineof future work. In essence, the nominal type environment will be revised to handle the ofconstruct. Each entry in the revised ∆ contain three components. The name of the newly
defined nominal type, its parent type(s), and its subtype(s).
∆ = Student <: ⊤ ∗ [PG, UG] , PG <: Student ∗ [ ] , UG <: Student ∗ [ ]148

8.2 Future Work8.2.4 Gradual Typing
Gradual typing is another line of interest in practical programming languages with significant
recent development. Union types naturally provide gradual typing in a restricted fashionin such a way that actual type is among the certain candidates from a union of types. In
gradual typing, on the other hand, no information of the unknown type is statically available.
Inclusion of gradual typing with λu is another line of future work with practical interest. The
naive addition of gradual typing with disjoint switches may allow the following program(where * denotes unknown type):
Bool isInteger (x : Int | *) {switch (x):Int → true*
→ false}

The type-based switch construct in this code snippet is exhaustive, but how do we makesure that the two branches will not overlap? This question gives birth to a particular researchquestion of redefining the disjointness in the presence of union types, type-based switches,
and the unknown type (*). The unknown type makes the disjointness non-trivial because of
the unavailability of the static type information.

149

Bibliography[Citing pages are listed after each reference.]

Andreas Abel and Dulma Rodriguez. 2008. Syntactic metatheory of higher-order subtyping. In International Workshop on Computer Science Logic. Springer, 446–460. [cited on
page 10]
Robin Adams. 2006. Pure type systems with judgemental equality. Journal of Functional
Programming 16, 2 (2006), 219–246. [cited on page 138]João Alpuim, Bruno C. d. S. Oliveira, and Zhiyuan Shi. 2017. Disjoint Polymorphism. In
European Symposium on Programming (ESOP). [cited on pages 20, 40, 54, 56, and 135]
Nada Amin, Adriaan Moors, and Martin Odersky. 2012. Dependent object types. In 19thInternational Workshop on Foundations of Object-Oriented Languages. [cited on pages 10,
12, 13, and 104]
Nada Amin and Tiark Rompf. 2017. Type Soundness Proofs with Definitional Interpreters.
In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages (POPL 2017). 14 pages. [cited on page 140]
Davide Ancona and Andrea Corradi. 2014. Sound and complete subtyping between coinductive types for object-oriented languages. In European Conference on Object-Oriented
Programming. Springer, 282–307. [cited on page 133]
Davide Ancona and Andrea Corradi. 2016. Semantic subtyping for imperative objectoriented languages. ACM SIGPLAN Notices 51, 10 (2016), 568–587. [cited on page 133]
Peter B. Andrews. 1986. An Introduction to Mathematical Logic and Type Theory: To Truth
through Proof. Academic Press, Inc. [cited on page 137]
Inc Apple. 2021. Swift language guide.

https://docs.swift.org/swift-book/

LanguageGuide/TheBasics.html [cited on page 135]

151

Bibliography
Subarno Banerjee, Lazaro Clapp, and Manu Sridharan. 2019. Nullaway: Practical type-basednull safety for java. In Proceedings of the 2019 27th ACM Joint Meeting on European Software
Engineering Conference and Symposium on the Foundations of Software Engineering. 740–
750. [cited on page 135]
Franco Barbanera, Mariangiola Dezaniciancaglini, and Ugo Deliguoro. 1995. Intersection
and union types: syntax and semantics. Information and Computation 119, 2 (1995), 202–
230. [cited on pages 4, 10, 17, 33, and 133]
Henk Barendregt, Mario Coppo, and Mariangiola Dezani-Ciancaglini. 1983. A filter lambda
model and the completeness of type assignment. The journal of symbolic logic 48, 4 (1983),
931–940. [cited on pages 6 and 104]
Jon Barwise and Robin Cooper. 1981. Generalized quantifiers and natural language. In
Philosophy, language, and artificial intelligence. Springer, 241–301. [cited on page 10]
Véronique Benzaken, Giuseppe Castagna, and Alain Frisch. 2003. CDuce: an XML-centric
general-purpose language. ACM SIGPLAN Notices 38, 9 (2003), 51–63. [cited on pages 7,8, 54, and 133]
Giovanni Bernardi, Ornela Dardha, Simon J Gay, and Dimitrios Kouzapas. 2014. On duality
relations for session types. In International Symposium on Trustworthy Global Computing.
Springer, 51–66. [cited on page 138]
Jan Bessai, Boris Düdder, Andrej Dudenhefner, Tzu-Chun Chen, and Ugo de’Liguoro. 2015.
Typing classes and mixins with intersection types. arXiv preprint arXiv:1503.04911 (2015).
[cited on page 104]
Xuan Bi and Bruno C d S Oliveira. 2018. Typed first-class traits. In 32nd European Conference
on Object-Oriented Programming (ECOOP 2018). Schloss Dagstuhl-Leibniz-Zentrum fuer
Informatik. [cited on page 21]
Xuan Bi, Bruno C d S Oliveira, and Tom Schrijvers. 2018a. The essence of nested composition.
In 32nd European Conference on Object-Oriented Programming (ECOOP 2018). Schloss
Dagstuhl-Leibniz-Zentrum fuer Informatik. [cited on page 21]
Xuan Bi, Bruno C. d. S. Oliveira, and Tom Schrijvers. 2018b. The Essence of Nested Composition. In European Conference on Object-Oriented Programming (ECOOP). [cited on
pages 83 and 135]

152

Bibliography
Xuan Bi, Ningning Xie, Bruno C. d. S. Oliveira, and Tom Schrijvers. 2019. Distributive
Disjoint Polymorphism for Compositional Programming. In Programming Languages andSystems, Luís Caires (Ed.). Springer International Publishing, Cham, 381–409. [cited on
page 135]
Gavin Bierman, Martín Abadi, and Mads Torgersen. 2014. Understanding typescript. In European Conference on Object-Oriented Programming. Springer, 257–281. [cited on pages 7,
10, and 103]David Binder, Julian Jabs, Ingo Skupin, and Klaus Ostermann. 2019. Decomposition diversity
with symmetric data and codata. Proceedings of the ACM on Programming Languages 4,
POPL (2019), 30. [cited on page 138]
Richard Bird and Oege de Moor. 1996.

The Algebra of Programming.

http://www.cs.ox.ac.uk/publications/books/algebra/

Prentice-Hall.

[cited on pages 10,137, and 138]
Olivier Blanvillain, Jonathan Immanuel Brachthäuser, Maxime Kjaer, and Martin Odersky. 2022. Type-Level Programming with Match Types. Proc. ACM Program. Lang. 6,
POPL, Article 37 (jan 2022), 24 pages. https://doi.org/10.1145/3498698 [cited
on page 135]
François Bourdoncle and Stephan Merz. 1997. Type checking higher-order polymorphic
multi-methods. In Proceedings of the 24th ACM SIGPLAN-SIGACT symposium on Principles of programming languages. 302–315. [cited on pages 136 and 137]
R. M. Burstall, D. B. MacQueen, and D. T. Sannella. 1981. HOPE: An experimental applicative
Language. Technical Report CSR-62-80. Computer Science Dept, Univ. of Edinburgh.
[cited on pages 7 and 21]
Peter Canning, William Cook, Walter Hill, Walter Olthoff, and John C Mitchell. 1989. Fbounded polymorphism for object-oriented programming. In Proceedings of the fourthinternational conference on functional programming languages and computer architecture.
273–280. [cited on pages 10, 20, 28, 55, 109, and 139]
Luca Cardelli. 1996. Type systems. ACM Computing Surveys (CSUR) 28, 1 (1996), 263–264.
[cited on page 3]
Luca Cardelli, Simone Martini, John C Mitchell, and Andre Scedrov. 1994. An extension ofsystem F with subtyping. Information and Computation 109, 1-2 (1994), 4–56. [cited on
page 139]

153

Bibliography
Luca Cardelli and Peter Wegner. 1985. On understanding types, data abstraction, and polymorphism. ACM Computing Surveys (CSUR) 17, 4 (1985), 471–523. [cited on pages 4, 5,
12, 20, 28, 55, and 136]
Giuseppe Castagna and Alain Frisch. 2005. A gentle introduction to semantic subtyping. In
Proceedings of the 7th ACM SIGPLAN international conference on Principles and practiceof declarative programming. 198–199. [cited on pages 33 and 133]
Giuseppe Castagna, Giorgio Ghelli, and Giuseppe Longo. 1995. A calculus for overloadedfunctions with subtyping. Information and Computation 117, 1 (1995), 115–135. [cited
on pages 4, 5, 21, 28, 83, 136, and 137]
Giuseppe Castagna and Victor Lanvin. 2017. Gradual typing with union and intersection
types. Proceedings of the ACM on Programming Languages 1, ICFP (2017), 1–28. [cited
on page 133]
Giuseppe Castagna, Victor Lanvin, Mickaël Laurent, and Kim Nguyen. 2019. Revisiting
Occurrence Typing. arXiv preprint arXiv:1907.05590 (2019). [cited on page 134]
Giuseppe Castagna, Mickaël Laurent, Kim Nguyen, and Matthew Lutze. 2022. On typecases, union elimination, and occurrence typing. Proceedings of the ACM on Programming
Languages 6, POPL (2022), 75. [cited on page 134]
Giuseppe Castagna, Kim Nguyen, Zhiwu Xu, Hyeonseung Im, Sergueï Lenglet, and Luca
Padovani. 2014a. Polymorphic Functions with Set-Theoretic Types: Part 1: Syntax, Semantics, and Evaluation. In Proceedings of the 41st ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages (San Diego, California, USA) (POPL ’14). Association for Computing Machinery, New York, NY, USA, 5–17. https://doi.org/10.
1145/2535838.2535840 [cited on pages 7, 8, 23, 24, and 54]
Giuseppe Castagna, Kim Nguyen, Zhiwu Xu, Hyeonseung Im, Sergueï Lenglet, and Luca
Padovani. 2014b. Polymorphic functions with set-theoretic types: part 1: syntax, semantics, and evaluation. In Proceedings of the 41st ACM SIGPLAN-SIGACT Symposium on
Principles of Programming Languages. 5–17. [cited on pages 136 and 137]
Arthur Charguéraud. 2011. The Locally Nameless Representation. Journal of AutomatedReasoning (2011). [cited on page 123]
Avik Chaudhuri. 2015. Flow: a static type checker for JavaScript. SPLASH-I In Systems, Programming, Languages and Applications: Software for Humanity (2015). [cited on pages 7,
10, and 103]

154

Bibliography
Avik Chaudhuri, Panagiotis Vekris, Sam Goldman, Marshall Roch, and Gabriel Levi. 2017.
Fast and precise type checking for JavaScript. Proceedings of the ACM on Programming
Languages 1, OOPSLA (2017), 1–30. [cited on page 135]
Curtis Clifton, Todd Millstein, Gary T Leavens, and Craig Chambers. 2006. MultiJava: Design rationale, compiler implementation, and applications. ACM Transactions on Programming Languages and Systems (TOPLAS) 28, 3 (2006), 517–575. [cited on page 136]
Adriana B Compagnoni and Benjamin C Pierce. 1996. Higher-order intersection types andmultiple inheritance. Mathematical Structures in Computer Science 6, 5 (1996), 469–501.
[cited on pages 5 and 18]
Irving M Copi, Carl Cohen, and Victor Rodych. 2018. Introduction to logic. Routledge. [cited
on page 29]
Mario Coppo, Mariangiola Dezani-Ciancaglini, and Betti Venneri. 1981. Functional characters of solvable terms. Mathematical Logic Quarterly 27, 2-6 (1981), 45–58. [cited on
page 18]
Douglas Crockford. 2008. JavaScript: The Good Parts: The Good Parts. ” O’Reilly Media,
Inc.”. [cited on page 3]
Haskell Brooks Curry, Robert Feys, William Craig, J Roger Hindley, and Jonathan P Seldin.
1958. Combinatory logic. Vol. 1. North-Holland Amsterdam. [cited on pages 17 and 29]
Oliver Danvy and Andrzej Filinski. 1992. Representing Control: A Study of the CPS transformation. Mathematical Structures in Computer Science 2, 4 (December 1992), 361–391.
[cited on page 137]
Ornela Dardha, Daniele Gorla, and Daniele Varacca. 2013. Semantic subtyping for objects
and classes. In Formal Techniques for Distributed Systems. Springer, 66–82.

[cited on

page 133]
Rowan Davies and Frank Pfenning. 2000. Intersection types and computational effects. In
Proceedings of the fifth ACM SIGPLAN international conference on Functional programming. 198–208. [cited on pages 35 and 45]
Stephen Dolan and Alan Mycroft. 2017. Polymorphism, subtyping, and type inference inMLsub. In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages. 60–72. [cited on page 61]

155

Bibliography
Kosta Došen. 1989. Logical constants as punctuation marks. Notre Dame J. Formal Logic
30, 3 (06 1989), 362–381. https://doi.org/10.1305/ndjfl/1093635154 [cited on
page 138]
Joshua Dunfield. 2014. Elaborating intersection and union types. Journal of Functional Programming 24, 2-3 (2014), 133–165. [cited on pages 4, 7, 14, 15, 17, 18, 19, 20, 23, 33, 54,
83, 84, 93, 94, 97, 133, and 143]
Joshua Dunfield and Neel Krishnaswami. 2019.

Bidirectional Typing.

arXiv preprint

arXiv:1908.05839 (2019). [cited on page 139]
Joshua Dunfield and Neelakantan R. Krishnaswami. 2013. Complete and Easy Bidirectional
Typechecking for Higher-Rank Polymorphism. In ICFP. [cited on page 139]
Joshua Dunfield and Frank Pfenning. 2003. Type assignment for intersections and unions
in call-by-value languages. In International Conference on Foundations of Software Science
and Computation Structures. Springer, 250–266. [cited on pages 33 and 133]
Tim Freeman and Frank Pfenning. 1991. Refinement types for ML. In Proceedings of the ACM
SIGPLAN 1991 conference on Programming language design and implementation. 268–277.
[cited on page 18]
Alain Frisch, Giuseppe Castagna, and Véronique Benzaken. 2002. Semantic subtyping. In
Proceedings 17th Annual IEEE Symposium on Logic in Computer Science. IEEE, 137–146.
[cited on page 133]
Jacques Garrigue. 1998. Programming with polymorphic variants. In ML workshop. [cited
on pages 7 and 21]
Gerhard Gentzen. 1934. Untersuchungen über das logische Schliessen. Mathematische
Zeitschrift 39 (1934), 176–210, 405–431. [cited on page 137]
Adele Goldberg and David Robson. 1983. Smalltalk-80: the language and its implementation.
Addison-Wesley Longman Publishing Co., Inc. [cited on page 136]
James Gosling, Bill Joy, Guy Steele, and Gilad Bracha. 2000. The Java language specification.
Addison-Wesley Professional. [cited on page 3]
James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Alex Buckley, Daniel Smith, and Gavin
Bierman. 2021. The Java language specification. https://docs.oracle.com/javase/
specs/jls/se14/html/index.html [cited on page 24]

156

Bibliography
Eric Gunnerson. 2012. Nullable Types. In A Programmer’s Guide to C# 5.0. Springer, 247–
250. [cited on pages 7 and 26]
Haruo Hosoya and Benjamin C Pierce. 2003. XDuce: A statically typed XML processing
language. ACM Transactions on Internet Technology (TOIT) 3, 2 (2003), 117–148. [cited
on page 133]
William A Howard. 1980. The formulae-as-types notion of construction. To HB Curry:
essays on combinatory logic, lambda calculus and formalism 44 (1980), 479–490. [cited on
pages 17 and 29]
Xuejing Huang and Bruno C. d. S. Oliveira. 2020. A Type-Directed Operational Semantics For a Calculus with a Merge Operator. In 34th European Conference on ObjectOriented Programming (ECOOP 2020) (Leibniz International Proceedings in Informatics(LIPIcs), Vol. 166), Robert Hirschfeld and Tobias Pape (Eds.). Schloss Dagstuhl–LeibnizZentrum für Informatik, Dagstuhl, Germany, 26:1–26:32.

https://doi.org/10.

4230/LIPIcs.ECOOP.2020.26 [cited on pages 19, 20, 83, 85, and 87]
Xuejing Huang and Bruno C d S Oliveira. 2021. Distributing intersection and union types
with splits and duality (functional pearl). Proceedings of the ACM on Programming Languages 5, ICFP (2021), 1–24. [cited on pages 6, 14, 15, 36, 46, 47, and 61]
DeLesley S. Hutchins. 2010. Pure Subtype Systems. (2010). [cited on page 139]
Atsushi Igarashi and Hideshi Nagira. 2006. Union types for object-oriented programming.
In Proceedings of the 2006 ACM symposium on Applied computing. 1435–1441. [cited on
page 133]
Atsushi Igarashi, Benjamin C. Pierce, and Philip Wadler. 2001. Featherweight Java: a minimalcore calculus for Java and GJ. ACM Trans. Program. Lang. Syst. 23, 3 (2001), 396–450.
[cited on page 46]
Atsushi Igarashi and Mirko Viroli. 2002. On Variance-Based Subtyping for Parametric Types.
In ecoop. sv, Malaga, Spain, 441–469. To appear in ACM Transactions on Programming
Languages and Systems.. [cited on page 140]
LSV Jutting. 1993. Typing in pure type systems. Information and Computation 105, 1 (1993),
30–41. [cited on page 138]
Gavin King. 2013. The Ceylon language specification, version 1.0. [cited on pages 4, 7, 8,
10, 25, and 103]

157

Bibliography
Gavin King. 2016. Disjointness in Ceylon.

http://web.mit.edu/ceylon_v1.3.3/

ceylon-1.3.3/doc/en/spec/html_single [cited on page 10]
Foundation Kotlin. 2021. Kotlin programming language.

https://kotlinlang.org/[cited on page 135]
Saunders Mac Lane. 1998. Categories for the Working Mathematician (2 ed.). Number 5 in
Graduate Texts in Mathematics. Springer. [cited on pages 11 and 137]
David MacQueen, Gordon Plotkin, and Ravi Sethi. 1984. An ideal model for recursive polymorphic types. In Proceedings of the 11th ACM SIGACT-SIGPLAN symposium on Principles of programming languages. 165–174. [cited on pages 33 and 133]
James McKinna and Robert Pollack. 1993. Pure type systems formalized. In International
Conference on Typed Lambda Calculi and Applications. Springer, 289–305.

[cited on

page 138]
Corporation Microsoft. 2023.

Union Types in TypeScript.

https://www.

typescriptlang.org/docs/handbook/unions-and-intersections.html [cited
on page 26]
Fabian Muehlboeck and Ross Tate. 2018. Empowering union and intersection types withintegrated subtyping. Proceedings of the ACM on Programming Languages 2, OOPSLA(2018), 1–29. [cited on pages 4, 9, 17, 23, 46, 50, and 134]
Aleksandar Nanevski, Frank Pfenning, and Brigitte Pientka. 2008. Contextual modal typetheory. ACM Transactions on Computational Logic (TOCL) 9, 3 (2008), 23. [cited on
pages 28 and 139]
Abel Nieto, Yaoyu Zhao, Ondřej Lhoták, Angela Chang, and Justin Pu. 2020. Scala with
Explicit Nulls. In 34th European Conference on Object-Oriented Programming (ECOOP
2020) (Leibniz International Proceedings in Informatics (LIPIcs)). 25:1–25:26. [cited on
pages 7, 26, and 135]
Martin Odersky. 2021. Scala 3: A next generation compiler for Scala.

https://dotty.

epfl.ch [cited on pages 4 and 7]
Martin Odersky, Philippe Altherr, Vincent Cremet, Burak Emir, Sebastian Maneth, Stéphane
Micheloud, Nikolay Mihaylov, Michel Schinz, Erik Stenman, and Matthias Zenger. 2004.
An overview of the Scala programming language. Technical Report. [cited on pages 3, 10,
and 103]

158

Bibliography
Bruno C. d. S. Oliveira, Zhiyuan Shi, and Joao Alpuim. 2016. Disjoint intersection types. In
Proceedings of the 21st ACM SIGPLAN International Conference on Functional Programming. 364–377. [cited on pages 6, 10, 14, 15, 19, 20, 34, 37, 39, 44, 50, 83, 84, 86, 98, 135,
and 145]
Klaus Ostermann and Julian Jabs. 2018. Dualizing Generalized Algebraic Data Types by
Matrix Transposition. In European Symposium on Programming. Springer, 60–85. [cited
on page 138]
Matthew M Papi, Mahmood Ali, Telmo Luis Correa Jr, Jeff H Perkins, and Michael D Ernst.
2008. Practical pluggable types for Java. In Proceedings of the 2008 international symposium
on Software testing and analysis. 201–212. [cited on page 135]
Gyunghee Park, Jaemin Hong, Guy L Steele Jr, and Sukyoung Ryu. 2019. Polymorphic symmetric multiple dispatch with variance. Proceedings of the ACM on Programming Languages 3, POPL (2019), 1–28. [cited on page 137]
Benjamin C Pierce. 1991. Programming with intersection types, union types. Technical Report. and polymorphism. Technical Report CMU-CS-91-106, Carnegie Mellon University. [cited on pages 18, 23, 33, and 133]
Benjamin C Pierce. 2002a. Programming with intersection types, union types, and polymorphism. (2002). [cited on pages 4 and 17]
Benjamin C. Pierce. 2002b. Types and Programming Languages (1st ed.). The MIT Press.
[cited on pages 3, 6, and 16]
Benjamin C Pierce, Chris Casinghino, Marco Gaboardi, Michael Greenberg, Cătălin Hriţcu,Vilhelm Sjöberg, and Brent Yorgey. 2010. Software foundations. Webpage: http://www.
cis. upenn. edu/bcpierce/sf/current/index. html (2010). [cited on page 16]
Benjamin C. Pierce and David N. Turner. 2000. Local Type Inference. ACM Trans. Program.
Lang. Syst. 22, 1 (2000). [cited on page 139]
Garrel Pottinger. 1980. A type assignment for the strongly normalizable λ-terms. To HB
Curry: essays on combinatory logic, lambda calculus and formalism (1980), 561–577. [cited
on page 18]
Dag Prawitz. 1979. Proofs and the Meaning and Completeness of the Logical Constants. In
Essays on Mathematical and Philosophical Logic. Synthese Library (Studies in Epistemology,
Logic, Methodology, and Philosophy of Science). [cited on page 137]

159

Bibliography
Baber Rehman, Xuejing Huang, Ningning Xie, and Bruno C. d. S. Oliveira. 2022. Union
Types with Disjoint Switches. In 36th European Conference on Object-Oriented Programming (ECOOP 2022) (Leibniz International Proceedings in Informatics (LIPIcs), Vol. 222),
Karim Ali and Jan Vitek (Eds.). Schloss Dagstuhl – Leibniz-Zentrum für Informatik,
Dagstuhl, Germany, 25:1–25:31. https://doi.org/10.4230/LIPIcs.ECOOP.2022.
25 [cited on page 136]
John C Reynolds. 1988. Preliminary design of the programming language Forsythe. (1988).
[cited on pages 10, 14, 15, 19, 20, 21, 50, 83, and 84]
John C Reynolds. 1997. Design of the Programming Language F orsythe. In ALGOL-likelanguages. Springer, 173–233. [cited on page 20]
Nick Rioux, Xuejing Huang, Bruno C d S Oliveira, and Steve Zdancewic. 2023. A Bowtie fora Beast: Overloading, Eta Expansion, and Extensible Data Types in F�. Proceedings of the
ACM on Programming Languages 7, POPL (2023), 515–543. [cited on page 136]
Richard Routley and Robert K Meyer. 1972. The semantics of entailment—III. Journal ofphilosophical logic 1, 2 (1972), 192–208. [cited on page 46]
Paula Severi and Erik Poll. 1994. Pure type systems with definitions. In International Symposium on Logical Foundations of Computer Science. Springer, 316–328. [cited on page 138]
Alex K Simpson. 1994. The proof theory and semantics of intuitionistic modal logic. (1994).
[cited on pages 28 and 139]
Martin Steffen and Benjamin Pierce. 1994. Higher-order subtyping. (1994).

[cited on

page 10]
Bjarne Stroustrup. 1986. An overview of C++. In Proceedings of the 1986 SIGPLAN workshop
on Object-oriented programming. 7–18. [cited on pages 3 and 136]
Ross Tate. 2011. Overloading in Ceylon. https://github.com/ceylon/ceylon-spec/
issues/73 [cited on page 8]
Sam Tobin-Hochstadt and Matthias Felleisen. 2008. The design and implementation of
Typed Scheme. ACM SIGPLAN Notices 43, 1 (2008), 395–406. [cited on page 134]
Sam Tobin-Hochstadt and Matthias Felleisen. 2010. Logical types for untyped languages. In
Proceedings of the 15th ACM SIGPLAN international conference on Functional programming. 117–128. [cited on page 134]

160

Bibliography
Tarmo Uustalu and Varmo Vene. 2008. Comonadic notions of computation. Electronic Notes
in Theoretical Computer Science 203, 5 (2008), 263–284. [cited on page 11]
Steffen van Bakel, Mariangiola Dezani-Ciancaglini, Ugo de’Liguoro, and Yoko Motohoma.
2000. The minimal relevant logic and the call-by-value lambda calculus. Technical Report.
Citeseer. [cited on page 46]
LS van Benthem Jutting, James McKinna, and Robert Pollack. 1993. Checking algorithms forpure type systems. In International Workshop on Types for Proofs and Programs. Springer,
19–61. [cited on page 138]
Guido Van Rossum, Fred L Drake, et al. 1995. Python reference manual. Centrum voor
Wiskunde en Informatica Amsterdam. [cited on page 3]
Adriaan Van Wijngaarden, Barry J Mailloux, John EL Peck, Cornelius HA Koster, M Sintzoff,
CH Lindsey, LGLT Meertens, and RG Fisker. 1969. Report on the algorithmic language
ALGOL 68. Numer. Math. 14, 1 (1969), 79–218. [cited on page 6]
Adriaan van Wijngaarden, Barry James Mailloux, John Edward Lancelot Peck, Cornelis HA
Koster, CH Lindsey, M Sintzoff, Lambert GLT Meertens, and RG Fisker. 2012. Revised
report on the algorithmic language Algol 68. Springer Science & Business Media. [cited on
page 6]
Philip Wadler and Stephen Blott. 1989. How to make ad-hoc polymorphism less ad hoc. In
Proceedings of the 16th ACM SIGPLAN-SIGACT symposium on Principles of programming
languages. 60–76. [cited on pages 4, 5, 28, and 136]
Ningning Xie, Bruno C d S Oliveira, Xuan Bi, and Tom Schrijvers. 2020.

Row andbounded polymorphism via disjoint polymorphism. In 34th European Conference on
Object-Oriented Programming (ECOOP 2020). Schloss Dagstuhl-Leibniz-Zentrum für Informatik. [cited on page 21]
Xu Xue, Bruno C d S Oliveira, and Ningning Xie. 2022. Applicative Intersection Types.
In Programming Languages and Systems: 20th Asian Symposium, APLAS 2022, Auckland,
New Zealand, December 5, 2022, Proceedings. Springer, 155–174. [cited on page 90]
Yanpeng Yang and Bruno C. d. S. Oliveira. 2017. Unifying typing and subtyping. Proceedingsof the ACM on Programming Languages 1, OOPSLA (2017), 47. [cited on page 139]
Francesco Zappa Nardelli, Julia Belyakova, Artem Pelenitsyn, Benjamin Chung, Jeff Bezanson, and Jan Vitek. 2018. Julia subtyping: a rational reconstruction. Proceedings of the161Bibliography
ACM on Programming Languages 2, OOPSLA (2018), 1–27.

[cited on pages 24, 104,

and 137]Jan Zwanenburg. 1999. Pure type systems with subtyping. In International Conference on
Typed Lambda Calculi and Applications. Springer, 381–396. [cited on page 138]

162

Part VI
Technical Appendix

163

A Alternative Disjointness
We discuss another variant of the disjointness algorithm that depends on so called Common
Ordinary Subtypes (COST) in this chapter. Recall that the disjointness algorithm discussed
in Section 3.3 is set-based (LOS). The COST defined in this chapter is not set-based. We also
prove the equivalence of standard disjointness specifications and a variant based on COSTin this chapter.

Syntax and subtyping. Syntax and the subtyping stays the same as in λu with intersection
types and are shown in Figure A.1. Types, expressions, values, context and subtyping havealready been explained. Subtyping relation preserves the standard properties of reflexivity
and transitivity.
Lemma A.1 (Subtyping Reflexivity). A <: A
Lemma A.2 (Subtyping Transitivity). If A <: B and B <: C then A <: C

A.1 Common Ordinary Subtypes (COST)
COST play an integral role in the design of the disjointness algorithm presented in this chapter. This section explains the COST in detail by discussing the COST specifications as wellas the corresponding algorithm that computes COST.

COST Specifications. The specifications for the COST are shown in Definition 11. It
trivially states that two types A and B share a COST if there exist an ordinary type C suchthat C is subtype of A and B. The ordinary types are shown in the middle of Figure A.1. Int,A → B, and Null constitute ordinary types.
Definition 11 (COST Specifications). A ⊓s B ::= ∃ C, Ord C and C <: A and C <: B

165

A Alternative Disjointness

A, B, Cev
Γ

⊤ | ⊥ | Int | A → B | A ∨ B | A ∧ B | Nullx | i | λx.e | e1 e2 | switch e {(x : A) → e1 , (y : B) → e2 } | nulli | λx.e | null
· | Γ, x : A

::=
::=
::=
::=

A <: B(Subtyping)

s-top

s-int

s-bot

s-nullA <: ⊤

Int <: Int⊥ <: A

Null <: Nulls-arrow
B1 <: A1A2 <: B2
A1 → A2 <: B1 → B2s-orb
A <: BA <: B ∨ Cs-orc
A <: CA <: B ∨ C

s-andaA <: BA <: CA <: B ∧ Cs-ora
A <: CB <: CA ∨ B <: Cs-andb
A <: CA ∧ B <: Cs-andcB <: CA ∧ B <: C

Ord A

(Ordinary Types)
ord-int

ord-arroword-null

Ord Int

Ord A → B

Ord NullA⊚(Union Ordinary Types)
uo-topuo-intuo-arrow

uo-unit⊤⊚Int⊚(A → B)⊚

Null⊚

B◁A▷C
usp-orA◁A∨B▷B

uo-andA⊚
B⊚(A ∧ B)⊚(Union Splittable Types)
usp-orandl
A1 ◁ A ▷ A2
A1 ∧ B ◁ A ∧ B ▷ A2 ∧ B

usp-orandr
B1 ◁ B ▷ B2
A ∧ B1 ◁ A ∧ B ▷ A ∧ B2

Figure A.1: Syntax, subtyping, ordinary, union ordinary and union splittable types for λu with intersection types.

Union ordinary and union splittable types. Union ordinary and union splittable types
are shown at the bottom in Figure A.1. These types have already been discussed in Chapter 4.
Union ordinary types include ⊤, Int, A → B, Null and an intersection of union ordinary
types. Any type that is not union ordinary is union splittable.

166

A.1 Common Ordinary Subtypes (COST)
A⊓B(Common Ordinary Subtypes)

cost-top⊤⊓⊤

cost-ordl
Ord A
A⊓⊤

cost-arrow
A1 → B1 ⊓ A2 → B2
cost-orra
B1 ◁ B ▷ B2
A ⊓ B1A⊓B

cost-ordr
Ord A⊤⊓A
cost-orlaA1 ◁ A ▷ A2
A1 ⊓ BA⊓B

cost-orrb
B1 ◁ B ▷ B2
A ⊓ B2A⊓B

cost-int

cost-nullInt ⊓ Int

Null ⊓ Nullcost-orlb
A1 ◁ A ▷ A2
A2 ⊓ BA⊓B

cost-andlB⊚
A1 ⊓ BA2 ⊓ B(A1 ∧ A2 ) ⊓ B

cost-andrA⊚A ⊓ B1A ⊓ B2A ⊓ (B1 ∧ B2 )A1 ⊓ A2

B1 ⊓ B2

Figure A.2: Common ordinary subtypes based on union splittable types for λu .

COST Algorithm. The algorithm that computes COST is shown in Figure A.2. In essence,
the COST algorithm computes whether two given types potentially share an ordinary subtype
or not. Rule cost-top states that ⊤ shares an ordinary subtype with ⊤. This is triviallytrue, such as Int is a subtype of ⊤. Rules cost-ordl and cost-ordr state that ⊤ shares anordinary subtype with all the ordinary types. Rules cost-int, cost-null, and cost-arrow
are the natural rules for Int, Null, and A → B respectively.
Rules cost-orla, cost-orlb, cost-orra, and cost-orrb deal with the union splittabletypes. These rules collectively state that if a type B shares an ordinary subtype with a part(A1 or A2 ) of union splittable type (A1 ◁ A ▷ A2 ), then B shares an ordinary subtype with
A. This due to the fact that parts (A1 and A2 ) of a union splittable type are subtypes of theoriginal type (A). Rules cost-andl and cost-andr deal with the intersection types. Anintersection type A1 ∧ A2 shares a ordinary subtype with B if A1 shares an ordinary subtype
with B, A2 shares an ordinary subtype with B, and A1 shares an ordinary subtype with A2 . A
side condition of union ordinary B (B⊚ ) must also hold. Note that the side condition of B⊚
is essential. The COST algorithm will not be sound without this condition.
Soundness and completeness of COST algorithm. We prove that the COST algorithm is sound and complete with respect to the COST specifications.
Lemma A.3 (COST Soundness). A ⊓ B → A ⊓s B.
Lemma A.4 (COST Completeness). A ⊓s B → A ⊓ B.

167

A Alternative Disjointness

A.2 Disjointness
The disjointness for λu is the converse of COST. Two types are disjoint if they do not shareany ordinary subtype. Whereas, the COST algorithm states the otherwise. Therefore, thedisjointness is simply the negation of COST and is shown in Definition 12.
Definition 12 (Disjointness Algorithm). A ∗a B ::= ¬ (A ⊓ B)

Disjointness equivalence. We prove that the novel disjointness based on COST is soundand complete with respect to standard disjointness specifications. The disjointness specifications are shown again in Definition 13 for readability.
Definition 13 (∧-Disjointness). A ∗ B ::= ∄ C, Ord C and C <: A and C <: B.
Lemma A.5 (Disjointness equivalence). A ∗ B ↔ ¬ (A ⊓s B)

A.3 Typing, Operational Semantics, and Type-safety
The typing and the operational semantics do not essentially require revision for this chapter
and are shown in Figure A.3. Both of these relations are standard and have already been
explained.
Type-safety and determinism. The standard properties of the type-safety consisting oftype preservation and the progress hold in this system. Theorem A.6 states type preservation and the Theorem A.7 states progress. We also show that the reduction is deterministic(Theorem A.8).
Theorem A.6 (Type Preservation). If Γ ⊢ e : A and e −→ e′ then Γ ⊢ e′ : A.
Theorem A.7 (Progress). If Γ ⊢ e : A then either e is a value; or e can take a step to e′ .
Theorem A.8 (Determinism). If Γ ⊢ e : A and e −→ e1 and e −→ e2 then e1 = e2 .

168

A.3 Typing, Operational Semantics, and Type-safetyΓ⊢e:A(Typing)

typ-int

typ-nullΓ ⊢ i : IntΓ ⊢ null : Nulltyp-subΓ⊢e:A
A <: B
Γ⊢e:B

typ-varx:A ∈ ΓΓ⊢x:A
typ-abs
Γ, x : A ⊢ e : BΓ ⊢ λx.e : A → B

typ-appΓ ⊢ e1 : A → BΓ ⊢ e2 : AΓ ⊢ e1 e2 : B
typ-andΓ⊢e:A
Γ⊢e:B
Γ⊢e:A∧B

typ-switch

Γ⊢e:A∨BΓ, x : A ⊢ e1 : C
Γ, y : B ⊢ e2 : CA∗BΓ ⊢ switch e {(x : A) → e1 , (y : B) → e2 } : C

e −→ e′(Operational semantics)
step-appl
e1 −→ e′1
e1 e2 −→ e′1 e2

step-appre −→ e′v e −→ v e′

step-beta(λx.e) v −→ e[x ⇝ v]

step-switche −→ e′switch e {(x : A) → e1 , (y : B) → e2 } −→ switch e′ {(x : A) → e1 , (y : B) → e2 }step-switchl⌊v⌋ <: A
switch v {(x : A) → e1 , (y : B) → e2 } −→ e1 [x ⇝ v]

step-switchr⌊v⌋ <: B
switch v {(x : A) → e1 , (y : B) → e2 } −→ e2 [y ⇝ v]

Figure A.3: Typing for λu .

169