Inspired by the course "Funktionale Programmierung" @ FU Berlin
A suite of programs to evaluate lambda calculus expressions. Try it out right now on the web
First you need to install git and clone the repo
> git clone https://github.com/theRealProHacker/lambda.git
> cd lambda
Run the Python script very easily. No dependencies required but at least Python 3.10!
> py pylambda.py
The script will then ask you to input your lambda expression and evaluate it, when you hit enter.
The amazing part is that it's about 100 lines of code with a lot of comments and a cli attached. Without all comments, type signatures and the cli interface, its a bit less than half of that!
Make sure that you have ghc
installed and in your Path. Then you can compile lambda.hs
.
> ghc --make lambda
Now you can run the executable like this.
> lambda (\kbc.k(bb)c)(\xy.xya)(\f.f)
(\c.ac)
Note: These examples are for Windows. On Linux replace
lambda
with./lambda
As you may have noticed, the λ is replaced with a back slash (\) inspired by the notation for lambda functions in Haskell. Also whitespaces have a significance as they end the body of a function.
> lambda \kbc.k(bb)c \xy.xya \f.f
(\c.ca)
Alternatively you can run the code without compiling it.
> runhaskell lambda \kbc.k(bb)c \xy.xya \f.f
(\c.ca)
In lambda.hs
you can see this line
debug = False
If you set debug
to True
you can see the steps of the reduction process, which is perfect for introspection because it also dumps the ast at every step.
You can also run ilambda.hs
in an interactive ghci session. But don't forget to use double backslashes (\\) (which in Markdown ironically needs 4 backslashes).
> ghci ilambda
GHCi, version 8.10.2: https://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Main ( ilambda.hs, interpreted )
Ok, one module loaded.
*Main> (\a.a)b
"*** Exception: Stray . found
CallStack (from HasCallStack):
error, called at ilambda.hs:30:32 in main:Main
*Main> beta "(\\a.a)b"
"b"
From Wikipedia - the free encyclopedia
Lambda calculus (also written as λ-calculus) is a formal system in mathematical logic for expressing computation based on function abstraction and application using variable binding and substitution. It is a universal model of computation that can be used to simulate any Turing machine. It was introduced by the mathematician Alonzo Church in the 1930s as part of his research into the foundations of mathematics.
It is basically a first programming language from the 1930s before computers even existed! To use it here replace the lambda symbol (λ) with a back slash ().
Going further I'd like to introduce a file input mode where you can define variables that can then be used globally.
// define Variables
True = (\tf.t)
False = (\tf.f)
// and use them
And = \ab.ab(False)
Or = \ab.a(True)b
But that'd just become more and more like Haskell.
Combined with the above you could convert your lambda expressions into haskell code.
x = \ab.ab
would become
x a b = a b
Just for fun I put the smallest "possible" compressed version of ilambda.hs
(the already compressed version) here, which is just 5 lines of code. I'll bet you that you can't make it much smaller. The code is technically not exactly the same because errors are thrown implicitly but nice and shiny explicit errors that tell you exactly where you went wrong aren't really a requirement.
data Expression = Var Char | Func String Expression | Appl [Expression] deriving (Show, Eq);showExpr x = case x of (Var c) -> [c]; (Func head body) -> "(\\" ++ head ++ "." ++ (showExpr body) ++ ")"; (Appl xs) -> foldl (++) [] $ map showExpr xs;
_split_on s1 (c:s2) x = if c == x then (s1, s2) else _split_on (s1++[c]) s2 x;split_closing_paren' n before (c:rest) = if c == ')' then if n==0 then (reverse before, rest) else split_closing_paren' (n-1) before rest else if c=='(' then split_closing_paren' (n+1) before rest else split_closing_paren' n (c:before) rest;tokenize exprs str = case str of ""->exprs; '\\':rest -> let (head, body) = _split_on "" rest '.' in if ' ' `elem` body then let (body', rest') = _split_on "" body ' ' in tokenize ((Func head $ parse body'):exprs) rest' else tokenize ((Func head $ parse body):exprs) ""; '(':rest -> let (inner, after) = (split_closing_paren' 0 "" rest) in tokenize ((parse inner): exprs) after; ' ':rest -> tokenize exprs rest; x:rest -> tokenize (Var x:exprs) rest;
parse = Appl . reverse . tokenize [];replaceArg c e expr = case expr of var@(Var x) -> if c==x then e else var; func@(Func head body) -> if c `elem` head then func else Func head (replaceArg c e body); (Appl exprs) -> Appl $ map (replaceArg c e) exprs;
reduce x = case x of (Appl [x]) -> reduce x; (Appl ((Func [] body):rest)) -> reduce $ Appl (body:rest); (Appl ((Func [param] body):arg:arg_rest)) -> reduce $ Appl (replaceArg param arg body:arg_rest); (Appl ((Func (param1:param_rest) body):arg1:arg_rest)) -> reduce $ Appl (Func param_rest (replaceArg param1 arg1 body):arg_rest); (Appl exprs) -> reduce $ Appl $ map reduce exprs; (Func head body) -> Func head $ reduce body; _ -> x;
beta = showExpr . reduce . parse