Skip to content

Commit e7d64b2

Browse files
committed
add arithmetic lambda terms
1 parent 263d948 commit e7d64b2

File tree

6 files changed

+258
-4
lines changed

6 files changed

+258
-4
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ The `lambda_calculus` package contains classes which implement basic operations
55
To use it, simply import the classes `Variable`, `Abstraction` and `Application` from this package
66
and nest them to create more complex lambda terms.
77

8-
You can also use the `visitors` subpackage to define your own operations on terms.
8+
You can also use the `visitors` subpackage to define your own operations on terms or
9+
use predefined ones from the `terms` subpackage.
910

1011
## Requirements
1112

lambda_calculus/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .terms import Variable, Abstraction, Application
66

7-
__version__ = "1.7.1"
7+
__version__ = "1.8.0"
88
__author__ = "Eric Niklas Wolf"
99
__email__ = "[email protected]"
1010
__all__ = (

lambda_calculus/terms/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"Term",
1616
"Variable",
1717
"Abstraction",
18-
"Application"
18+
"Application",
19+
"arithmetic"
1920
)
2021

2122
T = TypeVar("T")
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/python3
2+
3+
"""Implementations of natural numbers and arithmetic operators"""
4+
5+
from . import Term, Variable, Abstraction, Application
6+
7+
__all__ = (
8+
"SUCCESSOR",
9+
"PREDECESSOR",
10+
"ADD",
11+
"SUBTRACT",
12+
"MULTIPLY",
13+
"POWER",
14+
"number"
15+
)
16+
17+
SUCCESSOR = Abstraction.curried(
18+
("n", "f", "x"),
19+
Application(
20+
Variable("f"),
21+
Application.with_arguments(
22+
Variable("n"),
23+
(Variable("f"), Variable("x"))
24+
)
25+
)
26+
)
27+
28+
PREDECESSOR = Abstraction.curried(
29+
("n", "f", "x"),
30+
Application.with_arguments(
31+
Variable("n"),
32+
(
33+
Abstraction.curried(
34+
("g", "h"),
35+
Application(
36+
Variable("h"),
37+
Application(
38+
Variable("g"),
39+
Variable("f")
40+
)
41+
)
42+
),
43+
Abstraction("u", Variable("x")),
44+
Abstraction("u", Variable("u")),
45+
)
46+
)
47+
)
48+
49+
ADD = Abstraction.curried(
50+
("m", "n", "f", "x"),
51+
Application.with_arguments(
52+
Variable("m"),
53+
(
54+
Variable("f"),
55+
Application.with_arguments(
56+
Variable("n"),
57+
(Variable("f"), Variable("x"))
58+
)
59+
)
60+
)
61+
)
62+
63+
SUBTRACT = Abstraction.curried(
64+
("m", "n"),
65+
Application.with_arguments(
66+
Variable("n"),
67+
(PREDECESSOR, Variable("m"))
68+
)
69+
)
70+
71+
MULTIPLY = Abstraction.curried(
72+
("m", "n", "f"),
73+
Application(
74+
Variable("m"),
75+
Application(
76+
Variable("n"),
77+
Variable("f")
78+
)
79+
)
80+
)
81+
82+
POWER = Abstraction.curried(
83+
("b", "e"),
84+
Application(
85+
Variable("e"),
86+
Variable("b")
87+
)
88+
)
89+
90+
91+
def number(n: int) -> Abstraction[str]:
92+
"""return n encoded in lambda terms"""
93+
if n < 0:
94+
raise ValueError("number is not natural")
95+
f = Variable("f")
96+
body: Term[str] = Variable("x")
97+
for _ in range(n):
98+
body = Application(f, body)
99+
return Abstraction.curried(
100+
("f", "x"),
101+
body
102+
)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "lambda_calculus"
3-
version = "1.7.1"
3+
version = "1.8.0"
44
description = "Implementation of the Lambda calculus"
55
requires-python = ">=3.10"
66
keywords = []

