-- Types are inferred; annotations are checked.
fibonacci : Number -> Number = \n.
(if (lt n 2) n (add (fibonacci (sub n 1)) (fibonacci (sub n 2))))
compose = \f. \g. \x. (f (g x)) -- (a -> b) -> (c -> a) -> c -> b
(println (map fibonacci (range 0 10))) -- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
(println ((compose show (mul 2)) 21)) -- 42
- Lambdas, and little elsePrograms are applications of
\x. bodyfunctions, written as S-expressions. The whole grammar fits on a postcard. - Inferred typesHindley-Milner inference with let-polymorphism. Write annotations when they help you; the checker holds them to their word.
- Lazy by defaultCall-by-need evaluation: arguments are evaluated only when used, and at most once. Tail calls run in constant space.
- Immutable dataLists are persistent vectors. Every operation returns a new list and leaves the old one exactly as it was.
- Errors that helpSeveral diagnostics per run, each pointing at the code that caused it, with "did you mean" suggestions.
- A real compilerParser, resolver, type checker and a Core IR in front of a lazy abstract machine, with room for more backends.
A first program#
Save this as hello.lbd:
greet : String -> String = \name. (concat "Hello, " name)
(println (greet "World"))
and run it:
$ lbd run hello.lbd
Hello, World
Or start lbd with no arguments for a REPL, where :type shows the type of
any expression:
λ> :type (map (add 1))
(map (add 1)) : List<Number> -> List<Number>
Where to next#
- Install the interpreter.
- Read the language reference and the builtins.
- Browse the examples, from Hello World to Advent of Code.
- Find out why the language looks the way it does.