A purely-functional programming language with Hindley-Milner type inference and `callcc`.
Go to file
James T. Martin 586be18c80
Add support for many data types, pattern matching, and literals.
* Data types: products, sums, naturals, lists, characters
* Literals: naturals, lists, characters, and strings

I also updated the description with examples of how to use all these new features.

The code's a bit messy and will need cleanup, but for now, it works!
2021-03-17 00:20:17 -07:00
.github/workflows Cleanup: fix warnings, fix indentation, upgrade dependencies. 2021-03-05 19:04:06 -08:00
app Combine expression representations using the 'trees that grow' design pattern. 2021-03-16 18:25:42 -07:00
src Add support for many data types, pattern matching, and literals. 2021-03-17 00:20:17 -07:00
test Make `callcc` into a proper primitive rather than a hardcoded "variable". 2021-03-16 19:03:02 -07:00
.editorconfig Cleanup: fix warnings, fix indentation, upgrade dependencies. 2021-03-05 19:04:06 -08:00
.gitignore Complexity was getting out of hand. Beginning a rewrite. Added tests. 2019-12-11 18:29:28 -08:00
LICENSE Initial commit. Some terms are still not evaluated correctly. 2019-08-15 10:42:24 -07:00
README.md Add support for many data types, pattern matching, and literals. 2021-03-17 00:20:17 -07:00
Setup.hs Initial commit. Some terms are still not evaluated correctly. 2019-08-15 10:42:24 -07:00
package.yaml Make `callcc` into a proper primitive rather than a hardcoded "variable". 2021-03-16 19:03:02 -07:00
stack.yaml Combine expression representations using the 'trees that grow' design pattern. 2021-03-16 18:25:42 -07:00
stack.yaml.lock Combine expression representations using the 'trees that grow' design pattern. 2021-03-16 18:25:42 -07:00

README.md

Lambda Calculus

This is a simple programming language derived from lambda calculus.

Usage

Run the program using stack run (or run the tests with stack test).

Type in your expression at the prompt: >> . The expression will be evaluated to normal form using the call-by-value evaluation strategy and then printed. Exit the prompt with Ctrl-c (or equivalent).

Syntax

The parser's error messages currently are virtually useless, so be very careful with your syntax.

  • Variable names: any sequence of letters.
  • Function application: f x y
  • Lambda abstraction: \x y z. E or λx y z. E
  • Let expressions: let x = E; y = F in G
  • Parenthetical expressions: (E)
  • Constructors: (), (x, y) (or (,) x y), Left x, Right y, Z, S, [], (x : xs) (or (:) x xs), Char n.
    • The parentheses around the cons constructor are not optional.
    • Char takes a natural number and turns it into a character.
  • Pattern matchers: { Left x -> e ; Right y -> f }
    • Pattern matchers can be applied like functions, e.g. { Z -> x, S -> y } 10 reduces to y.
    • Patterns must use the regular form of the constructor, e.g. (x : xs) and not ((:) x xs).
    • There are no nested patterns or default patterns.
    • Incomplete pattern matches will crash the interpreter.
  • Literals: 1234, [e, f, g, h], 'a, "abc"
    • Strings are represented as lists of characters.

Call/CC

This interpreter has preliminary support for the call-with-current-continuation control flow operator. However, it has not been thoroughly tested.

To use it, simply apply the variable callcc like you would a function, e.g. (callcc (\k. ...)).

Continuations are printed as λ!. ... ! ..., like a lambda abstraction with an argument named ! which is used exactly once; however, continuations are not the same as lambda abstractions because they perform the side effect of modifying the current continuation, and this is not valid syntax you can input into the REPL.

Example code

The fixpoint function:

(\x. x x) \fix f x. f (fix fix f) x

Create a list by iterating f n times:

fix \iterate f x. { Z -> x ; S n -> iterate f (f x) n }

Create a list whose first element is n - 1, counting down to a last element of 0:

\n. { (n, x) -> x } (iterate { (n, x) -> (S n, (n : x)) } (0, []) n)

Putting it all together to count down from 10:

>> let fix = (\x. x x) \fix f x. f (fix fix f) x; iterate = fix \iterate f x. { Z -> x ; S n -> iterate f (f x) n }; countDownFrom = \n. { (n, x) -> x } (iterate { (n, x) -> (S n, (n : x)) } (0, []) n) in countDownFrom 10
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Append two lists together:

fix \append xs ys. { [] -> ys ; (x : xs) -> (x : append xs ys) } xs

Reverse a list:

fix \reverse. { [] -> [] ; (x : xs) -> append (reverse xs) [x] }

Putting them together so we can reverse "reverse":

>> let fix = (\x. x x) \fix f x. f (fix fix f) x; append = fix \append xs ys. { [] -> ys ; (x : xs) -> (x : append xs ys) } xs; reverse = fix \reverse. { [] -> [] ; (x : xs) -> append (reverse xs) [x] } in reverse "reverse"
"esrever"

Calculating 3 + 2 with the help of Church-encoded numerals:

>> let Sf = \n f x. f (n f x); plus = \x. x Sf in plus (\f x. f (f (f x))) (\f x. f (f x)) S Z
5

This expression would loop forever, but callcc saves the day!

>> y (callcc \k. (\x. (\x. x x) (\x. x x)) (k z))
y z

A few other weird expressions:

>> let D = \x. x x; F = \f. f (f y) in D (F \x. x)
y y
>> let T = \f x. f (f x) in (\f x. T (T (T (T T))) f x) (\x. x) y
y
>> (\x y z. x y) y
λy' z. y y'
>> { Char c -> Char (S c) } 'a
'b