Skip to content

Commit a86836a

Browse files
author
Filippo Costa
committed
lambda.py -> main.py, add MIT license
1 parent c91e772 commit a86836a

File tree

3 files changed

+182
-144
lines changed

3 files changed

+182
-144
lines changed

LICENSE.txt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright 2019 Filippo Costa
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

lambda.py

-144
This file was deleted.

main.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
3+
# This Python script declares a binary lambda calculus interpreter. The
4+
# interpreter is written in untyped lambda calculus itself (i.e. only function
5+
# declarations and applications are allowed). Native recursion in particular
6+
# is disallowed.
7+
8+
# All functions allow prefix notation; for example:
9+
# NOT (TRUE)
10+
# _1ST (tuple)
11+
# EQ (n) (m)
12+
13+
# See https://en.wikipedia.org/wiki/Fixed-point_combinator#Fixed_point_combinators_in_lambda_calculus
14+
# for more.
15+
# It lets you define recursive functions by passing a dummy function definition
16+
# as an argument. E.g.
17+
# >>> FACTORIAL = lambda f: lambda n: 1 if n == 0 else n * f(n-1)
18+
# >>> Y (FACTORIAL) (4)
19+
# 24
20+
Y = lambda f: ((lambda x: f(lambda y: x(x)(y)))(lambda x: f(lambda y: x(x)(y))))
21+
ID = NONE = lambda x: x
22+
23+
# Boolean values as ternary operators. E.g.
24+
# >>> TRUE ('foo') ('bar')
25+
# 'foo'
26+
# >>> (AND (TRUE) (FALSE)) ('foo') ('bar')
27+
# 'bar'
28+
TRUE = lambda p: lambda q: p
29+
FALSE = lambda p: lambda q: q
30+
NOT = lambda p: p(FALSE)(TRUE)
31+
AND = lambda p: lambda q: p(q)(FALSE)
32+
OR = lambda p: lambda q: p(TRUE)(q)
33+
34+
# This tuple holds any two elements and provides access via postfix boolean
35+
# values. E.g.
36+
# >>> TUPLE ('foo') ('bar') (TRUE)
37+
# 'foo'
38+
# >>> TUPLE ('foo') ('bar') (FALSE)
39+
# 'bar'
40+
# You can view it as a ternary operator with postfix notation.
41+
TUPLE = lambda x: lambda y: lambda f: f(x)(y)
42+
43+
# Church numerals. These will be useful for defining variables.
44+
#
45+
# See:
46+
# - https://en.wikipedia.org/wiki/Church_encoding#Calculation_with_Church_numerals
47+
48+
INCR = lambda a: lambda p: lambda q: p(n(p)(q))
49+
DECR = (
50+
lambda a: lambda p: lambda q: (a)(lambda r: lambda s: s(r(p)))(lambda u: q)(ID)
51+
)
52+
53+
PLUS = lambda a: lambda b: a(INCR)(b)
54+
MINUS = lambda a: lambda b: a(DECR)(b)
55+
56+
ZERO = FALSE
57+
ONE = INCR(ZERO)
58+
59+
IS_ZERO = lambda a: a(lambda p: FALSE)(TRUE)
60+
LESS_OR_EQUAL = lambda m: lambda n: IS_ZERO(MINUS(m)(n))
61+
EQUAL = lambda a: lambda b: ((AND)(LESS_OR_EQUAL(a)(b))(LESS_OR_EQUAL(b)(a)))
62+
63+
# Lambda terms
64+
# ============
65+
# Our code will be a function that takes boolean inputs and then outputs a lazy
66+
# evaluation encoded using booleans as well.
67+
68+
VARIABLE = lambda a: lambda b: lambda c: a
69+
ABSTRACTION = lambda a: lambda b: lambda c: b
70+
APPLICATION = lambda a: lambda b: lambda c: c
71+
72+
CHAR_0 = lambda a: lambda b: lambda c: a
73+
CHAR_1 = lambda a: lambda b: lambda c: b
74+
CHAR_EOF = lambda a: lambda b: lambda c: c
75+
CHAR_IS_EOF = lambda char: char(FALSE)(FALSE)(TRUE)
76+
77+
DE_BRUIJN_INDEX_PARSER = (
78+
lambda f: lambda current_index: lambda char: char(f(INCR(current_index)))(current_index)(NONE)
79+
)
80+
81+
# Takes in a stream of boolean values and outputs an expression
82+
PARSER = (lambda f:
83+
lambda char:
84+
char
85+
(TUPLE (VARIABLE) (Y (DE_BRUIJN_INDEX_PARSER) (ZERO)))
86+
(lambda next_char:
87+
next_char
88+
(TUPLE (APPLICATION) (TUPLE (f) (f)))
89+
(TUPLE (ABSTRACTION) (f))
90+
(NONE))
91+
(NONE))
92+
93+
# λ-calculus call-by-name reduction
94+
# =================================
95+
96+
CALL_BY_NAME = (lambda f:
97+
lambda expression:
98+
lambda term:
99+
# Check expression type
100+
(expression (TRUE))
101+
(lambda variable: variable)
102+
(lambda abstraction:
103+
(TUPLE (ABSTRACTION))
104+
# We increment the counter 'n' and keep searching for terms bound to 'x'.
105+
(f (expression (FALSE)) (term) (INCR (n))))
106+
(lambda application:
107+
(TUPLE
108+
(APPLICATION)
109+
# We reduce both terms and then the second is applied to the first.
110+
(f (_1ST (expression (FALSE))) (x) (n))
111+
# The dummy argument g=ID is just to avoid strict evaluation.
112+
(f (_2ND (_2ND (e))) (x) (n)))))
113+
114+
# It receives a null-terminated stream of characters and finally returns 'TRUE'
115+
# if it's a valid BLC program.
116+
SYNTAX_CHECK = (lambda f:
117+
lambda count:
118+
lambda char:
119+
(lambda next_char:
120+
(f (INCR (INCR (count))))
121+
(f (INCR (count)))
122+
( DE_BRUIJN_INDEX_PARSER )
123+
(IS_ZERO (count))))
124+
125+
# Reads characters until EOF and returns its internal state. Then prints its response.
126+
INTERPRETER_STEP = (lambda f:
127+
lambda syntax_check:
128+
lambda parser:
129+
lambda char:
130+
CHAR_IS_EOF
131+
(f
132+
(syntax_check (char))
133+
(parser (char)))
134+
(syntax_check (char)
135+
(RUN_BLC (parser (char)))
136+
((lambda _: print("<invalid program>") (NONE)))))
137+
138+
INTERPRETER = Y(INTERPRETER_STEP)(SYNTAX_CHECK(ZERO))(PARSER)
139+
140+
# Takes in a stream of bits and outputs a stream of lambda terms.
141+
# 1. Transform 0s and 1s into lambda booleans. This step requires operating on
142+
# Python strings, something that lambda calculus obviously can't do, so we
143+
# do it in normal Python.
144+
# 2. Parse lambda booleans into tokens (00, 01, or 1{n}0).
145+
# 3. Run.
146+
147+
if __name__ == "__main__":
148+
print("A binary lambda calculus interpreter by Filippo Costa")
149+
print("Abstraction is 00")
150+
print("Application is 01")
151+
print("Variables are 1s followed by 0s")
152+
while True:
153+
source = input("> Type in your program: ")
154+
if not source:
155+
continue
156+
elif source == "exit":
157+
break
158+
state = Y(INTERPRETER)
159+
for c in source:
160+
state = state(CHAR_1 if c == '1' else CHAR_0)
161+
print("Normal form: ")
162+
state = state(CHAR_EOF)
163+
print("-- Moriturus te saluto.")

0 commit comments

Comments
 (0)