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/_dsl.py ADDED
@@ -0,0 +1,148 @@
1
+ """A Higher-Order Abstract Syntax surface, compiled to the first-order de Bruijn AST.
2
+
3
+ Object-language binders are written with Python ``lambda``, so terms read isomorphically to
4
+ the lambda-calculus, and Python's lexical scope is the implicit symbol table (no name
5
+ environment, no capture handling). The calculus is pure (``Var``/``Lam``/``App``); cyclic and
6
+ recursive data are written with the ``Y`` combinator (no recursion binder is needed, since
7
+ interning folds the structurally-repeating positions a ``Y`` recursion produces). The Python
8
+ lambdas run once at build time; the result is a pure first-order tree.
9
+
10
+ A ``Builder`` is a HOAS term: an abstract base whose subclasses (``_VarBuilder``, ``_LamBuilder``,
11
+ ``_AppBuilder``) each know how to produce their de Bruijn node at a given binder depth. Calling a
12
+ builder is object-language application (``f(x, y)`` is ``((f x) y)``); the depth-indexed node is
13
+ read with ``.at(depth)`` (or ``build`` at depth zero).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import inspect
19
+
20
+ from abc import ABC, abstractmethod
21
+ from typing import Callable
22
+
23
+ from co_lambda._ast import Node, make_app, make_lam, make_var
24
+
25
+
26
+ class Builder(ABC):
27
+ """A HOAS term: given the current binder depth, produce a de Bruijn node.
28
+
29
+ ``.at(depth)`` memoises the node by binder depth. A builder is a pure function of the binder
30
+ depth, so reusing the same builder object in several places (a shared subterm) yields the same
31
+ node at a given depth. Caching by depth makes a shared-builder DAG build in time linear in its
32
+ distinct nodes instead of unfolding it into a tree: ``build`` reaches each child builder per
33
+ occurrence, so without the cache a builder reused in ``n`` places is re-run ``n`` times. The
34
+ result nodes are interned regardless; this shares the construction work too.
35
+ """
36
+
37
+ __slots__ = ("_node_by_depth",)
38
+
39
+ def __init__(self) -> None:
40
+ self._node_by_depth: dict[int, Node] = {}
41
+
42
+ @abstractmethod
43
+ def _build_at(self, depth: int) -> Node:
44
+ """Produce this term's de Bruijn node at ``depth`` (uncached); implemented per subclass."""
45
+
46
+ def at(self, depth: int) -> Node:
47
+ """This HOAS term's de Bruijn node at the given binder depth (memoised)."""
48
+ node = self._node_by_depth.get(depth)
49
+ if node is None:
50
+ node = self._build_at(depth)
51
+ self._node_by_depth[depth] = node
52
+ return node
53
+
54
+ def __call__(self, *arguments: "Builder") -> "Builder":
55
+ """Object-language application, left-folded: ``f(x, y, z)`` is ``((f x) y) z``."""
56
+ result: Builder = self
57
+ for argument in arguments:
58
+ result = _AppBuilder(result, argument)
59
+ return result
60
+
61
+
62
+ class _VarBuilder(Builder):
63
+ """A bound variable, named by the binder level it refers to (turned into a de Bruijn index by
64
+ subtracting from the current depth)."""
65
+
66
+ __slots__ = ("_level",)
67
+
68
+ def __init__(self, level: int) -> None:
69
+ super().__init__()
70
+ self._level = level
71
+
72
+ def _build_at(self, depth: int) -> Node:
73
+ return make_var(depth - self._level - 1)
74
+
75
+
76
+ class _LamBuilder(Builder):
77
+ """An abstraction: a Python function from the bound-variable builder to the body builder.
78
+
79
+ ``_binder_hint`` is the readable name of the bound variable when it is known up front (``lam_named``,
80
+ used by ``curry`` to keep a decorated function's parameter names); ``None`` means a renderer should
81
+ recover the name from the Python lambda's own parameter. It never affects the built node.
82
+ """
83
+
84
+ __slots__ = ("_body", "_binder_hint")
85
+
86
+ def __init__(self, body: "Callable[[Builder], Builder]", binder_hint: "str | None") -> None:
87
+ super().__init__()
88
+ self._body = body
89
+ self._binder_hint = binder_hint
90
+
91
+ def _build_at(self, depth: int) -> Node:
92
+ return make_lam(self._body(_VarBuilder(depth)).at(depth + 1))
93
+
94
+
95
+ class _AppBuilder(Builder):
96
+ """An application of one builder to another."""
97
+
98
+ __slots__ = ("_function", "_argument")
99
+
100
+ def __init__(self, function: Builder, argument: Builder) -> None:
101
+ super().__init__()
102
+ self._function = function
103
+ self._argument = argument
104
+
105
+ def _build_at(self, depth: int) -> Node:
106
+ return make_app(self._function.at(depth), self._argument.at(depth))
107
+
108
+
109
+ def var_at(level: int) -> Builder:
110
+ return _VarBuilder(level)
111
+
112
+
113
+ def lam(body: "Callable[[Builder], Builder]") -> Builder:
114
+ return _LamBuilder(body, None)
115
+
116
+
117
+ def lam_named(binder_name: str, body: "Callable[[Builder], Builder]") -> Builder:
118
+ """``lam`` with the bound variable's readable name given explicitly, for when the body callable does
119
+ not carry it (a ``curry`` wrapper); the name is for rendering only and never affects the built node."""
120
+ return _LamBuilder(body, binder_name)
121
+
122
+
123
+ def app(function: Builder, argument: Builder) -> Builder:
124
+ return _AppBuilder(function, argument)
125
+
126
+
127
+ def curry(body: "Callable[..., Builder]") -> Builder:
128
+ """Expand an N-argument Python function into a curried HOAS lambda.
129
+
130
+ ``curry(lambda a, b, c: e)`` is ``lam(lambda a: lam(lambda b: lam(lambda c: e)))``: the
131
+ function's parameter count (read with ``inspect``) fixes the binder arity, and each parameter
132
+ is the ``Builder`` for a bound variable. A zero-argument ``body`` builds no binder and returns
133
+ ``body()`` directly. This is sugar for the nested ``lam`` chains that multi-argument terms need.
134
+ """
135
+ parameter_names = tuple(inspect.signature(body).parameters)
136
+ arity = len(parameter_names)
137
+
138
+ def collect(arguments: "list[Builder]") -> Builder:
139
+ if len(arguments) == arity:
140
+ return body(*arguments)
141
+ return lam_named(parameter_names[len(arguments)], lambda bound: collect([*arguments, bound]))
142
+
143
+ return collect([])
144
+
145
+
146
+ def build(term: Builder) -> Node:
147
+ """Finalize a HOAS term into a de Bruijn ``Node``."""
148
+ return term.at(0)
@@ -0,0 +1,5 @@
1
+ # Machine-generated Python: the defunctionalization compiler (DEFUN) self-compiled to a module of
2
+ # @interned closure classes, per Python version (the _py<ver> tag). Do not edit by hand.
3
+ # Regenerate with the console script co-lambda-regen-compiler
4
+ # (python -m co_lambda._compiler_artifact).
5
+ * linguist-generated=true
@@ -0,0 +1 @@
1
+ """Committed defunctionalized (compiled) modules for the bootstrap compiler."""