co-lambda 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
co_lambda/_sugar.py ADDED
@@ -0,0 +1,74 @@
1
+ """Shared HOAS transcription sugar: fixed-shape notation for writing lambda terms.
2
+
3
+ This module is one of the four strictly separated kinds (codec / sugar / runtime / pure-lambda
4
+ compiler source). Everything here is a one-to-one notational transcription: parameters are Builders
5
+ (or Python callables standing for object-language binders), the produced shape is literal at the
6
+ call site, and nothing is computed from data. The pure-lambda source modules import their writing
7
+ notation from here (and from ``_dsl``/``_pybuild``), so no Python helper definitions live next to
8
+ the lambda terms themselves.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from co_lambda._dsl import Builder, app, lam
14
+ from co_lambda._prelude import FALSE, MAP, SCOTT_CONS, SCOTT_NIL, TRUE
15
+
16
+
17
+ def ap(function: Builder, *arguments: Builder) -> Builder:
18
+ """Left-folded application: ``ap(f, x, y, z)`` is ``((f x) y) z``. A thin alias for calling the
19
+ builder directly (``function(*arguments)``), kept for the existing call sites."""
20
+ return function(*arguments)
21
+
22
+
23
+ def let(value: Builder, body) -> Builder:
24
+ """Bind ``value`` for ``body`` (a Python ``lambda`` over the bound Builder)."""
25
+ return app(lam(body), value)
26
+
27
+
28
+ def split_pair(pair_value: Builder, body) -> Builder:
29
+ """Destructure a continuation pair (``pair k = k first second``) for ``body`` (a Python
30
+ ``lambda`` over the two bound Builders)."""
31
+ return app(pair_value, lam(lambda first: lam(lambda second: body(first, second))))
32
+
33
+
34
+ def pair(first: Builder, second: Builder) -> Builder:
35
+ """The continuation pair: ``lambda k. k first second``."""
36
+ return lam(lambda consume: ap(consume, first, second))
37
+
38
+
39
+ def split_triple(triple_value: Builder, body) -> Builder:
40
+ """Destructure a right-nested triple ``pair(a, pair(b, c))`` for ``body(a, b, c)``."""
41
+ return split_pair(triple_value, lambda first, rest: split_pair(
42
+ rest, lambda second, third: body(first, second, third),
43
+ ))
44
+
45
+
46
+ def split_quad(quad_value: Builder, body) -> Builder:
47
+ """Destructure a right-nested quadruple ``pair(a, pair(b, pair(c, d)))`` for ``body(a, b, c, d)``."""
48
+ return split_pair(quad_value, lambda first, rest: split_triple(
49
+ rest, lambda second, third, fourth: body(first, second, third, fourth),
50
+ ))
51
+
52
+
53
+ def pair_first(pair_value: Builder) -> Builder:
54
+ return app(pair_value, TRUE)
55
+
56
+
57
+ def pair_second(pair_value: Builder) -> Builder:
58
+ return app(pair_value, FALSE)
59
+
60
+
61
+ def cons(head: Builder, tail: Builder) -> Builder:
62
+ return ap(SCOTT_CONS, head, tail)
63
+
64
+
65
+ def one(element: Builder) -> Builder:
66
+ return cons(element, SCOTT_NIL)
67
+
68
+
69
+ def two(first: Builder, second: Builder) -> Builder:
70
+ return cons(first, cons(second, SCOTT_NIL))
71
+
72
+
73
+ def map_list(function: Builder, source: Builder) -> Builder:
74
+ return ap(MAP, function, source)
@@ -0,0 +1,370 @@
1
+ """Simple-typability as a lambda term: algorithm-W (STLC) written in the pure calculus.
2
+
3
+ This is the soundness certificate the specializer needs. ``TYPABLE`` consumes a quoted term (the
4
+ ``QVar``/``QLam``/``QApp`` Scott value ``quote`` produces) and returns a Church boolean: whether the
5
+ term is simply typable, which is a sound certificate of strong normalization (so the strict
6
+ call-by-value runtime is safe). The Python ``_specialize._Inference`` is the specification this
7
+ ports and the test oracle it is checked against; it is not on the compile path.
8
+
9
+ This module is pure lambda calculus: every top-level binding is a ``Builder`` (a ``@curry``-decorated
10
+ ``def`` IS a Builder). The Python-side verdict readers (``is_typable_lambda``, ``typable_bu_lambda``)
11
+ live at the boundary (``_specialize``).
12
+
13
+ The encoding is monomorphic algorithm-W (one fresh monotype per binder, no generalization), threading
14
+ a state of ``(next-fresh-id, substitution, failed-flag)`` purely:
15
+
16
+ * Types are a two-constructor Scott value: ``TVAR id`` (``id`` a BinNat, so allocating and comparing
17
+ type variables is O(log id)) and ``TARROW l r``.
18
+ * The substitution is a function ``id -> Option Type`` (the empty map is ``lambda id. NONE``; extension
19
+ shadows by id-equality), so ``resolve`` follows the chain and ``occurs`` walks the resolved type.
20
+ * ``unify`` threads ``(substitution, failed)`` and sets ``failed`` when the occurs check fires, which is
21
+ exactly why the self-application ``x x`` (constraint ``alpha = alpha -> beta``) is untypable.
22
+ * ``infer`` threads the whole state and returns ``(state, type)``; a binder extends the typing context
23
+ (a Scott list indexed by de Bruijn index) with a fresh monotype. It short-circuits once failed, so an
24
+ untypable term is rejected as soon as the first occurs check fires.
25
+
26
+ Termination: ``infer`` recurses structurally on the term; the occurs check keeps the substitution
27
+ acyclic, so ``resolve``/``occurs``/``unify`` recurse on finite type trees. The verdict is therefore a
28
+ normal-form Church boolean.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from co_lambda._binnat import BIN_ADD, BIN_EQUAL, BIN_SUCC, BIN_ZERO
34
+ from co_lambda._dsl import Builder, app, lam
35
+ from co_lambda._prelude import FALSE, IS_ZERO, OR, PRED, TRUE, Y
36
+ from co_lambda._sugar import ap, let, pair, split_pair, split_quad, split_triple
37
+
38
+ # --- booleans -------------------------------------------------------------------------------------
39
+ _NOT: Builder = lam(lambda boolean: ap(boolean, FALSE, TRUE))
40
+
41
+ # Option: SOME carries a value, NONE is empty; consumed as ``option some_handler none_value``.
42
+ _SOME: Builder = lam(lambda value: lam(lambda some_handler: lam(lambda none_value: app(some_handler, value))))
43
+ _NONE: Builder = lam(lambda some_handler: lam(lambda none_value: none_value))
44
+
45
+ # Scott list: consumed as ``list nil_value cons_handler``.
46
+ _NIL: Builder = lam(lambda nil_value: lam(lambda cons_handler: nil_value))
47
+ _CONS: Builder = lam(lambda head: lam(lambda tail: lam(lambda nil_value: lam(lambda cons_handler: ap(cons_handler, head, tail)))))
48
+
49
+ # --- types: TVAR id | TARROW left right (consumed as ``type var_handler arrow_handler``) ----------
50
+ _TVAR: Builder = lam(lambda identifier: lam(lambda var_handler: lam(lambda arrow_handler: app(var_handler, identifier))))
51
+ _TARROW: Builder = lam(lambda left: lam(lambda right: lam(lambda var_handler: lam(lambda arrow_handler: ap(arrow_handler, left, right)))))
52
+
53
+ # --- substitution: a function id -> Option Type --------------------------------------------------
54
+ _EMPTY_SUBST: Builder = lam(lambda identifier: _NONE)
55
+ # extend subst id type = a new map shadowing ``id`` with ``SOME type``.
56
+ _EXTEND: Builder = lam(lambda subst: lam(lambda identifier: lam(lambda bound: lam(lambda lookup: ap(
57
+ ap(BIN_EQUAL, lookup, identifier),
58
+ app(_SOME, bound),
59
+ app(subst, lookup),
60
+ )))))
61
+
62
+ # resolve subst type: follow the substitution chain to the representative type.
63
+ _RESOLVE: Builder = app(Y, lam(lambda self_recursion: lam(lambda subst: lam(lambda type_: ap(
64
+ type_,
65
+ lam(lambda identifier: ap(
66
+ app(subst, identifier),
67
+ lam(lambda found: ap(self_recursion, subst, found)),
68
+ app(_TVAR, identifier),
69
+ )),
70
+ lam(lambda left: lam(lambda right: ap(_TARROW, left, right))),
71
+ )))))
72
+
73
+ # occurs subst id type: whether ``id`` occurs in the resolved ``type`` (the occurs check).
74
+ _OCCURS: Builder = app(Y, lam(lambda self_recursion: lam(lambda subst: lam(lambda identifier: lam(lambda type_: let(
75
+ ap(_RESOLVE, subst, type_),
76
+ lambda resolved: ap(
77
+ resolved,
78
+ lam(lambda other: ap(BIN_EQUAL, other, identifier)),
79
+ lam(lambda left: lam(lambda right: ap(
80
+ OR,
81
+ ap(self_recursion, subst, identifier, left),
82
+ ap(self_recursion, subst, identifier, right),
83
+ ))),
84
+ ),
85
+ ))))))
86
+
87
+ # bind subst id type: if the occurs check fires, fail; else extend the substitution. The result is a
88
+ # pair (substitution, failed).
89
+ _BIND: Builder = lam(lambda subst: lam(lambda identifier: lam(lambda bound: ap(
90
+ ap(_OCCURS, subst, identifier, bound),
91
+ pair(subst, TRUE),
92
+ pair(ap(_EXTEND, subst, identifier, bound), FALSE),
93
+ ))))
94
+
95
+ # unify state a b, with state = (substitution, failed): unify the two types, threading the state.
96
+ # Short-circuits when already failed; otherwise resolves both sides and matches the four shape cases:
97
+ # var/var (equal: nothing; else bind), var/arrow and arrow/var (bind after occurs check), arrow/arrow
98
+ # (unify the components left to right).
99
+ _UNIFY: Builder = app(Y, lam(lambda self_recursion: lam(lambda state: lam(lambda left_type: lam(lambda right_type: split_pair(
100
+ state,
101
+ lambda subst, failed: ap(
102
+ failed,
103
+ state,
104
+ let(ap(_RESOLVE, subst, left_type), lambda left: let(ap(_RESOLVE, subst, right_type), lambda right: ap(
105
+ left,
106
+ lam(lambda left_id: ap(
107
+ right,
108
+ lam(lambda right_id: ap(ap(BIN_EQUAL, left_id, right_id), state, ap(_BIND, subst, left_id, right))),
109
+ lam(lambda right_left: lam(lambda right_right: ap(_BIND, subst, left_id, right))),
110
+ )),
111
+ lam(lambda left_left: lam(lambda left_right: ap(
112
+ right,
113
+ lam(lambda right_id: ap(_BIND, subst, right_id, left)),
114
+ lam(lambda right_left: lam(lambda right_right: ap(
115
+ self_recursion,
116
+ ap(self_recursion, state, left_left, right_left),
117
+ left_right,
118
+ right_right,
119
+ ))),
120
+ ))),
121
+ ))),
122
+ ),
123
+ ))))))
124
+
125
+ # --- inference state: (next-fresh-id, (substitution, failed)) ------------------------------------
126
+ # The fresh-id counter is a BinNat, so allocating and comparing type variables is O(log id).
127
+ _INITIAL_STATE: Builder = pair(BIN_ZERO, pair(_EMPTY_SUBST, FALSE))
128
+
129
+ # fresh state = ((next+1, subst, failed), TVAR next): the new state and the fresh type variable.
130
+ _FRESH: Builder = lam(lambda state: split_triple(
131
+ state,
132
+ lambda next_id, subst, failed: pair(
133
+ pair(app(BIN_SUCC, next_id), pair(subst, failed)),
134
+ app(_TVAR, next_id),
135
+ ),
136
+ ))
137
+
138
+ # unify_state state a b: apply unify to the (substitution, failed) part of the inference state.
139
+ _UNIFY_STATE: Builder = lam(lambda state: lam(lambda left_type: lam(lambda right_type: split_triple(
140
+ state,
141
+ lambda next_id, subst, failed: split_pair(
142
+ ap(_UNIFY, pair(subst, failed), left_type, right_type),
143
+ lambda new_subst, new_failed: pair(next_id, pair(new_subst, new_failed)),
144
+ ),
145
+ ))))
146
+
147
+ # lookup context index: the type at de Bruijn ``index`` in the context, as an Option.
148
+ _LOOKUP: Builder = app(Y, lam(lambda self_recursion: lam(lambda context: lam(lambda index: ap(
149
+ context,
150
+ _NONE,
151
+ lam(lambda head: lam(lambda tail: ap(
152
+ app(IS_ZERO, index),
153
+ app(_SOME, head),
154
+ ap(self_recursion, tail, app(PRED, index)),
155
+ ))),
156
+ )))))
157
+
158
+ # infer state context node: infer the node's type, threading the state; returns (state, type).
159
+ # Once the state has failed (an occurs check fired), inference short-circuits: it returns a dummy type
160
+ # without recursing, so an untypable term (the compiler's Y, factorial, ...) is rejected as soon as the
161
+ # first self-application fails rather than building the whole constraint tree. This mirrors the Python
162
+ # ``_Inference.infer`` early return and is what keeps the certificate fast on the large untypable terms.
163
+ _INFER: Builder = app(Y, lam(lambda self_recursion: lam(lambda state: lam(lambda context: lam(lambda node: split_triple(
164
+ state,
165
+ lambda next_id, subst, failed: ap(
166
+ failed,
167
+ pair(state, app(_TVAR, next_id)), # already failed: short-circuit with a dummy type
168
+ ap(
169
+ node,
170
+ lam(lambda index: ap( # QVar index
171
+ ap(_LOOKUP, context, index),
172
+ lam(lambda found: pair(state, found)), # bound: its context type
173
+ app(_FRESH, state), # free: a fresh type variable
174
+ )),
175
+ lam(lambda body: split_pair( # QLam body
176
+ app(_FRESH, state),
177
+ lambda state_after_fresh, parameter: split_pair(
178
+ ap(self_recursion, state_after_fresh, ap(_CONS, parameter, context), body),
179
+ lambda state_after_body, result: pair(state_after_body, ap(_TARROW, parameter, result)),
180
+ ),
181
+ )),
182
+ lam(lambda function: lam(lambda argument: split_pair( # QApp function argument
183
+ ap(self_recursion, state, context, function),
184
+ lambda state_after_function, function_type: split_pair(
185
+ ap(self_recursion, state_after_function, context, argument),
186
+ lambda state_after_argument, argument_type: split_pair(
187
+ app(_FRESH, state_after_argument),
188
+ lambda state_after_fresh, result: pair(
189
+ ap(_UNIFY_STATE, state_after_fresh, function_type, ap(_TARROW, argument_type, result)),
190
+ result,
191
+ ),
192
+ ),
193
+ ),
194
+ ))),
195
+ ),
196
+ ),
197
+ ))))))
198
+
199
+
200
+ # TYPABLE quoted = run inference from the initial state on the empty context, read the failed flag.
201
+ # A closed term is simply typable iff inference does not fail (no occurs-check violation).
202
+ TYPABLE: Builder = lam(lambda quoted: split_pair(
203
+ ap(_INFER, _INITIAL_STATE, _NIL, quoted),
204
+ lambda final_state, _type: split_triple(
205
+ final_state,
206
+ lambda next_id, subst, failed: app(_NOT, failed),
207
+ ),
208
+ ))
209
+
210
+
211
+ # === Bottom-up principal typing: one path-free fold the interpreter tables per distinct sub-term ===
212
+ # ``TYPABLE`` above threads a fresh-id/substitution state, so the interpreter cannot share its
213
+ # per-sub-term inference (the state differs at every position) and the substitution is an O(chain)
214
+ # function. ``PRINCIPAL`` is PATH-FREE -- it takes only the node -- so ``app(PRINCIPAL, sub)`` is the
215
+ # same interned node wherever ``sub`` occurs and the interpreter tables it ONCE per distinct sub-term
216
+ # (the compiler's combinators reuse sub-combinators heavily, so this is the win). Each ``App`` unifies
217
+ # in a FRESH LOCAL substitution, resolved and discarded, so there is no global chain. Type-variable
218
+ # ids are BinNats (O(log) equality); de Bruijn indices stay Church (small, bounded by depth) and are
219
+ # converted to BinNat only where they seed a type-variable id.
220
+ #
221
+ # A result is ``(next-fresh-id, context, type, failed)``: ``context`` is a Scott list of types indexed
222
+ # by de Bruijn index (a fresh type per binder so siblings constrain them independently); ``type`` is
223
+ # the sub-term's type with the local substitution applied; ``next-fresh-id`` (a BinNat) bounds the
224
+ # type-variable ids used, so a sibling is renamed apart by adding it; ``failed`` is the occurs-check
225
+ # verdict for the whole sub-tree.
226
+
227
+ # Convert a Church numeral (a de Bruijn index from ``quote``) to a BinNat type-variable id.
228
+ _CHURCH_TO_BINNAT: Builder = lam(lambda church_value: ap(church_value, BIN_SUCC, BIN_ZERO))
229
+
230
+ # build_vars count = [TVAR 0, TVAR 1, ..., TVAR (count-1)]: the fresh context for a variable at de
231
+ # Bruijn index count-1, one distinct fresh type per enclosing binder (BinNat ids).
232
+ _BUILD_VARS_GO: Builder = app(Y, lam(lambda self_recursion: lam(lambda current: lam(lambda count: ap(
233
+ ap(BIN_EQUAL, current, count),
234
+ _NIL,
235
+ ap(_CONS, app(_TVAR, current), ap(self_recursion, app(BIN_SUCC, current), count)),
236
+ )))))
237
+
238
+ _BUILD_VARS: Builder = lam(lambda count: ap(_BUILD_VARS_GO, BIN_ZERO, count))
239
+
240
+ # shift_type offset type: add ``offset`` to every type-variable id (rename a whole type apart).
241
+ _SHIFT_TYPE: Builder = app(Y, lam(lambda self_recursion: lam(lambda offset: lam(lambda type_: ap(
242
+ type_,
243
+ lam(lambda identifier: app(_TVAR, ap(BIN_ADD, offset, identifier))),
244
+ lam(lambda left: lam(lambda right: ap(
245
+ _TARROW,
246
+ ap(self_recursion, offset, left), ap(self_recursion, offset, right),
247
+ ))),
248
+ )))))
249
+
250
+ # shift_context offset context: rename every type in a context apart by ``offset``.
251
+ _SHIFT_CONTEXT: Builder = app(Y, lam(lambda self_recursion: lam(lambda offset: lam(lambda context: ap(
252
+ context,
253
+ _NIL,
254
+ lam(lambda head: lam(lambda tail: ap(
255
+ _CONS,
256
+ ap(_SHIFT_TYPE, offset, head), ap(self_recursion, offset, tail),
257
+ ))),
258
+ )))))
259
+
260
+ # apply_subst subst type: resolve ``type`` deeply, so the result carries no residual substitution.
261
+ _APPLY_SUBST: Builder = app(Y, lam(lambda self_recursion: lam(lambda subst: lam(lambda type_: ap(
262
+ ap(_RESOLVE, subst, type_),
263
+ lam(lambda identifier: app(_TVAR, identifier)),
264
+ lam(lambda left: lam(lambda right: ap(
265
+ _TARROW,
266
+ ap(self_recursion, subst, left), ap(self_recursion, subst, right),
267
+ ))),
268
+ )))))
269
+
270
+ _APPLY_SUBST_CONTEXT: Builder = app(Y, lam(lambda self_recursion: lam(lambda subst: lam(lambda context: ap(
271
+ context,
272
+ _NIL,
273
+ lam(lambda head: lam(lambda tail: ap(
274
+ _CONS,
275
+ ap(_APPLY_SUBST, subst, head), ap(self_recursion, subst, tail),
276
+ ))),
277
+ )))))
278
+
279
+ # merge state a b, state = (subst, failed): unify the shared prefix of two contexts (same de Bruijn
280
+ # indices) and keep the tail of the longer; returns (state, merged-context).
281
+ _MERGE: Builder = app(Y, lam(lambda self_recursion: lam(lambda state: lam(lambda a: lam(lambda b: ap(
282
+ a,
283
+ pair(state, b), # a is nil: the merge is b
284
+ lam(lambda head_a: lam(lambda tail_a: ap(
285
+ b,
286
+ pair(state, ap(_CONS, head_a, tail_a)), # b is nil: the merge is a
287
+ lam(lambda head_b: lam(lambda tail_b: split_pair(
288
+ ap(self_recursion, ap(_UNIFY, state, head_a, head_b), tail_a, tail_b),
289
+ lambda merged_state, merged_tail: pair(merged_state, ap(_CONS, head_a, merged_tail)),
290
+ ))),
291
+ ))),
292
+ ))))))
293
+
294
+ _INITIAL_PAIR: Builder = pair(_EMPTY_SUBST, FALSE)
295
+
296
+
297
+ # principal node: the bottom-up principal typing of a quoted term, the path-free fold described above.
298
+ PRINCIPAL: Builder = app(Y, lam(lambda self_recursion: lam(lambda node: ap(
299
+ node,
300
+ # QVar index: context [TVAR 0 .. TVAR index], type TVAR index (ids as BinNat).
301
+ lam(lambda index: let(app(_CHURCH_TO_BINNAT, index), lambda binnat_index: pair(
302
+ app(BIN_SUCC, binnat_index),
303
+ pair(
304
+ app(_BUILD_VARS, app(BIN_SUCC, binnat_index)),
305
+ pair(app(_TVAR, binnat_index), FALSE),
306
+ ),
307
+ ))),
308
+ # QLam body: discharge de Bruijn index 0 (the binder's type) from the body's context.
309
+ lam(lambda body: split_quad(
310
+ app(self_recursion, body),
311
+ lambda next_body, context_body, type_body, failed_body: ap(
312
+ context_body,
313
+ # body uses no enclosing binder: the parameter is a fresh, unconstrained type.
314
+ pair(
315
+ app(BIN_SUCC, next_body),
316
+ pair(_NIL, pair(ap(_TARROW, app(_TVAR, next_body), type_body), failed_body)),
317
+ ),
318
+ lam(lambda parameter: lam(lambda rest: pair(
319
+ next_body,
320
+ pair(rest, pair(ap(_TARROW, parameter, type_body), failed_body)),
321
+ ))),
322
+ ),
323
+ )),
324
+ # QApp function argument: rename the argument's type-var band apart, merge the shared context,
325
+ # then unify the function's type with (argument-type -> fresh result).
326
+ lam(lambda function: lam(lambda argument: split_quad(
327
+ app(self_recursion, function),
328
+ lambda next_f, context_f, type_f, failed_f: split_quad(
329
+ app(self_recursion, argument),
330
+ lambda next_a, context_a, type_a, failed_a: ap(
331
+ ap(OR, failed_f, failed_a),
332
+ pair(
333
+ app(BIN_SUCC, ap(BIN_ADD, next_f, next_a)),
334
+ pair(_NIL, pair(app(_TVAR, BIN_ZERO), TRUE)),
335
+ ),
336
+ let(ap(BIN_ADD, next_f, next_a), lambda total: let(
337
+ ap(_SHIFT_CONTEXT, next_f, context_a), lambda context_a_shifted: let(
338
+ ap(_SHIFT_TYPE, next_f, type_a), lambda type_a_shifted: let(
339
+ app(_TVAR, total), lambda result_type: split_pair(
340
+ ap(_MERGE, _INITIAL_PAIR, context_f, context_a_shifted),
341
+ lambda merged_state, merged_context: split_pair(
342
+ ap(_UNIFY, merged_state, type_f, ap(_TARROW, type_a_shifted, result_type)),
343
+ lambda final_subst, final_failed: ap(
344
+ final_failed,
345
+ pair(
346
+ app(BIN_SUCC, total),
347
+ pair(_NIL, pair(app(_TVAR, BIN_ZERO), TRUE)),
348
+ ),
349
+ pair(
350
+ app(BIN_SUCC, total),
351
+ pair(
352
+ ap(_APPLY_SUBST_CONTEXT, final_subst, merged_context),
353
+ pair(ap(_APPLY_SUBST, final_subst, result_type), FALSE),
354
+ ),
355
+ ),
356
+ ),
357
+ ),
358
+ )))),
359
+ ),
360
+ ),
361
+ ),
362
+ ))),
363
+ ))))
364
+
365
+
366
+ # TYPABLE_BU quoted: simply typable iff the bottom-up principal typing has no occurs-check failure.
367
+ TYPABLE_BU: Builder = lam(lambda quoted: split_quad(
368
+ app(PRINCIPAL, quoted),
369
+ lambda next_id, context, type_, failed: app(_NOT, failed),
370
+ ))
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: co-lambda
3
+ Version: 0.5.0
4
+ Summary: A least-fixpoint first-order-shape-relation interpreter for the lambda-calculus
5
+ Project-URL: Repository, https://github.com/Atry/MIXINv2
6
+ Author-email: "Yang, Bo" <yang-bo@yang-bo.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Python: >=3.11
13
+ Requires-Dist: fixpoints
14
+ Requires-Dist: typing-extensions>=4.1.0
15
+ Description-Content-Type: text/markdown
16
+
17
+ # co_lambda
18
+
19
+ A first-order-shape-relation interpreter for the lambda-calculus, realizing the
20
+ semantics of the paper `papers/co-lambda/first-order.tex`, depending on `fixpoints`.
21
+
22
+ A lambda-term's tree is the readout of a single first-order weak-head **shape relation** `Sh`
23
+ over term positions. The shape at a position is single-valued, so there is no set to aggregate.
24
+ `readout(node)` resolves each position's head via its `Sh` and descends.
25
+
26
+ Because positions are **interned** (structurally-equal positions are one object, identity is a
27
+ pointer test), a cyclic structure has finitely many positions and the least-fixpoint reading
28
+ folds it into a finite rational tree where head reduction would unfold forever. So the readout
29
+ terminates on every rational tree, and decides an unproductive cycle as the meaningless leaf in
30
+ finite time, where head reduction diverges. Interning is the *finest* instance of a pluggable
31
+ **position congruence** (see below); a coarser sound congruence folds more.
32
+
33
+ `readout` has two re-entry policies:
34
+
35
+ - `fold_cycles=True` (default) is the least fixpoint `lfp`, the denotation: a guarded cycle
36
+ folds into a finite rational graph (`render` prints it with `#N` back-references); the only
37
+ leaves are variables and the meaningless `⊥`.
38
+ - `fold_cycles=False` is the finite-budget first-iteration reading `T↑1`: a re-entered guarded
39
+ cycle is cut to the distinct guarded-cut leaf `∅` (the hole where the budget stopped on a
40
+ productive cycle), kept separate from the meaningless `⊥` (an unproductive cycle, a position
41
+ with no shape). `∅` never appears in the least fixpoint.
42
+
43
+ The calculus is **pure** (`Var`/`Lam`/`App`): no recursion binder is needed. The `Y` combinator
44
+ produces the structural repetition that interning folds, so:
45
+
46
+ - `Y (cons 0)` (the cyclic stream `r = cons 0 r`) folds to a finite rational tree.
47
+ - `Ω = (λx.xx)(λx.xx)` and `Y (λx.x)` (i.e. `letrec x = x`) are unproductive cycles: they read
48
+ out as `⊥` under both readings.
49
+
50
+ The fold/cut is taken only at **closed** positions, so a folded back-reference never misreads a
51
+ free de Bruijn variable.
52
+
53
+ ## Position congruence (a second parameter)
54
+
55
+ *Which* positions count as "the same" when the readout folds is a parameter, a **position
56
+ congruence** (`Definition def:congruence` in the paper). `readout(node, congruence=...)` keys the
57
+ fold on `congruence.key(node)` instead of raw object identity. A congruence is **sound** when it
58
+ is contained in tree equality (it never folds positions with different trees); stated over the
59
+ full signature, soundness is the congruence law read coinductively, so a well-formed congruence
60
+ folds without changing the denotation. `_congruence.py` provides four instances, from finest to
61
+ coarser:
62
+
63
+ - `IdentityCongruence` (the default): syntactic de Bruijn identity, `key = id(node)`, a pointer
64
+ test. The finest instance; reproduces the pure-interning readout exactly.
65
+ - `EGraphCongruence`: a union-find with congruence closure over the *syntactic* constructors. The
66
+ caller asserts sound (tree-equal) merges with `merge(a, b)`; closure propagates them to
67
+ `App`/`Lam` parents, and the readout shares the merged positions. The inductive
68
+ (least-fixpoint) family: it folds whatever finitely many tree-equal pairs generate.
69
+ - `PositionEGraphCongruence`: the faithful `def:congruence`, keyed on the *demanded descent* (the
70
+ shape tree) rather than the syntax. It auto-folds any two positions bisimilar under `Sh` with
71
+ no asserted merge (a redex and its reduct collapse on sight), folding exactly the rational
72
+ fragment. It cannot finitize an infinitely-presented shape graph, so the `Y F 0` witness below
73
+ still diverges: bisimulation alone does not rescue a dead-argument cycle.
74
+ - `DeadSubtermCongruence(rules=...)`: equality up to dead subterms, the key being the syntax with
75
+ every dead-argument slot erased to a canonical placeholder (a tree-preserving map, not a
76
+ congruence closure). The dead slots are recognised by a **library of sound rules**; the caller
77
+ enables a subset. `RecursionArgumentRule` folds the paper's `Y F 0` witness, where a
78
+ constant-headed recursion carries an index it never inspects; `UnusedParameterRule` erases the
79
+ argument of a function that discards it. This is the one reading that folds the witness, which
80
+ neither e-graph can.
81
+
82
+ No parser is provided. Build terms in Python with the HOAS DSL in `_dsl.py` (`lam`, `app`,
83
+ `build`), which compiles to a first-order de Bruijn AST; `_prelude.py` collects example terms
84
+ (combinators, Scott-encoded lists, Church numerals with Peano arithmetic).
@@ -0,0 +1,28 @@
1
+ co_lambda/__init__.py,sha256=jlsjd6vC85uaCQcvtQdTczXIb_U1ggnan7Zj9okwme0,87
2
+ co_lambda/_analysis.py,sha256=jK7hM3Ec5-xgxtg6Xk8X3S_XZMKRBYIm11gfFq0R0kc,5224
3
+ co_lambda/_ast.py,sha256=sbjz-OgsjAbiTZwBWZoUFpHWblplYJcI4bmsyljvpDs,15467
4
+ co_lambda/_binnat.py,sha256=6JZk6AKoMiH1QaJEAkOHHehKAtm3ymXGR1UBEYf6sDs,6589
5
+ co_lambda/_codec.py,sha256=apgi6NqK_BfyeJbYs_gROCLIlefQIbmaAeuhgTK8Fmo,5979
6
+ co_lambda/_compiler_artifact.py,sha256=aTknT4uPoJ27NluVMueRFoEcdXtTgZz5K0lxl9pnWGQ,1828
7
+ co_lambda/_defun_codegen.py,sha256=0Lwn6Z7V71hfhjLmnXev5AekzPPszKgtR3lLnClNrbQ,12942
8
+ co_lambda/_defun_runtime.py,sha256=t9yXzG4qUPI423d5oBXCvxxJVrIIykgaTSyvHrqmxKM,6624
9
+ co_lambda/_defunctionalize.py,sha256=cNTLvZmcrvE0FB2CmpFEC0C1SHX5TveRd4eIC_Bpp_w,18511
10
+ co_lambda/_dsl.py,sha256=CGPRtfobNboS49Bf38rT98v5pyWJP10gHnrl66IoRz8,5901
11
+ co_lambda/_hoas_latex.py,sha256=fa71h7ZE7npe7viISb8mKY70gTExQpgnJ3dOscprlKs,6205
12
+ co_lambda/_latex.py,sha256=mSKFx-LOs1aNPViB5CHHVjwfUHz92NRL5LIIjlXlHkc,2378
13
+ co_lambda/_prelude.py,sha256=QzgCnnEio2vqqmaWDlAzuqEZNNvtWTmuWluZRUV1gvg,6694
14
+ co_lambda/_pyast.py,sha256=l_5kzE4A5xIpfW-m3O1FguOIubp1d5o15aeAn62bQp4,18526
15
+ co_lambda/_pybuild.py,sha256=kDO5e2oF71EBij6-y_GAYW7NnsyAoa1-ofEhHwVPJ_g,12898
16
+ co_lambda/_reduce.py,sha256=38sp9EcbDAaj2qbU3zwfgl9VbteHb3cRBJsTB7125n8,7794
17
+ co_lambda/_shape.py,sha256=uSE65a1028s7dGMLFxvXoN2Sx8COorcb2uIEOWPweqQ,9991
18
+ co_lambda/_sugar.py,sha256=zpqMOEjaPjKXZ9QCKQaJjeOYHjQXmQBdyB40rzPU09Q,2804
19
+ co_lambda/_typecheck.py,sha256=E2b8CRNLprqwkD6REgeTsmDjD4XI3DONqviWoG9Fv3E,18451
20
+ co_lambda/_generated/.gitattributes,sha256=wKvWc-ybPwrbJlEkT0CjI34N0mlTFIt_Gs634YupU6U,319
21
+ co_lambda/_generated/__init__.py,sha256=_XA-qAGrK7esywkAOlTnKDF01A8c3MVFCK4FToC8Y6U,80
22
+ co_lambda/_generated/_generated_defun_compiler_py311.py,sha256=vwGvCqaUszUXKoRkfVeJG-P1TBKlp3wc49ew05aE64o,221357
23
+ co_lambda/_generated/_generated_defun_compiler_py312.py,sha256=jY2a0we8bxa4kj9r3bO0Bc1kC6pcxW4Hct0oUExHl5A,221417
24
+ co_lambda/_generated/_generated_defun_compiler_py313.py,sha256=_rY7M5lWz-HhGKpsd4vA-CkBD7AD1vG2wzswQ7Fwgus,221417
25
+ co_lambda-0.5.0.dist-info/METADATA,sha256=DNF8Xz81YqNQKhAqEMZmDbEzoi9ruqoUzb4XOQ3jLrM,5179
26
+ co_lambda-0.5.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
27
+ co_lambda-0.5.0.dist-info/entry_points.txt,sha256=6aVuCcnD39-E3dCVplkt_qG4Grc_x_q_wTPSS9ppcck,79
28
+ co_lambda-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ co-lambda-regen-compiler = co_lambda._compiler_artifact:main