Commit | Line | Data |
---|---|---|
9678065e NB |
1 | # Exercises to learn MAL |
2 | ||
3 | The process introduces LISP by describing the internals of selected | |
4 | low-level constructs. As a complementary and more traditional | |
5 | approach, you may want to solve the following exercises in the MAL | |
6 | language itself, using any of the existing implementations. | |
7 | ||
8 | You are encouraged to use the shortcuts defined in the step files | |
4f72e010 NB |
9 | (`not`...) and `the `lib/` subdirectory (`reduce`...) whenever you |
10 | find that they increase the readability. | |
9678065e NB |
11 | |
12 | The difficulty is progressive in each section, but they focus on | |
13 | related topics and it is recommended to start them in parallel. | |
14 | ||
15 | Some solutions are given in the `examples` directory. Feel free to | |
16 | submit new solutions, or new exercises. | |
17 | ||
18 | ## Replace parts of the process with native constructs | |
19 | ||
0544b52f NB |
20 | Once you have a working implementation, you may want to implement |
21 | parts of the process inside the MAL language itself. This has no other | |
22 | purpose than learning the MAL language. Once it exists, a built-in | |
23 | implementation will always be more efficient than a native | |
24 | implementation. Also, the functions described in MAL process are | |
25 | selected for educative purposes, so portability accross | |
26 | implementations does not matter much. | |
27 | ||
28 | You may easily check your answers by passing them directly to the | |
29 | interpreter. They will hide the built-in functions carrying the same | |
9c20ca3f | 30 | names, and the usual tests will check them. |
9678065e NB |
31 | ``` |
32 | make REGRESS=1 TEST_OPTS='--hard --pre-eval=\(load-file\ \"../answer.mal\"\)' test^IMPL^stepA | |
33 | ``` | |
0544b52f | 34 | |
9678065e NB |
35 | - Implement `nil?`, `true?`, `false?`, `empty?` and `sequential` with |
36 | another built-in function. | |
0544b52f NB |
37 | |
38 | - Implement `>`, `<=` and `>=` with `<`. | |
39 | ||
fbfe6784 | 40 | - Implement `list`, `vec`, `prn`, `hash-map` and `swap!` as non-recursive |
1ca3ee3d NB |
41 | functions. |
42 | ||
43 | - Implement `count`, `nth`, `map`, `concat` and `conj` with the empty | |
44 | constructor `()`, `empty?`, `cons`, `first` and `rest`. | |
45 | ||
9678065e NB |
46 | You may use `or` to make the definition of `nth` a bit less ugly, |
47 | but avoid `cond` because its definition refers to `nth`. | |
48 | ||
1ca3ee3d | 49 | Let `count` and `nth` benefit from tail call optimization. |
0544b52f | 50 | |
1ca3ee3d | 51 | Try to replace explicit recursions with calls to `reduce` and `foldr`. |
0544b52f | 52 | |
42d31a20 NB |
53 | Once you have tested your solution, you should comment at least |
54 | `nth`. Many implementations, for example `foldr` in `core.mal`, | |
55 | rely on an efficient `nth` built-in function. | |
56 | ||
0544b52f NB |
57 | - Implement the `do` special as a non-recursive function. The special |
58 | form will hide your implementation, so in order to test it, you will | |
59 | need to give it another name and adapt the test accordingly. | |
60 | ||
9c20ca3f NB |
61 | - Implement quoting with macros. |
62 | The same remark applies. | |
63 | ||
64 | - Implement most of `let*` as a macro that uses `fn*` and recursion. | |
304930e7 | 65 | The same remark applies. |
9678065e NB |
66 | A macro is necessary because a function would attempt to evaluate |
67 | the first argument. | |
0544b52f | 68 | |
9c20ca3f NB |
69 | Once your answer passes most tests and you understand which part is |
70 | tricky, you should search for black magic recipes on the web. Few of | |
71 | us mortals are known to have invented a full solution on their own. | |
7977c2cb | 72 | |
1ca3ee3d | 73 | - Implement `apply`. |
0544b52f NB |
74 | |
75 | - Implement maps using lists. | |
304930e7 NB |
76 | - Recall how maps must be evaluated. |
77 | - In the tests, you may want to replace `{...}` with `(hash-map ...)`. | |
78 | - An easy solution relies on lists alterning keys and values, so | |
79 | that the `hash-map` is only a list in reverse order so that the | |
80 | last definition takes precedence during searches. | |
81 | - As a more performant solution will use lists to construct trees, | |
82 | and ideally keep them balanced. You will find examples in most | |
83 | teaching material about functional languages. | |
84 | - Recall that `dissoc` is an optional feature. One you can implement | |
85 | dissoc is by assoc'ing a replacement value that is a magic delete | |
86 | keyword (e.g.: `__..DELETED..__`) which allows you to shadow | |
87 | values in the lower levels of the structure. The hash map | |
88 | functions have to detect that and do the right thing. e.g. `(keys | |
89 | ...)` might have to keep track of deleted values as it is scanning | |
90 | the tree and not add those keys when it finds them further down | |
91 | the tree. | |
0544b52f | 92 | |
0544b52f | 93 | - Implement macros within MAL. |
9678065e NB |
94 | |
95 | ## More folds | |
96 | ||
97 | - Compute the sum of a sequence of numbers. | |
98 | - Compute the product of a sequence of numbers. | |
99 | ||
100 | - Compute the logical conjunction ("and") and disjunction ("or") of a | |
101 | sequence of MAL values interpreted as boolean values. For example, | |
102 | `(conjunction [true 1 0 "" "a" nil true {}])` | |
103 | should evaluate to `false` or `nil` because of the `nil` element. | |
104 | ||
105 | Why are folds not the best solution here, in terms of average | |
106 | performances? | |
107 | ||
108 | - Does "-2-3-4" translate to `(reduce - 0 [2 3 4])`? | |
109 | ||
110 | - Suggest better solutions for | |
111 | `(reduce str "" xs)` and | |
112 | `(reduce concat [] xs)`. | |
113 | ||
114 | - What does `(reduce (fn* [acc _] acc) xs)` nil answer? | |
115 | ||
116 | - The answer is `(fn* [xs] (reduce (fn* [_ x] x) nil xs))`. | |
117 | What was the question? | |
118 | ||
119 | - What is the intent of | |
120 | `(reduce (fn* [acc x] (if (< acc x) x acc)) 0 xs)`? | |
121 | ||
122 | Why is it the wrong answer? | |
123 | ||
124 | - Though `(sum (map count xs))` or `(count (apply concat xs))` can be | |
125 | considered more readable, implement the same effect with a single loop. | |
126 | - Compute the maximal length in a list of lists. | |
127 | ||
128 | - How would you name | |
129 | `(fn* [& fs] (foldr (fn* [f acc] (fn* [x] (f (acc x)))) identity fs))`? |