eli
represents the culmination of more than 15 years of designing and implementing embedded Lisp interpreters in various languages.
It all began with wishing for a nice language to script a personal project, but evolved into one of the deepest rabbit holes I've had the pleasure of falling into.
If you would like to see this project succeed, all contributions are welcome.
I've decided to release the book under an open license to benefit as many as possible, because I believe knowledge should be shared freely. But I also believe in compensation for creators; and the less economic pressure I have to deal with, the more time and energy I can put into the project.
The repository is set up for sponsoring via Stripe and Liberapay, alternatively you may use BTC (bitcoin:18k7kMcvPSSSzQtJ6hY5xxCt5U5p45rbuh) or ETH (0x776001F33F6Fc07ce9FF70187D5c034DCb429811).
The following projects implement eli
in different languges, some are more complete than others. Most work currently happens in eli-java.
I'm also working on bringing some eli
magic to Common Lisp.
I decided early on that Python would make a reasonable performance target to aim for, since it's mostly interpreted and well known.
$ python3 benchmarks/run.py
fact 1.689405483
fib 0.634149815
$ java -jar eli.jar benchmarks/run.eli
fact 1.682860949
fib 1.568288647
The root of all types.
Bits are either true or false.
T
F
The root of callable types.
The root of comparable types.
The root of countable types.
Characters.
\a
Quoted expressions.
'(+ 1 2)
Floating point values with absolute precision.
1.23
64 bit integers.
42
Root of iterator types.
Root of iterable types.
Type of libraries.
Mutable, random access sequences of values.
[1 2 3]
Type of macros.
Mutable, ordered mappings between values.
{1:2 3:4}
Type of types.
Type of methods.
Missing values.
_
Root of numeric types.
Pairs of values.
1:2
Root of values with heads and tails.
Immutable sequences of characters.
"abc"
String literals support the following escapes: \n
for line break, "
for double quote, and \\
for backslash.
Quoted identifiers.
'foo
Durations of time.
Points in time.
(now)
All types have Bit
representations, most unconditionally evaluate to T
; such values are referred to as truthy. Notable exceptions besides F
are []
, {}
and _
.
if
evaluates its body if the specified condition is truthy.
(if T 1)
1
(if F 1)
_
else
may be used to provide an alternative branch.
(if F 1 (else 2))
2
else-if
may be used to reduce nesting.
(if F 1 (else-if F 2 3))
3
Any expression may be quoted by prefixing with '
.
'(+ 1 2)
'(+ 1 2)
Quoted lists become lists of quoted items.
'[foo bar baz]
['foo 'bar 'baz]
Quoted maps only quote the keys.
'{a:(+ 1 2) b:(+ 3 4)}
{'a:3 'b:7}
Courtesy of pairs behaving the same way.
'foo:(+ 1 2)
'foo:3
,
unquotes the succeding form.
(let [foo '(+ 1 2)]
,foo)
3
var
binds values to identifiers, the value is evaluated at compile time.
(var foo 42)
foo
42
let
may be used to create scoped runtime bindings,
(let [foo 'bar]
foo)
'bar
and to locally override existing var
bindings.
(var foo 1)
foo
1
(^baz [] foo)
(let [foo 2] (baz))
2
foo
1
set
updates the value of existing bindings.
(let [foo 1 bar 2]
(set foo 3 bar 4)
foo:bar)
3:4
=
returns T
if specified arguments have equal values.
(= [1 2 3:4] [1 2 3:4])
T
While is
returns T
only if specified arguments have equal identities.
(is [1 2 3:4] [1 2 3:4])
F
(let [foo [1 2 3:4]]
(is foo foo))
T
head
returns the first item in a sequence, or _
if empty.
(head "foo")
\f
tail
returns the tail of a sequence, or _
if empty.
(tail '{a:1 b:2 c:3})
{'b:2 'c:3}
count
returns the number of items in a sequence, #
may be used as shorthand.
#7:35:42
3
loop
repeats its body indefinitely.
(let [foo 0]
(loop
(if (= (inc foo) 10) (break foo))))
10
for
repeats its body with variables bound to successive items from an iterable.
(let [foo 0]
(iter/for [i [1 2 3]
(inc foo i))
foo)
6
Multiple sequences may be iterated in parallel.
(let [foo 0 bar 0]
(iter/for [i [1 2 3]
j [4 5]]
(inc foo i)
(inc bar j))
foo:bar)
6:9
break
evaluates its arguments and jumps to the end of the loop.
(let [foo 0]
(loop T
(if (= (inc foo) 10)
(break (* foo 2)))))
20
next
evaluates its arguments and jumps to the start of next iteration.
(let [foo 0]
(loop
(if (< (inc foo) 10)
(next))
(break (* foo 2))))
20
get
may be used to acquire an iterator for any iterable value. pop
returns the next item or _
.
(let [i (iter/get '[foo bar baz])]
(iter/pop i))
'foo
all
returns T
if all items from a set of iterables pass the predicate, otherwise F
.
(iter/all < [1 2] [3 4])
T
any
returns T
if any items from a set of iterables pass the predicate, otherwise F
.
(iter/any > [1 2 5] [3 4 5])
F
comb
returns an iterator for all combinations of items in an iterable.
[(iter/comb [1 2 3])*]
[[1] [2] [1 2] [3] [1 3] [2 3] [1 2 3]]
cross
returns an iterator for the cross product of two iterables.
[(iter/cross + [1 2] [3 4])*]
[4 5 5 6]
concat
returns a concatenated iterator for a set of iterables.
[(iter/concat [1 2 3] 4:5)*]
[1 2 3 4 5]
fold
folds a set of iterables into a value using the specified binary operation and seed value.
(^my-sum [in]
(iter/fold + 0 in))
(my-sum 1 2 3)
6
map
returns an iterator to a method mapped over a set of iterables.
[(iter/map + [1 3] [5 7 11])*]
[6 10]
where
returns an iterator to items matching a predicate from a set of iterables.
[(iter/where > [1 5 2 6] [1 2 3 4])*]
[5:2 6:4]
unzip
splits an iterable of pairs into a pair of lists.
(iter/unzip [1:2 3:4])
[1 3]:[2 4]
zip
transforms a set of iterables into a pair iterator.
[(iter/zip 'foo:'bar:'baz [1 2 3] "abc")*]
['foo:1:\a 'bar:2:\b 'baz:3:\c]
Methods may be defined using ^
.
(^foo [x]
x)
(foo 42)
42
Final arguments suffixed with ?
are optional.
(^foo [x y?]
(say x " " y))
(foo 1 2)
(foo 3)
1 2
3 _
Suffixing the last argument with *
makes the method accept a variable number of arguments.
(^foo [x*]
(+ x*))
(foo 35 7)
42
By default, the last value is returned.
(^foo [x]
x)
(foo 42)
42
return
evaluates its arguments and jumps to the end of the method.
(^foo [] 1 (return 2 3) 4)
(foo)
3
Methods returning pairs support call site destructuring.
(^foo []
1:2:3)
(foo:_)
1
(_:_:foo)
3
(_:foo)
2:3
Methods support overloading. When called; the most specific, most recent, matching definition is chosen.
(^foo [x]
(say "x 1"))
(^foo [x y]
(say "x y"))
(foo 1)
(^foo [x]
(say "x 2"))
(foo 1)
(foo 1 2)
x 1
x 2
x y
Lambdas may be created by leaving out the method name.
(^[] 42)
(^repl@1:1 [])
All bindings; let
, var
and method arguments; support in place destructuring of pairs and maps.
(var i:j 1:2)
i:j
1:2
Maps support two forms; one with implicit keys,
(var {foo baz} '{foo:1 bar:2 baz:3})
foo:baz
1:3
and the other explicit.
(var {f:foo b:baz} '{foo:1 bar:2 baz:3})
f:b
Lists, maps and method calls support expanding iterable values in place by suffixing with *
.
["abc"*]
[\a \b \c]
Maps require pair values.
(let [foo "abc"]
{(iter/map (^[i] i:(foo i)) (range 0 3 1))*})
{0:\a 1:\b 2:\c}
Expressions may be suffixed with @
to type check.
(+ 35 7)@Numeric
42
An error is signalled if types aren't compatible.
42@String
Error in REPL@1:3: Type check failed, expected String: 42
Bindings are supported.
(let [foo@Int 42]
foo]
42
As well as method arguments, where more specifically typed arguments have higher priority in overload resolution.
(^foo [x@Int]
x)
(foo 42)
42
say
prints its arguments followed by newline to standard output.
(say "35+7=" (+ 35 7))
35+7=42
lib
defines/extends namespaces.
(lib foo
(var bar 42))
foo/bar
42
import
pulls external bindings into the current namespace.
(import f/bar)
bar
42
The target id may be overridden.
(import foo:f)
f/bar
42
test.eli:
42
include
emits the content of external files at compile time.
(include "test.eli")
42
load
evaluates the content of external files at run time.
(load "test.eli")
42
dump
converts its arguments to readable strings.
(dump ['foo "bar" 42])
"['foo \"bar\" 42]"
check
signals an error if its body doesn't produce the specified result.
(check 1 2)
Error in REPL@1:1: Check failed; expected 1, actual: 2
Should you find yourself involved in a software project with interesting non-GenAI challenges and in need of a creative developer/tech/team lead with 26 years experience, don't hesitate to get in touch.