Commit | Line | Data |
---|---|---|
3cbd2ef7 | 1 | # Mal/Make-a-Lisp FAQ |
263007a8 | 2 | |
8d8ca1f0 JM |
3 | <a name="why_mal"></a> |
4 | ||
81f5db50 JM |
5 | ### Why did you create mal/make-a-lisp? |
6 | ### OR Why the name "mal"? | |
7 | ### OR Why? | |
8 | ### OR Wat? | |
9 | ||
10 | In November of 2013, Alan Dipert gave a [lightning talk at | |
11 | Clojure/conj](https://www.youtube.com/watch?v=bmHTFo2Rf2w#t=28m55s) | |
12 | about [gherkin](https://github.com/alandipert/gherkin), a Lisp | |
e1ab693c | 13 | implemented in bash. His presentation led me to ask myself the question |
1e66ee3f JM |
14 | of whether a Lisp could be created using the GNU Make macro language. |
15 | As you have probably guessed, the answer to that question is yes. | |
81f5db50 JM |
16 | |
17 | Interestingly, the current pedagogical/educational purpose of mal | |
18 | happened due to a semantic naming accident (naming is such a fraught | |
1e66ee3f JM |
19 | task in computer science). If I am remembering correctly, the name |
20 | "mal" original meant "MAke Lisp". I do not remember precisely why | |
21 | I continued to create more implementations, apart from the fact that | |
22 | it was a fun challenge, but after the make implementation, many of the | |
23 | others were relatively easy. At some point during that process, | |
24 | I realized that the multiple implementations and incremental steps | |
25 | (which was originally just for my own clarity) was a useful learning | |
26 | tool and so the "mal" name became a double entendre for "Make, A Lisp" | |
27 | and "make-a-lisp" (and eventually just the latter given that the make | |
28 | implementation is now just a small part of the whole). | |
81f5db50 JM |
29 | |
30 | ||
8d8ca1f0 JM |
31 | <a name="code_split"></a> |
32 | ||
263007a8 JM |
33 | ### Why is some code split into steps and some code not? |
34 | ||
35 | The split between code that goes in steps and code that goes into other files | |
36 | is not completely arbitrary (a bit arbitrary, but not completely). My rule of | |
37 | thumb is something like this: if the code is specific and necessary for | |
38 | implementing a Lisp then it belongs in the step files. If the purpose of the | |
39 | code is for implementing new dynamic data-types/objects and the functions or | |
40 | methods that operate on those types, then it goes in separate files. | |
41 | ||
42 | If the target language has types and functions that resemble mal types, then | |
43 | those files tend to be very small or non-existent. Examples: | |
44 | ||
45 | * the mal implementation has no types, reader, printer files and | |
46 | has a trivial core file (just to hoist underlying functions) | |
d78d9393 JM |
47 | * the Clojure implementation has no types file and a fairly trivial |
48 | core file | |
263007a8 JM |
49 | * ruby types and the functions that operate on them are very "Lispy" |
50 | so the Ruby types file and core file are very small. | |
51 | ||
52 | The env file is somewhat more arbitrary, however, it is | |
53 | a self-contained module that is implemented early and changes very | |
54 | little after that, so I decided to separate it. Also, for languages | |
55 | that have hierarchical maps/dictionaries (e.g. Javascript | |
56 | objects/prototype chain), you do not necessarily need an env file. | |
57 | ||
58 | Another way of summarizing this answer is that the step files | |
59 | represent the core of what makes something a Lisp, the rest of the | |
60 | modules are just language specific details (they may be the harder | |
61 | than the Lisp part, but that is due to the nature of the target | |
62 | language not because of Lisp functionality per se). | |
63 | ||
8d8ca1f0 JM |
64 | |
65 | <a name="steps"></a> | |
66 | ||
263007a8 JM |
67 | ### Why are the mal/make-a-lisp steps structured the way they are? |
68 | ||
69 | ### OR Why is X functionality in step Y instead of step Z? | |
70 | ||
71 | There is no single consistent rule that I have used to determine which | |
72 | functionality goes in which step and the arrangement has changed | |
73 | numerous times since the beginning of the project. There are several | |
74 | different goals that I try and balance in determining which | |
75 | functionality goes into which step: | |
76 | ||
77 | * **Optimize Lisp learning**: I want developers who are unfamiliar with | |
78 | Lisp to be able to use the project and guide to learn about Lisp | |
79 | without becoming overwhelmed. In many Lisp introductions, concepts | |
80 | like quoting and homoiconicity (i.e. a user exposed eval function) | |
81 | are introduced early. But these are fairly foreign to most other | |
82 | languages so they are introduced in later steps in mal. I also try | |
83 | to not to concentrate too many Lisp concepts in a single step. So | |
84 | many steps contain one or two Lisp concepts plus some core function | |
85 | additions that support those concepts. | |
86 | ||
87 | * **Optimize implementation language learning (equal-ish step | |
88 | sizing)**: I try to structure the steps so that the target | |
89 | implementation can be learned incrementally. This goal is the one | |
90 | that has caused me to refactor the steps the most. Different | |
91 | languages have different areas that they optimize and make simple | |
92 | for the developer. For example, in Java (prior to 8) and PostScript | |
93 | creating the equivalent of anonymous functions and function closures | |
94 | is painful. In other languages, function closures are trivial, but | |
95 | IO and error handling are tedious when you are first learning the | |
96 | language (I am looking at you Haskell). So this goal is really about | |
97 | trying to balance step size across multiple languages. | |
98 | ||
99 | * **Practical results early and continuous feedback**: it is | |
100 | a scientific fact that many small rewards are more motivating than | |
101 | a single large reward (citation intentionally omitted, get a small | |
102 | reward by googling it yourself). Each step in mal adds new | |
0198b7a2 | 103 | functionality that can actually be exercised by the implementer and, |
263007a8 JM |
104 | just as importantly, easily tested. |
105 | ||
106 | Also, the step structure of mal/make-a-lisp is not perfect. It never | |
107 | will be perfect, but there are some areas that could be improved. The | |
108 | most glaring problem is that step1 is on the heavy/large size because | |
109 | in most languages you have to implement a good portion of the | |
110 | reader/printer before you can begin using/testing the step. The | |
111 | compromise I have settled on for now is to put extra detail in the | |
112 | process guide for step1 and to be clear that many of the types are | |
113 | deferrable until later. But I am always open to suggestions. | |
81f5db50 JM |
114 | |
115 | ||
8d8ca1f0 JM |
116 | <a name="add_implementation"></a> |
117 | ||
81f5db50 JM |
118 | ### Will you add my new implementation? |
119 | ||
120 | Absolutely! I want mal to have a idiomatic implementation in every | |
121 | programming language. | |
122 | ||
123 | Here are a few guidelines for getting your implementation accepted | |
124 | into the main repository: | |
125 | ||
81f5db50 | 126 | * Your implementation should follow the existing mal steps and |
8d8ca1f0 JM |
127 | structure: Lisp-centric code (eval, eval_ast, quasiquote, |
128 | macroexpand) in the step files, other code in reader, printer, env, | |
129 | and core files. See [code layout rationale](#code_split) above. | |
130 | I encourage you to create implementations that take mal in new | |
131 | directions for your own learning and experimentation, but for it to | |
132 | be included in the main repository I ask that it follows the steps | |
133 | and structure. | |
81f5db50 JM |
134 | |
135 | * Your implementation should stick as much as possible to the accepted | |
136 | idioms and conventions in that language. Try to create an | |
137 | implementation that will not make an expert in that language say | |
138 | "Woah, that's a strange way of doing things". And on that topic, | |
139 | I make no guarantees that the existing implementations are | |
140 | particularly idiomatic in their target languages (improvements are | |
141 | welcome). However, if it is clear to me that your implementation is | |
142 | not idiomatic in a given language then I will probably ask you to | |
143 | improve it first. | |
012e4179 JM |
144 | |
145 | * Your implementation needs to be complete enough to self-host. This | |
146 | means that all the mandatory tests should pass in both direct and | |
147 | self-hosted modes: | |
148 | ```bash | |
149 | make "test^[IMPL_NAME]" | |
150 | make MAL_IMPL=[IMPL_NAME] "test^mal" | |
151 | ``` | |
152 | You do not need to pass the final optional tests for stepA that are | |
153 | marked as optional and not needed for self-hosting (except for the | |
154 | `time-ms` function which is needed to run the micro-benchmark tests). | |
155 | ||
156 | * Create a `Dockerfile` in your directory that installs all the | |
157 | packages necessary to build and run your implementation. Refer to other | |
158 | implementations for examples of what the Dockerfile should contain. | |
159 | Build your docker image and tag it `kanaka/mal-test-[IMPL_NAME]`. | |
160 | The top-level Makefile has support for building/testing within | |
161 | docker with the `DOCKERIZE` flag: | |
162 | ```bash | |
163 | make DOCKERIZE=1 "test^[IMPL_NAME]" | |
164 | make DOCKERIZE=1 MAL_IMPL=[IMPL_NAME] "test^mal" | |
165 | ``` | |
166 | ||
167 | * Make sure the Travis build and test scripts pass locally: | |
168 | ```bash | |
169 | IMPL=[IMPL_NAME] ./.travis_build.sh | |
170 | ./.travis_test.sh test [IMPL_NAME] | |
171 | ``` | |
172 | ||
81f5db50 | 173 | * If you are creating a new implementation for an existing |
068b8d35 JM |
174 | implementation (or somebody beats you to the punch while you are |
175 | working on it), there is still a chance I will merge your | |
176 | implementation. If you can make a compelling argument that your | |
d78d9393 JM |
177 | implementation is more idiomatic or significantly better in some way |
178 | than the existing implementation then I may replace the existing | |
179 | one. However, if your approach is different or unique from the | |
180 | existing implementation, there is still a good chance I will merge | |
181 | your implementation side-by-side with the existing one. At the very | |
182 | least, even if I decide not to merge your implementation, I am | |
183 | certainly willing to link to you implementation once it is | |
184 | completed. | |
81f5db50 JM |
185 | |
186 | * You do not need to implement line editing (i.e. readline) | |
187 | functionality for your implementation, however, it is a nice | |
188 | convenience for users of your implementation and I personally find | |
189 | it saves a lot of time when I am creating a new implementation to | |
280688b2 | 190 | have line edit support early in the process. |
c088d7cf | 191 | |
d78d9393 | 192 | ### Why do some mal forms end in "\*" or "!" (swap!, def!, let\*, etc)? |
c088d7cf | 193 | |
d78d9393 JM |
194 | The forms that end in a bang mutate something: |
195 | * **def!** mutates the current environment | |
196 | * **swap!** and **reset!** mutate an atom to refer to a new value | |
197 | ||
198 | The forms that end in a star are similar to similar Clojure forms but | |
199 | are more limited in functionality: | |
200 | * **fn\*** does not do parameter destructuring and only supports | |
201 | a single body form. | |
202 | * **let\*** does not do parameter destructuring | |
203 | * **try\*** and **catch\*** do not support type matching of | |
204 | exceptions | |
c088d7cf | 205 |