| 1 | # Mal/Make-a-Lisp FAQ |
| 2 | |
| 3 | <a name="why_mal"></a> |
| 4 | |
| 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 |
| 13 | implemented in bash. His presentation led me to ask myself the question |
| 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. |
| 16 | |
| 17 | Interestingly, the current pedagogical/educational purpose of mal |
| 18 | happened due to a semantic naming accident (naming is such a fraught |
| 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). |
| 29 | |
| 30 | |
| 31 | <a name="code_split"></a> |
| 32 | |
| 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) |
| 47 | * the Clojure implementation has no types file and fairly trivial |
| 48 | reader and printer files (just to modify the Clojure reader/writer |
| 49 | slightly) and a fairly trivial core file |
| 50 | * ruby types and the functions that operate on them are very "Lispy" |
| 51 | so the Ruby types file and core file are very small. |
| 52 | |
| 53 | The env file is somewhat more arbitrary, however, it is |
| 54 | a self-contained module that is implemented early and changes very |
| 55 | little after that, so I decided to separate it. Also, for languages |
| 56 | that have hierarchical maps/dictionaries (e.g. Javascript |
| 57 | objects/prototype chain), you do not necessarily need an env file. |
| 58 | |
| 59 | Another way of summarizing this answer is that the step files |
| 60 | represent the core of what makes something a Lisp, the rest of the |
| 61 | modules are just language specific details (they may be the harder |
| 62 | than the Lisp part, but that is due to the nature of the target |
| 63 | language not because of Lisp functionality per se). |
| 64 | |
| 65 | |
| 66 | <a name="steps"></a> |
| 67 | |
| 68 | ### Why are the mal/make-a-lisp steps structured the way they are? |
| 69 | |
| 70 | ### OR Why is X functionality in step Y instead of step Z? |
| 71 | |
| 72 | There is no single consistent rule that I have used to determine which |
| 73 | functionality goes in which step and the arrangement has changed |
| 74 | numerous times since the beginning of the project. There are several |
| 75 | different goals that I try and balance in determining which |
| 76 | functionality goes into which step: |
| 77 | |
| 78 | * **Optimize Lisp learning**: I want developers who are unfamiliar with |
| 79 | Lisp to be able to use the project and guide to learn about Lisp |
| 80 | without becoming overwhelmed. In many Lisp introductions, concepts |
| 81 | like quoting and homoiconicity (i.e. a user exposed eval function) |
| 82 | are introduced early. But these are fairly foreign to most other |
| 83 | languages so they are introduced in later steps in mal. I also try |
| 84 | to not to concentrate too many Lisp concepts in a single step. So |
| 85 | many steps contain one or two Lisp concepts plus some core function |
| 86 | additions that support those concepts. |
| 87 | |
| 88 | * **Optimize implementation language learning (equal-ish step |
| 89 | sizing)**: I try to structure the steps so that the target |
| 90 | implementation can be learned incrementally. This goal is the one |
| 91 | that has caused me to refactor the steps the most. Different |
| 92 | languages have different areas that they optimize and make simple |
| 93 | for the developer. For example, in Java (prior to 8) and PostScript |
| 94 | creating the equivalent of anonymous functions and function closures |
| 95 | is painful. In other languages, function closures are trivial, but |
| 96 | IO and error handling are tedious when you are first learning the |
| 97 | language (I am looking at you Haskell). So this goal is really about |
| 98 | trying to balance step size across multiple languages. |
| 99 | |
| 100 | * **Practical results early and continuous feedback**: it is |
| 101 | a scientific fact that many small rewards are more motivating than |
| 102 | a single large reward (citation intentionally omitted, get a small |
| 103 | reward by googling it yourself). Each step in mal adds new |
| 104 | functionality that can actually be exercised by the implementor and, |
| 105 | just as importantly, easily tested. |
| 106 | |
| 107 | Also, the step structure of mal/make-a-lisp is not perfect. It never |
| 108 | will be perfect, but there are some areas that could be improved. The |
| 109 | most glaring problem is that step1 is on the heavy/large size because |
| 110 | in most languages you have to implement a good portion of the |
| 111 | reader/printer before you can begin using/testing the step. The |
| 112 | compromise I have settled on for now is to put extra detail in the |
| 113 | process guide for step1 and to be clear that many of the types are |
| 114 | deferrable until later. But I am always open to suggestions. |
| 115 | |
| 116 | |
| 117 | <a name="add_implementation"></a> |
| 118 | |
| 119 | ### Will you add my new implementation? |
| 120 | |
| 121 | Absolutely! I want mal to have a idiomatic implementation in every |
| 122 | programming language. |
| 123 | |
| 124 | Here are a few guidelines for getting your implementation accepted |
| 125 | into the main repository: |
| 126 | |
| 127 | * Your implementation needs to be complete enough to self-host. This |
| 128 | means that all the mandatory tests should pass in both direct and |
| 129 | self-hosted modes: |
| 130 | ```bash |
| 131 | make "test^[IMPL_NAME]" |
| 132 | make MAL_IMPL=[IMPL_NAME] "test^mal" |
| 133 | ``` |
| 134 | You do not need to pass the final optional tests for stepA that are |
| 135 | marked as optional and not needed for self-hosting. |
| 136 | |
| 137 | * Your implementation should follow the existing mal steps and |
| 138 | structure: Lisp-centric code (eval, eval_ast, quasiquote, |
| 139 | macroexpand) in the step files, other code in reader, printer, env, |
| 140 | and core files. See [code layout rationale](#code_split) above. |
| 141 | I encourage you to create implementations that take mal in new |
| 142 | directions for your own learning and experimentation, but for it to |
| 143 | be included in the main repository I ask that it follows the steps |
| 144 | and structure. |
| 145 | |
| 146 | * Your implementation should stick as much as possible to the accepted |
| 147 | idioms and conventions in that language. Try to create an |
| 148 | implementation that will not make an expert in that language say |
| 149 | "Woah, that's a strange way of doing things". And on that topic, |
| 150 | I make no guarantees that the existing implementations are |
| 151 | particularly idiomatic in their target languages (improvements are |
| 152 | welcome). However, if it is clear to me that your implementation is |
| 153 | not idiomatic in a given language then I will probably ask you to |
| 154 | improve it first. |
| 155 | |
| 156 | * If you are creating a new implementation for an existing |
| 157 | implementation (or somebody beats you to the punch while you are |
| 158 | working on it), there is still a chance I will merge your |
| 159 | implementation. If you can make a compelling argument that your |
| 160 | implementation is more idiomatic or significantly better than the |
| 161 | existing implementation then I may replace the existing one. |
| 162 | However, if your approach is different or unique from the existing |
| 163 | implementation, there is still a good chance I will merge your |
| 164 | implementation side-by-side with the existing one. In that case |
| 165 | I will add your github username as a suffix to the language |
| 166 | implementation directory. At the very least, even if I decide not to |
| 167 | merge your implementation, I am certainly willing to link to you |
| 168 | implementation once it is completed. |
| 169 | |
| 170 | * You do not need to implement line editing (i.e. readline) |
| 171 | functionality for your implementation, however, it is a nice |
| 172 | convenience for users of your implementation and I personally find |
| 173 | it saves a lot of time when I am creating a new implementation to |
| 174 | have line edit support early in the process. |
| 175 | |
| 176 | --- |
| 177 | |
| 178 | **Good questions that either don't have answer or need more detail** |
| 179 | |
| 180 | ### Why do some mal forms end in "\*" or "!" (swap!, def!, let\*, etc)? |