-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSystemFPoly.lean
More file actions
269 lines (219 loc) · 12.6 KB
/
Copy pathSystemFPoly.lean
File metadata and controls
269 lines (219 loc) · 12.6 KB
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/-
System F: Polymorphic Lambda Calculus - Simplified Representation
This uses a simpler, extrinsic typing approach to demonstrate
polymorphic code generation. For the intrinsically-typed version
with full proofs, see SystemFComplete.lean (which covers STLC).
The key insight: at the code generation level, types are erased anyway,
so we can represent System F terms more simply.
-/
-- ═══════════════════════════════════════════════════════════════════════════════
-- TYPES (unindexed for simplicity)
-- ═══════════════════════════════════════════════════════════════════════════════
inductive Ty where
| var : Nat → Ty -- Type variable (de Bruijn)
| unit : Ty
| bool : Ty
| arr : Ty → Ty → Ty
| all : Ty → Ty -- ∀. τ (binds variable 0)
deriving Repr, DecidableEq
notation:50 α " ⟶ " β => Ty.arr α β
notation:40 "∀'" τ => Ty.all τ
-- Shift free variables (for substitution under binders)
partial def Ty.shift (cutoff : Nat) : Ty → Ty
| .var n => if n >= cutoff then .var (n + 1) else .var n
| .unit => .unit
| .bool => .bool
| .arr α β => .arr (α.shift cutoff) (β.shift cutoff)
| .all τ => .all (τ.shift (cutoff + 1))
-- Type substitution (substitute type variable 0)
partial def Ty.subst (body : Ty) (arg : Ty) (depth : Nat := 0) : Ty :=
match body with
| .var n =>
if n == depth then arg
else if n > depth then .var (n - 1)
else .var n
| .unit => .unit
| .bool => .bool
| .arr α β => .arr (α.subst arg depth) (β.subst arg depth)
| .all τ => .all (τ.subst (arg.shift 0) (depth + 1))
-- ═══════════════════════════════════════════════════════════════════════════════
-- TERMS (extrinsically typed)
-- ═══════════════════════════════════════════════════════════════════════════════
inductive Term where
-- STLC
| var : Nat → Term -- Variable (de Bruijn)
| lam : Ty → Term → Term -- λx:τ. e
| app : Term → Term → Term -- e₁ e₂
| tt : Term -- unit
| true : Term
| false : Term
| ite : Term → Term → Term → Term -- if c then t else e
-- System F
| tlam : Term → Term -- Λ. e (type abstraction)
| tapp : Term → Ty → Term -- e [τ] (type application)
deriving Repr
-- ═══════════════════════════════════════════════════════════════════════════════
-- TYPE CHECKING
-- ═══════════════════════════════════════════════════════════════════════════════
-- Context: list of types
abbrev TyCtx := List Ty
-- Type checking (returns Option Ty)
partial def typeCheck (tyDepth : Nat) (ctx : TyCtx) : Term → Option Ty
| .var n => ctx.get? n
| .lam τ body => do
let bodyTy ← typeCheck tyDepth (τ :: ctx) body
return (τ ⟶ bodyTy)
| .app f a => do
let fTy ← typeCheck tyDepth ctx f
let aTy ← typeCheck tyDepth ctx a
match fTy with
| .arr α β => if α == aTy then some β else none
| _ => none
| .tt => some .unit
| .true => some .bool
| .false => some .bool
| .ite c t e => do
let cTy ← typeCheck tyDepth ctx c
if cTy != .bool then none else
let tTy ← typeCheck tyDepth ctx t
let eTy ← typeCheck tyDepth ctx e
if tTy == eTy then some tTy else none
| .tlam body => do
-- Under a type binder, shift all type variables in context
let ctx' := ctx.map (Ty.shift 0)
let bodyTy ← typeCheck (tyDepth + 1) ctx' body
return (∀' bodyTy)
| .tapp e τ => do
let eTy ← typeCheck tyDepth ctx e
match eTy with
| .all body => some (body.subst τ)
| _ => none
-- ═══════════════════════════════════════════════════════════════════════════════
-- CODE GENERATION
-- ═══════════════════════════════════════════════════════════════════════════════
def varName (n : Nat) : String := s!"x{n}"
def codegenTy : Ty → String
| .var n => s!"a{n}"
| .unit => "Unit"
| .bool => "Boolean"
| .arr α β => s!"({codegenTy α} -> {codegenTy β})"
| .all τ => s!"(forall a. {codegenTy τ})"
def codegen (t : Term) (depth : Nat := 0) : String :=
match t with
| .var n => varName (depth - n - 1)
| .lam τ body => s!"(\\{varName depth} -> {codegen body (depth + 1)})"
| .app f a => s!"({codegen f depth} {codegen a depth})"
| .tt => "unit"
| .true => "true"
| .false => "false"
| .ite c t e => s!"(if {codegen c depth} then {codegen t depth} else {codegen e depth})"
-- Type abstraction and application are ERASED
| .tlam body => codegen body depth
| .tapp e _ => codegen e depth
-- ═══════════════════════════════════════════════════════════════════════════════
-- EXAMPLES
-- ═══════════════════════════════════════════════════════════════════════════════
-- Polymorphic identity: Λα. λx:α. x
-- Type: ∀α. α → α
def polyId : Term := .tlam (.lam (.var 0) (.var 0))
#eval codegen polyId -- "(\x0 -> x0)"
#eval typeCheck 0 [] polyId -- some (∀' (.var 0 ⟶ .var 0))
-- Polymorphic const: Λα. Λβ. λa:α. λb:β. a
-- Type: ∀α. ∀β. α → β → α
def polyConst : Term :=
.tlam (.tlam (.lam (.var 1) (.lam (.var 0) (.var 1))))
#eval codegen polyConst -- "(\x0 -> (\x1 -> x0))"
#eval typeCheck 0 [] polyConst -- some (∀' (∀' (.var 1 ⟶ .var 0 ⟶ .var 1)))
-- Polymorphic flip: Λα. Λβ. Λγ. λf:(α→β→γ). λb:β. λa:α. f a b
def polyFlip : Term :=
.tlam (.tlam (.tlam (
.lam ((.var 2) ⟶ (.var 1) ⟶ (.var 0)) (
.lam (.var 1) (
.lam (.var 2) (
.app (.app (.var 2) (.var 0)) (.var 1)))))))
#eval codegen polyFlip -- "(\x0 -> (\x1 -> (\x2 -> ((x0 x2) x1))))"
#eval typeCheck 0 [] polyFlip
-- Polymorphic compose: Λα. Λβ. Λγ. λf:(β→γ). λg:(α→β). λx:α. f (g x)
def polyCompose : Term :=
.tlam (.tlam (.tlam (
.lam ((.var 1) ⟶ (.var 0)) (
.lam ((.var 2) ⟶ (.var 1)) (
.lam (.var 2) (
.app (.var 2) (.app (.var 1) (.var 0))))))))
#eval codegen polyCompose -- "(\x0 -> (\x1 -> (\x2 -> (x0 (x1 x2)))))"
#eval typeCheck 0 [] polyCompose
-- ═══════════════════════════════════════════════════════════════════════════════
-- TYPE INSTANTIATION
-- ═══════════════════════════════════════════════════════════════════════════════
-- Apply polyId to Bool to get id_Bool : Bool → Bool
def idBool : Term := .tapp polyId .bool
#eval codegen idBool -- "(\x0 -> x0)" (same as polyId - types erased!)
#eval typeCheck 0 [] idBool -- some (.bool ⟶ .bool)
-- Apply idBool to true
def idTrue : Term := .app idBool .true
#eval codegen idTrue -- "((\x0 -> x0) true)"
#eval typeCheck 0 [] idTrue -- some .bool
-- ═══════════════════════════════════════════════════════════════════════════════
-- VERIFIED ERASURE PROPERTIES
-- ═══════════════════════════════════════════════════════════════════════════════
-- Type application doesn't change code
theorem tapp_erased (e : Term) (τ : Ty) (d : Nat) :
codegen (.tapp e τ) d = codegen e d := rfl
-- Type abstraction doesn't change code
theorem tlam_erased (body : Term) (d : Nat) :
codegen (.tlam body) d = codegen body d := rfl
-- Corollary: any sequence of type applications/abstractions is erased
theorem tapp_sequence_erased (e : Term) (τ₁ τ₂ : Ty) (d : Nat) :
codegen (.tapp (.tapp e τ₁) τ₂) d = codegen e d := rfl
-- ═══════════════════════════════════════════════════════════════════════════════
-- CHURCH ENCODINGS (demonstrating expressiveness)
-- ═══════════════════════════════════════════════════════════════════════════════
-- Church Boolean type: ∀α. α → α → α
def churchBoolTy : Ty := ∀' (.var 0 ⟶ .var 0 ⟶ .var 0)
-- Church true: Λα. λt:α. λf:α. t
def churchTrue : Term := .tlam (.lam (.var 0) (.lam (.var 0) (.var 1)))
-- Church false: Λα. λt:α. λf:α. f
def churchFalse : Term := .tlam (.lam (.var 0) (.lam (.var 0) (.var 0)))
#eval codegen churchTrue -- "(\x0 -> (\x1 -> x0))"
#eval codegen churchFalse -- "(\x0 -> (\x1 -> x1))"
#eval typeCheck 0 [] churchTrue -- some (∀' (.var 0 ⟶ .var 0 ⟶ .var 0))
#eval typeCheck 0 [] churchFalse -- some (∀' (.var 0 ⟶ .var 0 ⟶ .var 0))
-- Church Nat type: ∀α. (α → α) → α → α
def churchNatTy : Ty := ∀' ((.var 0 ⟶ .var 0) ⟶ .var 0 ⟶ .var 0)
-- Church zero: Λα. λs:(α→α). λz:α. z
def churchZero : Term := .tlam (.lam (.var 0 ⟶ .var 0) (.lam (.var 0) (.var 0)))
-- Church successor: λn:Nat. Λα. λs:(α→α). λz:α. s (n [α] s z)
-- (this is more complex due to recursion, omitted)
#eval codegen churchZero -- "(\x0 -> (\x1 -> x1))"
#eval typeCheck 0 [] churchZero
-- ═══════════════════════════════════════════════════════════════════════════════
-- WHAT WE ACHIEVED
-- ═══════════════════════════════════════════════════════════════════════════════
/-
SYSTEM F IMPLEMENTATION:
✓ Type variables with de Bruijn indices
✓ Type substitution (for type application)
✓ Type abstraction (Λ) and application (e [τ])
✓ Type checking (extrinsic)
✓ Code generation with type erasure
VERIFIED PROPERTIES (by rfl):
✓ tapp_erased: type application produces same code
✓ tlam_erased: type abstraction produces same code
✓ tapp_sequence_erased: multiple type ops are erased
EXAMPLES:
✓ polyId, polyConst, polyFlip, polyCompose
✓ Type instantiation (idBool, idTrue)
✓ Church encodings (booleans, naturals)
TRADE-OFFS:
- Extrinsic typing (separate typeCheck function)
- Less static guarantees than intrinsic approach
- But simpler and fully working!
RELATIONSHIP TO PURESCRIPT:
PureScript erases types at compile time, exactly like this.
The codegen output is valid PureScript modulo syntax details.
Type safety is guaranteed by type checking, not runtime.
FOR INTRINSIC SYSTEM F:
See SystemFComplete.lean for the intrinsic STLC approach.
Extending to full System F intrinsically requires careful
handling of universe levels (Type 1 for interpreting ∀).
-/