HookRace Blog     Posts     Feed     DDNet     Me

Make a Lisp in Nim

I spent the last weekend working through the amazing guide for Make a Lisp, writing a Lisp interpreter in Nim. The final result just made it into the repository.

Running the Nim version is pretty simple. You need the Nim compiler from the devel branch and nre which can be installed through nimble. After installing those you can build and run the MAL interpreter:

$ cd nim
$ make
  # OR
$ nimble build
$ ./stepA_mal
Mal [nim]
user> 12
12
user> (+ 2 3)
5

Running the tests:

$ cd ..
$ make "test^nim^step1" # A single test
$ make "test^nim" # All tests
$ make "perf^nim" # All benchmarks

There is a nice presentation in MAL about MAL you can read:

$ cd nim
$ ./stepA_mal ../examples/clojurewest2014.mal

And you can run MAL implented in MAL itself using the Nim interpreter:

$ ./stepA_mal ../mal/stepA_mal.mal
Mal [nim-mal]
mal-user>

The benchmark results from Joel Martin, the author of MAL, don't look bad for Nim. Edit: Note that these are just rough measurements to see that the Nim implementation is doing fine. Don't judge the other languages for their numbers, which may not have ideal implementations performance-wise.

In the short benchmarks Nim is the fastest, in the long benchmark only the JVM can beat it. I didn't really try to optimize and oriented mostly on the Python implementation, which is quite a lot slower as you can see. So it's pretty nice to see idiomatic Nim performing well:

             perf1  perf2       perf3
            macros   math      macros
                ms     ms   iters/sec
java             6     24       17969
scala           47     87       15963
nim              1      1       11121
ocaml            1      3        7063
cs              10     11        5414
vb              12     13        4523
rust             2      5        4084
c                1      4        3649
go               1      6        3048
racket           3     10        2461
coffee           5     11        2326
js               6     14        1726
ruby             4     15        1255
haskell          4     14        1163
clojure         11     23        1174
forth            9     29         563
php             13     51         331
python          14     51         304
bash          1673   9000         276
perl            18     69         215
ps              41    332          48
R              116    490          28
miniMAL        779   3448           4
matlab        1688   5844           2
make          3427  28453           0

I also didn't really aim for a low amount of code but rather good readability, but it may still be interesting to look at. I guess Nim's size being so close to Python shows how much I oriented on the Python implementation:

                Lines   Words   Chars
mal               280     913    7075
clojure           369    1164   10099
ruby              481    1513   13247
coffee            532    2020   15653
racket            550    1937   17229
python            670    1997   19632
nim               715    2453   20263
r                 812    2283   21644
lua               914    2640   23102
ocaml             597    3704   24371
scala             871    3060   24885
js                920    3059   26437
php               940    3011   27332
matlab            959    2485   28322
perl             1081    3511   29091
miniMAL           857    3104   29983
haskell           985    4452   30115
bash             1347    3586   31717
go               1297    4744   36321
ps               1409    6117   38651
forth            1609    6826   44715
cs               1249    4136   45039
java             1591    4966   53223
rust             1897    5702   56516
vb               1556    5175   58099
make             1593    6055   62310
c                2304    6957   73047

Thanks to Joel Martin for this guide. This was my first time getting close to Lisp and I recommend others to work through the MAL guide as well. But be aware, once you get through the first steps, it becomes addictive.

Comments on Reddit and Hacker News.