-
Notifications
You must be signed in to change notification settings - Fork 0
/
WriteLambdaTerms.scala
249 lines (210 loc) · 7.86 KB
/
WriteLambdaTerms.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package ilc
package howTo
import feature._
import org.scalatest._
/** How to write simply-typed lambda terms
* in the presence of simple right-to-left type inference
*/
class WriteLambdaTerms extends FlatSpec with Matchers {
object Lang extends functions.Pretty
with integers.Syntax
with products.Syntax
with bags.Syntax
with sums.Syntax
with maps.Types // maps included for illustration only
import Lang._
// SHORT GUIDE
// The object language of paper 63 is simply typed lambda calculus.
// Syntax trees of the object language can always be constructed
// if the 3 rules below are observed.
//
//
// t ::= x x // (Scala identifier)
// | t₁ t₂ t1 ! t2
// | λx : τ. t lambda(τ) { x => t }
//
// Rule 1: Declare the type of syntax trees as `Term`.
// Rule 2: Annotate the argument type of lambda abstractions.
// Rule 3: Fully instantiate polymorphic constants on each use.
//
// Short examples:
val id_Int = lambda(IntType) { x => x }
"id_Int" should "be a function between integers" in {
assert(id_Int.getType == (IntType =>: IntType))
}
val insert_Int =
lambda(IntType) { n =>
lambda(BagType(IntType)) { bag =>
Union.tapply(IntType) ! (Singleton.tapply(IntType) ! n) ! bag
}
}
"insert_Int" should "have type Int => Bag[Int] => Bag[Int]" in {
assert(
insert_Int.getType
==
(IntType =>: BagType(IntType) =>: BagType(IntType))
)
}
// LONG GUIDE
//
// - Representation of abstract syntax trees
// - How & in what situation can one write less type annotations
// The object language of paper 63 is simply typed lambda calculus.
// In order for bottom-up visitors to know the type of the subterm
// being traversed over at all times, we choose to annotate every
// occurrence of a variable with its type.
//
// case Var(name: Name, getType: Type)
//
// Here is an example of a term with all annotations present.
// It is the uncurried union of bags of integers (here and elsewhere,
// symbols in human-readable syntax carry the default meaning of
// their definitions in Agda standard library. For example,
// `Int × Int` is the type of pairs of integers, and `Int → Int`
// is the type of unary integer functions).
//
// uncurriedUnion : Bag Int × Bag Int → Bag Int
val bagOfInts = BagType(IntType)
val pairOfBags = ProductType(bagOfInts, bagOfInts)
val uncurriedUnionWithFullTypeAnnotation: Term =
Abs(Var("pair", pairOfBags),
App(
App(
Union.tapply(IntType),
App(
Proj1.tapply(bagOfInts, bagOfInts),
Var("pair", pairOfBags))),
App(
Proj2.tapply(bagOfInts, bagOfInts),
Var("pair", pairOfBags))))
// Writing full annotation by hand is tedious and error-prone.
// We developed a simple right-to-left algorithm to infer the
// type annotations of variables (among other things). In
// retrospect, full type inference by unification might have
// been a better choice.
//
// right-to-left type inference is available in the body of any
// subclass of
//
// ilc.feature.functions.SyntaxSugar
//
// We use the exclamation mark (!) as infix contructor of
// applications, because Scala associates it to the left.
//
// It is very important to declare terms built using type
// inference as having the Scala type "Term"---it would
// trigger the implicit conversion that puts type annotations
// in their place.
val uncurriedUnion: Term =
lambda(Var("pair", pairOfBags)) { pair =>
Union ! (Proj1 ! pair) ! (Proj2 ! pair)
}
val inference = "Right-to-left type inference"
inference should "put type annotations in the correct places" in {
assert(uncurriedUnion == uncurriedUnionWithFullTypeAnnotation)
}
// This is the correspondence between the grammar of
// simply-typed lambda calculus and the syntax of our
// AST construction DSL.
//
// t ::= x x // (Scala identifier)
//
// | t₁ t₂ t1 ! t2
//
// | λx : τ. t lambda(Var("x"), τ) { x => t }
// --or--
// lambda(τ) { x => t }
//
// The name given to `lambda` constructor is only a suggestion
// and is subject to alpha-renaming. If the meaning of the
// actual name bound by λ is unimportant, we can omit it.
val uncurriedUnionWithoutNameSuggestion: Term =
lambda(pairOfBags) { pair =>
Union ! (Proj1 ! pair) ! (Proj2 ! pair)
}
// In a nutshell, the type inference algorithm computes the
// type of operands from bottom up and use that knowledge to
// annotate formal parameters of Operators. In particular,
// we can omit the argument type annotation in a β-redex.
val five: Term = (lambda { x => x }) ! LiteralInt(5)
inference should "infer the type of `id` applied to an integer" in {
assert(five.getType == IntType)
}
// However, no information flows from operators to operands;
// higher-order arguments in particular need enough annotations
// for the inference algorithm to fully obtain its type.
import ilc.feature.base.TypeError
inference should "have to know the type of arguments completely" in {
intercept[TypeError] {
val uninferrable: Term = lambda { x => x }
}
val id_Int2Int = lambda(IntType =>: IntType) { x => x }
assert(
id_Int2Int.getType
==
((IntType =>: IntType) =>: (IntType =>: IntType))
)
intercept[TypeError] {
val uninferrable: Term = id_Int2Int ! (lambda { x => x })
}
}
// Polymorphic constants are effectively constants with
// System F types, who have to embed themselves in simply
// typed lambda calculus by instantiating all quantified
// type variables to monotypes. Take for example
// `ilc.feature.abelianMaps.Syntax.SingletonMap`:
//
// singletonMap : ∀k v. k → v → Map k v
// (copied from src/main/scala/ilc/feature/abelianMaps/Syntax.scala)
object SingletonMap extends ConstantWith2TypeParameters {
val typeConstructor =
TypeConstructor("keyType", "valType") {
case Seq(keyType, valType) =>
keyType =>: valType =>: MapType(keyType, valType)
}
}
// In System F, we would call
//
// singletonMap [Int] [Int] : Int → Int → Map Int Int
//
// to specialize `singletonMap` to one simply-typed function.
// In the Scala AST, type application is encoded as Scala
// function call with types as arguments.
"Type applications on SingletonMap" should "be function calls" in {
assert(
SingletonMap.tapply(IntType, IntType).getType
==
(IntType =>: IntType =>: MapType(IntType, IntType))
)
}
// If a polymorphic constant is applied to enough arguments
// to fully infer the necessary type applications, then
// we can omit the type applications.
inference should
"be able to infer type applications given enoguh information" in {
val singleton: Term = SingletonMap ! LiteralInt(5) ! PlusInt
assert(
singleton.getType ==
MapType(IntType, IntType =>: IntType =>: IntType)
)
}
// If immediate arguments of a polymorphic constant don't
// provide enough information to infer all type applications,
// then we cannot create a simply typed term from that
// constant.
inference should
"give up if argument types don't provide enough information" in {
intercept[TypeError] {
val valueTypeUnknown: Term = SingletonMap ! LiteralInt(5)
}
}
// Of course, type applications of some polymorphic constants
// can never be inferred from argument types.
inference should "give up on a constructor of sum types" in {
intercept[TypeError] {
val sumOfIntAndUnknown: Term = Inj1 ! LiteralInt(5)
}
}
// We hope the information herein is sufficient for decoding
// object level programs in `ilc.examples.*`.
}