
Mal – Make a Lisp, in 68 languages - ingve
https://github.com/kanaka/mal#mal---make-a-lisp
======
chc4
I got decently far in my implementation, but probably wouldn't suggest you use
this. The actual guide is pretty sloppily written and doesn't outline all that
much, along with not explicitly saying what you have to define at each step.
It doesn't define what the different cases of the mal Lisp value type should
be, for example, or when to implement pretty much all of the "optional"
extensions it mentions in the first or second chapter. Most of the forms in
each chapter get a sentence explanation of what they do, at most, and I hit
several snags where I read an explanation and thought I implemented it
correctly, only to find out there was something subtly wrong with the obvious
implementation.

Also, writing a Lisp in a purely functional language isn't the best :X Things
like by-reference function environments and atoms are hard to implement if
you're not Haskell, at least if you try in the method that the guide
outlines...

------
sassy_samurai
There is a similar project called "Build Your Own Lisp"[0] (BYOL), which is a
book that walks one through making a Lisp in C. Has anybody here done BYOL?
How does it compare to MAL? If you had to choose one to learn how to create a
Lisp, which would you choose?

[0]: [http://www.buildyourownlisp.com/](http://www.buildyourownlisp.com/)

~~~
earenndil
I looked at BYOL a while ago, but was turned off by the fact that the author
wants you to use his parser. I don't know why, but I didn't like that. I feel
like there's value in using a pre-existing, widely-used parser like yacc/lex,
for no other reason than that it already exist and is widely used.

~~~
sassy_samurai
Oh man, that's a bummer. I think one of the great joys in constructing your
own compiler or interpreter is making your own lexer and parser. Doesn't using
a pre-made lexer/parser kind of defeat the purpose of making your own
compiler/interpreter?

------
falcolas
This is a fantastic way to "waste" a weekend, and there's so much learning to
be had.

The radically simple implementation of TCO really surprised me.

~~~
gbacon
See the design narrative at “Step 5: Tail call optimization”[0] in The Make-A-
Lisp Process[1].

[0]:
[https://github.com/kanaka/mal/blob/master/process/guide.md#s...](https://github.com/kanaka/mal/blob/master/process/guide.md#step-5-tail-
call-optimization)

[1]:
[https://github.com/kanaka/mal/blob/master/process/guide.md](https://github.com/kanaka/mal/blob/master/process/guide.md)

------
omginternets
Writing/understanding a lisp interpreter is a longstanding item on my TODO
list.

Question: how do I go from tackling this to tackling compilers?

~~~
whistlerbrk
I was just recommended "Lisp in Small Pieces" for just this.

~~~
DigitalJack
I have that book, though I haven't looked at it in years. I found it
intractable then. I've been using clojure in the interim and feel like I've
learned quite a bit, so maybe I should go back and look again.

------
hdhzy
Also check out miniMAL [0] - lisp in JSON form!

[0]: [https://github.com/kanaka/miniMAL](https://github.com/kanaka/miniMAL)

------
Sjenk
I am curious why is hackernews always so happy and obsessed with lisp, there
is almost daily a lisp post on the frontpage. I dont really care/mind but I am
just curious as why people love lisp so much. The only thing that I notice
about Lisp are the bracket jokes and memes.

~~~
josmar
After being a user of languages for a long time, writing my own LISP was the
experience of leaving Platons cave and seeing what the thing I use daily
really is made of. What is "if"? What is a boolean, a variable, a function?

It's a beautiful thing. I wish I would never have to climb down into the cave
(languages with more advanced parsers) again.

~~~
spc476
You climed in the wrong direction. What you want is assembly. That's where
software ends up.

Also, it's interpreters all the way down. Even assembly is interpreted.

~~~
cabaalis
>Also, it's interpreters all the way down. Even assembly is interpreted.

Speak truth to power. Every time someone says this there seem to be nothing
but haters.

~~~
dreamcompiler
> Every time someone says this there seem to be nothing but haters.

Because it's not completely true. If a CPU is microcoded, then it's accurate
to say "assembly is interpreted" because every instruction is effectively an
address into a lookup table of microinstructions. But in a non-microcoded
(e.g. purely RISC) CPU, the bits of the instruction word are effectively
enable and data lines to a bunch of hardware logic gates and flip-flops, which
cause register transfers and arithmetic operations to happen. In this case,
the ones and zeros in the instruction word _are_ voltage levels on logic
gates. Calling the latter "interpretation" is a stretch.

To be fair, there aren't many pure RISC implementations around these days.
Most everything has some degree of microcode involved, so to that extent
you're right.

~~~
kazinator
It's interpreted because the instructions are fetched one by one. A piano roll
is intepreted, even though its holes just activate keys with a "horizontal
encoding". It is interpreted because it moves through the piano, and a little
piece of it activates a behavior any one time, without leaving a permanent
record.

------
yosyp
Could someone explain this gem in the C version?

[https://github.com/kanaka/mal/blob/master/c/types.c#L170](https://github.com/kanaka/mal/blob/master/c/types.c#L170)

~~~
gbacon
In struct MalVal[0], the values f0 through f20, where each holds a pointer-to-
functions of the corresponding arity, live in a big (multifacted?) union.
Using arg_cnt, the switch assigns func to the appropriate value in mv->val
using the appropriate cast.

For example, when arg_cnt is 2, mv->val.f2 receives func converted to void *
(* )(void* , void* ), that is, pointer to a function that accepts two
parameters of type pointer-to-void and returns pointer-to-void. [Weird spacing
to effectively escape the asterisks that would otherwise be treated as
italicizing markup.]

C permits conversion between pointer-to-function of one type to pointer-to-
function of another type. The inverse is also defined, and the resulting
pointer will compare equal to the original. However, calling a function
through a pointer-to-function where the types do not match invokes undefined
behavior.

[0]:
[https://github.com/kanaka/mal/blob/master/c/types.h#L85](https://github.com/kanaka/mal/blob/master/c/types.h#L85)

~~~
copx
This could be made massively less ugly and more readable by typedef'ing the
function pointer types by the way. Then one could just write:

    
    
      case 13: mv->val.f13 = (F13)func; break;
    

As a C programmer I got to say people in general don't use typedef for
function pointer types often enough. The types are so ugly and verbose, they
should be typedef'ed by default.

~~~
kazinator
This code could take advantage of the fact that the type is a union.

One can bend the rules of prim-and-proper well-defined ISO C just a little bit
and assign to the simplest function pointer, taking advantage of all function
pointers having the same representation on every machine known to mortal
hacker, and all being overlaid by the union. Thus all the cases collapse down
to this:

    
    
       mv->val.u.f0 = (void (*)(void)) func;
    

done. Now if this is 13 arguments, then strictly speaking, accessing
mv->val.uf13 is not well-defined behavior. That's only going to be problem
when it _actually_ doesn't work, though. A big problem, then. :)

~~~
gbacon
I can respect the author taking the trouble to avoid undefined behavior.
Technically correct is the best kind of correct.

~~~
kazinator
That sort of love affair with ISO gospel lands on the rocks as soon as your
Lisp stuffs a couple of tag bits into a C pointer.

------
suzuki
There is another Lisp implemented in 42 languages (including Ceylon, Dylan,
Oz, Pike, Scratch, Smalltalk, SML etc.):

[https://github.com/zick/ZickStandardLisp](https://github.com/zick/ZickStandardLisp)

The benchmark results of 42 implementations are very impressive:

[http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stag...](http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stage1.png) [http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stag...](http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stage2.png) [http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stag...](http://blog.bugyo.tk/lyrical/wp-
content/uploads/2014/12/stage3.png)

which are discussed in Japanese at:

[http://blog.bugyo.tk/lyrical/archives/2024](http://blog.bugyo.tk/lyrical/archives/2024)

------
tekknolagi
Not to toot my own horn too much, but I have also written a tutorial[0] aimed
at building a Lisp in OCaml. I wrote it to teach as much as learn how to go
about it.

[0]([https://bernsteinbear.com/blog/lisp/](https://bernsteinbear.com/blog/lisp/))

------
arunc
I tried finding the lines of code in each language with cloc. C/C++ Header
contains a sum of C and CPP header files. Python has been used across
different language dirs as wrapper script or so, otherwise, it's own
implementation is probably 1350 lines at max. This would also probably include
bugs in cloc.

    
    
        github.com/AlDanial/cloc v 1.70  T=2.86 s (433.6 files/s, 67570.0 lines/s)
        --------------------------------------------------------------------------------
        Language                      files          blank        comment           code
        --------------------------------------------------------------------------------
        Visual Basic                     36           1569            132           8036
        Swift                            36           1199           1606           7951
        SQL                              36            956            649           7594
        Pascal                           19            704            951           6253
        Ada                              28           1975            380           5856
        Elm                              19           1619            226           5647
        awk                              16            290             15           5169
        Lisp                             37            825            256           4562
        VHDL                             17            434             32           4228
        C#                               20            658            346           4175
        Python                           37            574            394           4018
        make                             89           1244            873           4010
        C                                19            466            392           3499
        Perl                             35            413            171           3450
        Go                               17            281            148           3397
        Rust                             18            304            130           3331
        JavaScript                       43            449            247           3235
        Forth                            18            516            158           3228
        C++                              18            568             71           2986
        Java                             17            325            135           2938
        D                                17            363             10           2938
        Racket                           35            480            385           2740
        TypeScript                       17            284             56           2740
        Markdown                          8            714              0           2730
        Rexx                             17            283             47           2713
        Bourne Shell                     30            390            315           2692
        Tcl/Tk                           17            284             43           2416
        Dart                             16            255             41           2303
        F#                               20            337             13           2298
        Objective C                      17            285            150           2189
        Erlang                           17            277            191           2185
        MATLAB                           26            184            128           2165
        Haxe                             19            254             79           2161
        Crystal                          18            417             58           2146
        vim script                       17            242             50           2076
        Haskell                          17            331             99           2000
        PHP                              18            249            120           1955
        Lua                              18            248             87           1874
        Scala                            16            221            113           1816
        Elixir                           19            366             35           1809
        JSON                             28            246              0           1765
        R                                17            201            100           1612
        Groovy                           17            170             98           1582
        Kotlin                           17            312              0           1554
        Julia                            17            189            126           1405
        Nim                              16            320             36           1397
        lex                              18            209              0           1355
        Ruby                             17            167             87           1264
        OCaml                            15            109             98           1211
        PowerShell                       11            128             71           1146
        ClojureC                         15            257            129           1053
        CoffeeScript                     17            195            126           1037
        CSS                               6            150            132            792
        C/C++ Header                     22            266             44            752
        HTML                              2             36             35            486
        Bourne Again Shell               47              1              4            135
        YAML                              2              6             10             86
        Maven                             1              2              7             85
        Clojure                           2              8             12             65
        ClojureScript                     1              1              0              2
        --------------------------------------------------------------------------------
        SUM:                           1242          24806          10447         158293
        --------------------------------------------------------------------------------

~~~
flavio81
Note that the number of intermediate "steps" uploaded to github, as well as
the number of tests, differ for each language.

~~~
kanaka
The steps and main files are the same for every implementation (that's part of
the requirements for merging into the main tree). Some implementations have
additional files like readline, utility routines, etc. But for the most part
the general structure and file divisions are very similar.

------
faaq
What is the smallest Mal implementation? OCalm?

~~~
kanaka
Implementation size is hard to compare accurately across languages (bytes?
lines of code? exclude comments? excluding leading spaces?). Also, the mal
implementations were created by many different people so individual style
plays a large role in concision/verbosity.

However, that being said, the following implementations are "smallish" (in
both lines of code and bytes): mal itself (self-hosted), Clojure, factor, Perl
6, Ruby. The following are "largish": Awk, PL-pgSQL, C, PL-SQL, Chuck, Swift
2, Ada.

OCaml is in the "smallest" third of the implementations.

------
ertucetin
Why?

~~~
flavio81
Writing an interpreter (or compiler) greatly enhances one's programming
skills.

~~~
rjsw
Why not work on improving an existing one ?

~~~
chriswarbo
While we're at it, why don't we replace high school math tests with one
question: "Solve the Riemann hypothesis". We can replace the physics
curriculum with "Unify quantum mechanics with general relativity", and throw
out computing courses in favour of "Prove whether or not P = NP".

It's important to re-do things which others have already worked out, in order
to reach a greater understanding :)

~~~
rjsw
Whatever.

I learned how to implement Lisp by reading the source code for multiple
compilers. Started with Franz Lisp, then KCL and CMUCL. I don't feel that a
toy interpreter is going to teach the same things.

~~~
ozzmotik
everyone learns in different ways, don't be too attached to your own
perspective. we all interpret the world through different lenses and need to
learn through the expressions of different methodologies. some people benefit
heavily by implementing something themselves and not just reading through
something someone else wrote. some people feed off of that practical and
robust experience of implementation in order to get a proper context for how
things actually work established in their head. concrete examples are more
helpful than you might think to people who aren't as capable of abstract
thought and logical analysis of something from its constituent parts

~~~
rjsw
Maybe part of my reaction to this is a wish to avoid reinforcing the "Lisp is
an interpreted language" meme.

