|
| 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