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/__init__.py +1 -0
- co_lambda/_analysis.py +92 -0
- co_lambda/_ast.py +372 -0
- co_lambda/_binnat.py +170 -0
- co_lambda/_codec.py +147 -0
- co_lambda/_compiler_artifact.py +50 -0
- co_lambda/_defun_codegen.py +321 -0
- co_lambda/_defun_runtime.py +188 -0
- co_lambda/_defunctionalize.py +470 -0
- co_lambda/_dsl.py +148 -0
- co_lambda/_generated/.gitattributes +5 -0
- co_lambda/_generated/__init__.py +1 -0
- co_lambda/_generated/_generated_defun_compiler_py311.py +7586 -0
- co_lambda/_generated/_generated_defun_compiler_py312.py +7586 -0
- co_lambda/_generated/_generated_defun_compiler_py313.py +7586 -0
- co_lambda/_hoas_latex.py +144 -0
- co_lambda/_latex.py +58 -0
- co_lambda/_prelude.py +163 -0
- co_lambda/_pyast.py +418 -0
- co_lambda/_pybuild.py +313 -0
- co_lambda/_reduce.py +145 -0
- co_lambda/_shape.py +224 -0
- co_lambda/_sugar.py +74 -0
- co_lambda/_typecheck.py +370 -0
- co_lambda-0.5.0.dist-info/METADATA +84 -0
- co_lambda-0.5.0.dist-info/RECORD +28 -0
- co_lambda-0.5.0.dist-info/WHEEL +4 -0
- co_lambda-0.5.0.dist-info/entry_points.txt +2 -0
co_lambda/_hoas_latex.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Render a HOAS ``Builder`` term, with readable binder names and named constants, to LaTeX or to text.
|
|
2
|
+
|
|
3
|
+
``_latex.term_to_latex`` renders the built de Bruijn ``Node``: it names binders ``x, y, z, ...`` by level
|
|
4
|
+
and fully expands every constant, so a term built from a library (edit distance over ``Y``, the BinNat
|
|
5
|
+
arithmetic, the Scott list) comes out as one enormous nameless expression. Here we render the ``Builder``
|
|
6
|
+
instead, so a binder shows the name of the Python ``lambda`` parameter that introduced it, and a sub-term
|
|
7
|
+
that *is* a named library constant, recognised by object identity through a caller-supplied ``names`` map,
|
|
8
|
+
shows that name rather than being expanded. A definition therefore reads as itself and cross-references the
|
|
9
|
+
others by name. This is what generates the paper's edit-distance code listing from the source terms.
|
|
10
|
+
|
|
11
|
+
Two ``Style`` presets format the same structure differently: ``MATH_STYLE`` for inline LaTeX math (used in
|
|
12
|
+
the prose), ``TEXT_STYLE`` for the ASCII lambda syntax of an auto-wrapping ``lstlisting`` (the appendix
|
|
13
|
+
code listing, where math would overflow the line). ``names`` maps an ``id`` to the constant's *base* name
|
|
14
|
+
(``"eq"``, ``"Y"``, ``"ed"``); the style turns that base into the rendered token.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import inspect
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Callable, final
|
|
23
|
+
|
|
24
|
+
from co_lambda._dsl import Builder, _AppBuilder, _LamBuilder, _VarBuilder
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@final
|
|
28
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
29
|
+
class Style:
|
|
30
|
+
"""How to format the four syntactic pieces, so one renderer serves both LaTeX math and ASCII text."""
|
|
31
|
+
|
|
32
|
+
constant: Callable[[str], str]
|
|
33
|
+
binder: Callable[[str], str]
|
|
34
|
+
abstraction: Callable[[str, str], str]
|
|
35
|
+
application: Callable[[str, str], str]
|
|
36
|
+
parenthesise: Callable[[str], str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _math_constant(name: str) -> str:
|
|
40
|
+
return name if len(name) == 1 else f"\\mathtt{{{name}}}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _math_binder(name: str) -> str:
|
|
44
|
+
head, _, tail = name.partition("_")
|
|
45
|
+
base = head if len(head) == 1 else f"\\mathit{{{head}}}"
|
|
46
|
+
return base if not tail else f"{base}_{{{tail}}}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
MATH_STYLE: Style = Style(
|
|
50
|
+
constant=_math_constant,
|
|
51
|
+
binder=_math_binder,
|
|
52
|
+
abstraction=lambda binder, body: f"\\lambda {binder}.\\, {body}",
|
|
53
|
+
application=lambda function, argument: f"{function}\\, {argument}",
|
|
54
|
+
parenthesise=lambda inner: f"({inner})",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
TEXT_STYLE: Style = Style(
|
|
58
|
+
constant=lambda name: name,
|
|
59
|
+
binder=lambda name: name,
|
|
60
|
+
abstraction=lambda binder, body: f"\\{binder}. {body}",
|
|
61
|
+
application=lambda function, argument: f"{function} {argument}",
|
|
62
|
+
parenthesise=lambda inner: f"({inner})",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _NamedBinder(Builder):
|
|
67
|
+
"""A stand-in for a bound variable while rendering: never built, only printed by its source name."""
|
|
68
|
+
|
|
69
|
+
__slots__ = ("source_name",)
|
|
70
|
+
|
|
71
|
+
def __init__(self, source_name: str) -> None:
|
|
72
|
+
super().__init__()
|
|
73
|
+
self.source_name = source_name
|
|
74
|
+
|
|
75
|
+
def _build_at(self, depth: int): # pragma: no cover - a rendering placeholder is never built
|
|
76
|
+
raise AssertionError("a _NamedBinder is a rendering placeholder and must never be built")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _binder_source_name(builder: _LamBuilder) -> str:
|
|
80
|
+
"""A lambda's bound-variable source name: its ``_binder_hint`` if set, else the Python parameter name."""
|
|
81
|
+
hint = builder._binder_hint
|
|
82
|
+
if hint is not None:
|
|
83
|
+
return hint
|
|
84
|
+
parameters = tuple(inspect.signature(builder._body).parameters)
|
|
85
|
+
single_name, = parameters
|
|
86
|
+
return single_name
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _fresh(name: str, scope: "tuple[str, ...]") -> str:
|
|
90
|
+
"""Disambiguate a binder name against those already in scope by priming, so two nested binders that
|
|
91
|
+
share a source name stay distinguishable."""
|
|
92
|
+
candidate = name
|
|
93
|
+
while candidate in scope:
|
|
94
|
+
candidate = f"{candidate}'"
|
|
95
|
+
return candidate
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def render(builder: Builder, names: "dict[int, str]", style: Style) -> str:
|
|
99
|
+
"""The rendering of ``builder``, expanding it one level (its own name, if any, is not substituted) and
|
|
100
|
+
rendering every nested named constant in ``names`` by its name."""
|
|
101
|
+
return _render(builder, names, style, is_root=True, scope=())
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _render(
|
|
105
|
+
builder: Builder, names: "dict[int, str]", style: Style, *, is_root: bool, scope: "tuple[str, ...]"
|
|
106
|
+
) -> str:
|
|
107
|
+
if not is_root and id(builder) in names:
|
|
108
|
+
return style.constant(names[id(builder)])
|
|
109
|
+
if isinstance(builder, _NamedBinder):
|
|
110
|
+
return style.binder(builder.source_name)
|
|
111
|
+
if isinstance(builder, _LamBuilder):
|
|
112
|
+
name = _fresh(_binder_source_name(builder), scope)
|
|
113
|
+
body = builder._body(_NamedBinder(name))
|
|
114
|
+
rendered_body = _render(body, names, style, is_root=False, scope=(*scope, name))
|
|
115
|
+
return style.abstraction(style.binder(name), rendered_body)
|
|
116
|
+
if isinstance(builder, _AppBuilder):
|
|
117
|
+
function = _render_function(builder._function, names, style, scope)
|
|
118
|
+
argument = _render_argument(builder._argument, names, style, scope)
|
|
119
|
+
return style.application(function, argument)
|
|
120
|
+
if isinstance(builder, _VarBuilder): # pragma: no cover - rendered terms introduce vars via lambdas
|
|
121
|
+
raise AssertionError("a free _VarBuilder reached the renderer; rendered terms must be closed")
|
|
122
|
+
raise TypeError(f"cannot render {builder!r}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _is_atom(builder: Builder, names: "dict[int, str]") -> bool:
|
|
126
|
+
"""An atom needs no parentheses in argument position: a bound variable or a named constant."""
|
|
127
|
+
return isinstance(builder, (_NamedBinder, _VarBuilder)) or id(builder) in names
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _render_function(
|
|
131
|
+
builder: Builder, names: "dict[int, str]", style: Style, scope: "tuple[str, ...]"
|
|
132
|
+
) -> str:
|
|
133
|
+
# A lambda shown expanded must be parenthesised in function position; a name or an application is bare.
|
|
134
|
+
inner = _render(builder, names, style, is_root=False, scope=scope)
|
|
135
|
+
if isinstance(builder, _LamBuilder) and id(builder) not in names:
|
|
136
|
+
return style.parenthesise(inner)
|
|
137
|
+
return inner
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _render_argument(
|
|
141
|
+
builder: Builder, names: "dict[int, str]", style: Style, scope: "tuple[str, ...]"
|
|
142
|
+
) -> str:
|
|
143
|
+
inner = _render(builder, names, style, is_root=False, scope=scope)
|
|
144
|
+
return inner if _is_atom(builder, names) else style.parenthesise(inner)
|
co_lambda/_latex.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Render a source lambda term to LaTeX with readable bound-variable names.
|
|
2
|
+
|
|
3
|
+
A binder is named by its de Bruijn level (its depth at introduction): the binder introduced at depth
|
|
4
|
+
``d`` is level ``d``, and a ``Var(i)`` seen at depth ``d`` refers to level ``d - 1 - i``. Each binder
|
|
5
|
+
thus has a distinct level, hence a distinct readable name (``x``, ``y``, ``z``, ...), so the rendering
|
|
6
|
+
is unambiguous and reads naturally. The compiler names the same binder ``v{level}`` in the emitted
|
|
7
|
+
Python, so the lambda's name at level ``k`` corresponds to the Python parameter ``v{k}``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from co_lambda._ast import App, Lam, Native, Node, Var
|
|
13
|
+
|
|
14
|
+
# Readable bound-variable names indexed by de Bruijn level; deeper levels fall back to a subscript.
|
|
15
|
+
_READABLE_NAMES = (
|
|
16
|
+
"x", "y", "z", "w", "u", "s", "t", "p", "q", "r",
|
|
17
|
+
"k", "m", "n", "a", "b", "c", "d", "e", "g", "h",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def term_to_latex(node: Node) -> str:
|
|
22
|
+
"""The source term as a LaTeX math string (no surrounding ``$``), with readable names."""
|
|
23
|
+
return _latex(node, 0)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _name(level: int) -> str:
|
|
27
|
+
assert level >= 0, "a closed term references only bound variables, so every level is nonnegative"
|
|
28
|
+
if level < len(_READABLE_NAMES):
|
|
29
|
+
return _READABLE_NAMES[level]
|
|
30
|
+
return f"x_{{{level - len(_READABLE_NAMES) + 1}}}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _latex(node: Node, depth: int) -> str:
|
|
34
|
+
match node:
|
|
35
|
+
case Var(index=index):
|
|
36
|
+
return _name(depth - 1 - index)
|
|
37
|
+
case Lam(body=body):
|
|
38
|
+
return f"\\lambda {_name(depth)}.\\, {_latex(body, depth + 1)}"
|
|
39
|
+
case App(function=function, argument=argument):
|
|
40
|
+
return f"{_function(function, depth)}\\, {_argument(argument, depth)}"
|
|
41
|
+
case Native(arity=arity):
|
|
42
|
+
return f"\\langle\\mathrm{{native}}/{arity}\\rangle"
|
|
43
|
+
case _:
|
|
44
|
+
raise TypeError(f"cannot render {node!r}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _function(node: Node, depth: int) -> str:
|
|
48
|
+
# A lambda in function position must be parenthesised; an application stays bare (left associative).
|
|
49
|
+
if isinstance(node, Lam):
|
|
50
|
+
return f"({_latex(node, depth)})"
|
|
51
|
+
return _latex(node, depth)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _argument(node: Node, depth: int) -> str:
|
|
55
|
+
# Only a variable is atomic in argument position; an application or lambda is parenthesised.
|
|
56
|
+
if isinstance(node, Var):
|
|
57
|
+
return _latex(node, depth)
|
|
58
|
+
return f"({_latex(node, depth)})"
|
co_lambda/_prelude.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Pure-lambda prelude: the combinators and Scott data vocabulary, written as literal HOAS.
|
|
2
|
+
|
|
3
|
+
This module is pure lambda calculus (one of the four strictly separated kinds: codec / sugar /
|
|
4
|
+
runtime / pure-lambda source): every top-level binding is a ``Builder`` lambda term, transcribed
|
|
5
|
+
one-to-one through the ``_dsl`` notation. Encodings of Python data (Church numeral rendering,
|
|
6
|
+
Scott-list building) live in ``_codec``; appliers and other writing sugar live in ``_sugar``; the
|
|
7
|
+
built example ``Node``s and the demo encoders (Datalog, tree DP, game search harnesses) live in
|
|
8
|
+
``_examples``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from co_lambda._dsl import Builder, app, lam
|
|
14
|
+
|
|
15
|
+
# Combinators.
|
|
16
|
+
IDENTITY: Builder = lam(lambda x: x)
|
|
17
|
+
KESTREL: Builder = lam(lambda x: lam(lambda y: x)) # K = lambda x. lambda y. x
|
|
18
|
+
SELF_APPLY: Builder = lam(lambda x: app(x, x))
|
|
19
|
+
Y: Builder = lam(
|
|
20
|
+
lambda f: app(
|
|
21
|
+
lam(lambda x: app(f, app(x, x))),
|
|
22
|
+
lam(lambda x: app(f, app(x, x))),
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Church booleans.
|
|
27
|
+
TRUE: Builder = lam(lambda a: lam(lambda b: a))
|
|
28
|
+
FALSE: Builder = lam(lambda a: lam(lambda b: b))
|
|
29
|
+
AND: Builder = lam(lambda p: lam(lambda q: app(app(p, q), FALSE))) # p and q
|
|
30
|
+
OR: Builder = lam(lambda p: lam(lambda q: app(app(p, TRUE), q))) # p or q
|
|
31
|
+
|
|
32
|
+
# Church-numeral literals the prelude's own terms need (the general renderer is ``_codec.church``).
|
|
33
|
+
ZERO: Builder = lam(lambda s: lam(lambda z: z))
|
|
34
|
+
ONE: Builder = lam(lambda s: lam(lambda z: app(s, z)))
|
|
35
|
+
|
|
36
|
+
# Peano arithmetic on Church numerals.
|
|
37
|
+
SUCC: Builder = lam(lambda n: lam(lambda s: lam(lambda z: app(s, app(app(n, s), z)))))
|
|
38
|
+
PLUS: Builder = lam(
|
|
39
|
+
lambda m: lam(lambda n: lam(lambda s: lam(lambda z: app(app(m, s), app(app(n, s), z)))))
|
|
40
|
+
)
|
|
41
|
+
MULT: Builder = lam(lambda m: lam(lambda n: lam(lambda s: app(m, app(n, s)))))
|
|
42
|
+
EXP: Builder = lam(lambda m: lam(lambda n: app(n, m))) # m ^ n = n m
|
|
43
|
+
IS_ZERO: Builder = lam(lambda n: app(app(n, lam(lambda x: FALSE)), TRUE))
|
|
44
|
+
PRED: Builder = lam(
|
|
45
|
+
lambda n: lam(lambda s: lam(lambda z: app(
|
|
46
|
+
app(
|
|
47
|
+
app(n, lam(lambda g: lam(lambda h: app(h, app(g, s))))),
|
|
48
|
+
lam(lambda u: z),
|
|
49
|
+
),
|
|
50
|
+
lam(lambda u: u),
|
|
51
|
+
)))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# factorial n = if n = 0 then 1 else n * factorial (n - 1); the Church boolean selects.
|
|
55
|
+
FACTORIAL: Builder = app(
|
|
56
|
+
Y,
|
|
57
|
+
lam(lambda f: lam(lambda n: app(
|
|
58
|
+
app(app(IS_ZERO, n), ONE),
|
|
59
|
+
app(app(MULT, n), app(f, app(PRED, n))),
|
|
60
|
+
))),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# fib n = if n = 0 then 0 else if (n - 1) = 0 then 1 else fib (n-1) + fib (n-2)
|
|
64
|
+
FIBONACCI: Builder = app(
|
|
65
|
+
Y,
|
|
66
|
+
lam(lambda f: lam(lambda n: app(
|
|
67
|
+
app(app(IS_ZERO, n), ZERO),
|
|
68
|
+
app(
|
|
69
|
+
app(app(IS_ZERO, app(PRED, n)), ONE),
|
|
70
|
+
app(
|
|
71
|
+
app(PLUS, app(f, app(PRED, n))),
|
|
72
|
+
app(f, app(PRED, app(PRED, n))),
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
))),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Scott-encoded lists, for cyclic data.
|
|
79
|
+
SCOTT_CONS: Builder = lam(
|
|
80
|
+
lambda h: lam(lambda t: lam(lambda c: lam(lambda n: app(app(c, h), t))))
|
|
81
|
+
)
|
|
82
|
+
SCOTT_NIL: Builder = lam(lambda c: lam(lambda n: n))
|
|
83
|
+
SCOTT_PRESENT: Builder = lam(lambda a: lam(lambda b: a)) # = TRUE / first Scott constructor
|
|
84
|
+
|
|
85
|
+
# The ordinary singly-linked-list map: nothing is cycle-aware. map f = Y (lambda self.
|
|
86
|
+
# lambda lst. lst (lambda h. lambda t. cons (f h) (self t)) nil). The recursion is guarded
|
|
87
|
+
# (a cons is exposed before the recursive call), so on a cyclic list the recursive
|
|
88
|
+
# application self t re-enters the same closed position and the least fixpoint folds it into
|
|
89
|
+
# a finite cyclic result, where head reduction would unfold the mapped stream forever.
|
|
90
|
+
MAP: Builder = lam(
|
|
91
|
+
lambda f: app(
|
|
92
|
+
Y,
|
|
93
|
+
lam(lambda self_recursion: lam(lambda source: app(
|
|
94
|
+
app(
|
|
95
|
+
source,
|
|
96
|
+
lam(lambda head: lam(lambda tail: app(
|
|
97
|
+
app(SCOTT_CONS, app(f, head)),
|
|
98
|
+
app(self_recursion, tail),
|
|
99
|
+
))),
|
|
100
|
+
),
|
|
101
|
+
SCOTT_NIL,
|
|
102
|
+
))),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# =====================================================================
|
|
107
|
+
# Dynamic programming with a tree state space: memoisation for free.
|
|
108
|
+
#
|
|
109
|
+
# A binary tree is Scott-encoded with two constructors, node(l, r) and leaf(v); a tree DP is an
|
|
110
|
+
# ordinary Y-recursion whose subproblems are the subtrees. Interning makes structurally-identical
|
|
111
|
+
# subtrees one node, so a DP over a DAG-compressed tree computes each distinct subtree once.
|
|
112
|
+
# =====================================================================
|
|
113
|
+
|
|
114
|
+
TREE_NODE: Builder = lam(
|
|
115
|
+
lambda l: lam(lambda r: lam(lambda on_node: lam(lambda on_leaf: app(app(on_node, l), r))))
|
|
116
|
+
)
|
|
117
|
+
TREE_LEAF: Builder = lam(lambda v: lam(lambda on_node: lam(lambda on_leaf: app(on_leaf, v))))
|
|
118
|
+
|
|
119
|
+
# tree_any t = OR over the leaves of t of the leaf's boolean. As a tree DP:
|
|
120
|
+
# tree_any = Y (lambda self. lambda t. t (lambda l. lambda r. OR (self l) (self r)) (lambda v. v))
|
|
121
|
+
TREE_ANY: Builder = app(
|
|
122
|
+
Y,
|
|
123
|
+
lam(lambda self_recursion: lam(lambda tree: app(
|
|
124
|
+
app(
|
|
125
|
+
tree,
|
|
126
|
+
lam(lambda left: lam(lambda right: app(
|
|
127
|
+
app(OR, app(self_recursion, left)), app(self_recursion, right)
|
|
128
|
+
))),
|
|
129
|
+
),
|
|
130
|
+
lam(lambda value: value),
|
|
131
|
+
))),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# =====================================================================
|
|
135
|
+
# Minimax / AND-OR game search with a transposition table for free.
|
|
136
|
+
#
|
|
137
|
+
# A game position is a MAX node (OR over moves), a MIN node (AND over moves), or a terminal LEAF
|
|
138
|
+
# carrying a Boolean outcome. A transposition is the same interned node, so its value is computed
|
|
139
|
+
# once: interning is the transposition table.
|
|
140
|
+
# =====================================================================
|
|
141
|
+
|
|
142
|
+
MAX_NODE: Builder = lam(lambda l: lam(lambda r: lam(
|
|
143
|
+
lambda on_max: lam(lambda on_min: lam(lambda on_leaf: app(app(on_max, l), r))))))
|
|
144
|
+
MIN_NODE: Builder = lam(lambda l: lam(lambda r: lam(
|
|
145
|
+
lambda on_max: lam(lambda on_min: lam(lambda on_leaf: app(app(on_min, l), r))))))
|
|
146
|
+
GAME_LEAF: Builder = lam(lambda v: lam(
|
|
147
|
+
lambda on_max: lam(lambda on_min: lam(lambda on_leaf: app(on_leaf, v)))))
|
|
148
|
+
|
|
149
|
+
# minimax = Y (lambda self. lambda pos. pos (max: OR (self l) (self r)) (min: AND (self l) (self r))
|
|
150
|
+
# (leaf: lambda v. v))
|
|
151
|
+
MINIMAX: Builder = app(
|
|
152
|
+
Y,
|
|
153
|
+
lam(lambda self_recursion: lam(lambda position: app(app(app(
|
|
154
|
+
position,
|
|
155
|
+
lam(lambda left: lam(lambda right: app(
|
|
156
|
+
app(OR, app(self_recursion, left)), app(self_recursion, right)))), # MAX: OR
|
|
157
|
+
),
|
|
158
|
+
lam(lambda left: lam(lambda right: app(
|
|
159
|
+
app(AND, app(self_recursion, left)), app(self_recursion, right)))), # MIN: AND
|
|
160
|
+
),
|
|
161
|
+
lam(lambda value: value), # LEAF
|
|
162
|
+
))),
|
|
163
|
+
)
|