tests/terms/test_arithmetic.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/python3
2+
3+
"""Tests for arithmetic terms"""
4+
5+
from unittest import TestCase
6+
from lambda_calculus.visitors.normalisation import BetaNormalisingVisitor
7+
from lambda_calculus.terms import Application, arithmetic
8+
9+
10+
class OrderingTest(TestCase):
11+
"""Test for ordering functions"""
12+
13+
visitor: BetaNormalisingVisitor
14+
15+
def setUp(self) -> None:
16+
"""create a visitor"""
17+
self.visitor = BetaNormalisingVisitor()
18+
19+
def test_successor(self) -> None:
20+
"""test successor term"""
21+
self.assertEqual(
22+
self.visitor.skip_intermediate(
23+
Application(arithmetic.SUCCESSOR, arithmetic.number(8))
24+
),
25+
arithmetic.number(9)
26+
)
27+
28+
def test_predecessor(self) -> None:
29+
"""test predecessor term"""
30+
self.assertEqual(
31+
self.visitor.skip_intermediate(
32+
Application(arithmetic.PREDECESSOR, arithmetic.number(8))
33+
),
34+
arithmetic.number(7)
35+
)
36+
37+
def test_predecessor_zero(self) -> None:
38+
"""test predecessor of zero"""
39+
self.assertEqual(
40+
self.visitor.skip_intermediate(
41+
Application(arithmetic.PREDECESSOR, arithmetic.number(0))
42+
),
43+
arithmetic.number(0)
44+
)
45+
46+
47+
class CalculationsTest(TestCase):
48+
"""Test for calculations with natural numbers"""
49+
50+
visitor: BetaNormalisingVisitor
51+
52+
def setUp(self) -> None:
53+
"""create a visitor"""
54+
self.visitor = BetaNormalisingVisitor()
55+
56+
def test_add(self) -> None:
57+
"""test addition term"""
58+
self.assertEqual(
59+
self.visitor.skip_intermediate(
60+
Application.with_arguments(
61+
arithmetic.ADD,
62+
(arithmetic.number(3), arithmetic.number(5))
63+
)
64+
),
65+
arithmetic.number(8)
66+
)
67+
68+
def test_subtract(self) -> None:
69+
"""test subtraction term"""
70+
self.assertEqual(
71+
self.visitor.skip_intermediate(
72+
Application.with_arguments(
73+
arithmetic.SUBTRACT,
74+
(arithmetic.number(10), arithmetic.number(3))
75+
)
76+
),
77+
arithmetic.number(7)
78+
)
79+
80+
def test_subtract_greater(self) -> None:
81+
"""test subtraction below zero"""
82+
self.assertEqual(
83+
self.visitor.skip_intermediate(
84+
Application.with_arguments(
85+
arithmetic.SUBTRACT,
86+
(arithmetic.number(10), arithmetic.number(13))
87+
)
88+
),
89+
arithmetic.number(0)
90+
)
91+
92+
def test_multiply(self) -> None:
93+
"""test multiplication term"""
94+
self.assertEqual(
95+
self.visitor.skip_intermediate(
96+
Application.with_arguments(
97+
arithmetic.MULTIPLY,
98+
(arithmetic.number(5), arithmetic.number(2))
99+
)
100+
),
101+
arithmetic.number(10)
102+
)
103+
104+
def test_power(self) -> None:
105+
"""test power term"""
106+
# alpha conversion needed
107+
nine = arithmetic.number(9)
108+
nine = nine.replace(body=nine.body.alpha_conversion("x1"))
109+
nine = nine.alpha_conversion("x")
110+
self.assertEqual(
111+
self.visitor.skip_intermediate(
112+
Application.with_arguments(
113+
arithmetic.POWER,
114+
(arithmetic.number(3), arithmetic.number(2))
115+
)
116+
),
117+
nine
118+
)
119+
120+
def test_power_zero(self) -> None:
121+
"""test power zero"""
122+
one = self.visitor.skip_intermediate(
123+
Application.with_arguments(
124+
arithmetic.POWER,
125+
(arithmetic.number(5), arithmetic.number(0))
126+
)
127+
)
128+
# eta conversion needed
129+
self.assertEqual(
130+
self.visitor.skip_intermediate(
131+
Application.with_arguments(
132+
arithmetic.ADD,
133+
(one, arithmetic.number(3))
134+
)
135+
),
136+
arithmetic.number(4)
137+
)
138+
# alpha conversion needed
139+
zero = arithmetic.number(0)
140+
zero = zero.replace(body=zero.body.alpha_conversion("x1"))
141+
zero = zero.alpha_conversion("x")
142+
self.assertEqual(
143+
self.visitor.skip_intermediate(
144+
Application.with_arguments(
145+
arithmetic.POWER,
146+
(arithmetic.number(0), arithmetic.number(5))
147+
)
148+
),
149+
zero
150+
)

0 commit comments

Comments
 (0)