diff --git a/docs/exercise.md b/docs/exercise.md index 266d2c54..3f49933f 100644 --- a/docs/exercise.md +++ b/docs/exercise.md @@ -15,21 +15,20 @@ pass a command like 'load-file' before running the testsuite. Some solutions are given in the `examples` directory. Feel free to submit new solutions, or new exercises. - -- Implement the following functions with other built-in functions. - - `nil?`, `true?` and `false?` - - `empty?` - - `sequential?` +- Implement `nil?`, `true?`, `false?` and `sequential?` with other + built-in functions. - Implement `>`, `<=` and `>=` with `<`. -- Implement the following non-recursive functions. - - `hash-map` - - `list` - - `prn` - - `swap!` +- Implement `hash-map`, `list`, `prn` and `swap!` as non-recursive + functions. -- Implement `map` with a recursion. +- Implement `count`, `nth`, `map`, `concat` and `conj` with the empty + constructor `()`, `empty?`, `cons`, `first` and `rest`. + + Let `count` and `nth` benefit from tail call optimization. + + Try to replace explicit recursions with calls to `reduce` and `foldr`. - Implement the `do` special as a non-recursive function. The special form will hide your implementation, so in order to test it, you will @@ -38,7 +37,7 @@ submit new solutions, or new exercises. - Implement `let*` as a macro that uses `fn*` and recursion. The same remark applies. -- Implement `apply` as a macro. +- Implement `apply`. - Implement maps using lists. - Recall how maps must be evaluated. diff --git a/examples/exercises.mal b/examples/exercises.mal index 821ad3c9..2ba330f5 100644 --- a/examples/exercises.mal +++ b/examples/exercises.mal @@ -1,11 +1,11 @@ ;; These are the answers to the questions in ../docs/exercise.md. +(load-file "../core.mal") + (def! nil? (fn* [x] (= x nil ))) (def! true? (fn* [x] (= x true ))) (def! false? (fn* [x] (= x false))) -(def! empty? (fn* [xs] (= 0 (count xs)))) - (def! sequential? (fn* [x] (if (list? x) true (if (vector? x) true false)))) (def! > (fn* [a b] (< b a) )) @@ -17,13 +17,33 @@ (def! prn (fn* [& xs] (println (apply pr-str xs)))) (def! swap! (fn* [a f & xs] (reset! a (apply f (deref a) xs)))) +(def! count + (let* [inc_left (fn* [acc _] (inc acc))] + (fn* [xs] (if (nil? xs) 0 (reduce inc_left 0 xs))))) +(def! nth + (fn* [xs index] + (if (empty? xs) + (throw "nth: index out of range") + (if (< index 0) + (throw "nth: index out of range") + (if (zero? index) + (first xs) + (nth (rest xs) (dec index))))))) (def! map (fn* [f xs] - (if (empty? xs) - () - (cons (f (first xs)) (map f (rest xs)))))) + (let* [iter (fn* [x acc] (cons (f x) acc))] + (foldr iter () xs)))) +(def! concat + (let* [concat2 (fn* [xs ys] (foldr cons ys xs))] + (fn* [& xs] (foldr concat2 () xs)))) +(def! conj + (let* [flip_cons (fn* [xs x] (cons x xs))] + (fn* [xs & ys] + (if (vector? xs) + (apply vector (concat xs ys)) + (reduce flip_cons xs ys))))) -(def! do2 (fn* [& xs] (nth xs (- (count xs) 1)))) +(def! do2 (fn* [& xs] (nth xs (dec (count xs))))) (defmacro! let2 ;; Must be a macro because the first argument must not be evaluated. @@ -32,20 +52,19 @@ form ;; This let* increases the readability, but the values could ;; easily be replaced below. - (let* [key (nth binds 0) + (let* [key (first 0) val (nth binds 1) more (rest (rest binds))] `((fn* [~key] (let2 ~more ~form)) ~val))))) (def! apply - (let* [ - ;; (a b [c d]) -> (a b c d) - flat_end (fn* [xs] - (if (= 1 (count xs)) - (first xs) ; [c d] above - (cons (first xs) (flat_end (rest xs))))) - ;; x -> 'x to protect the already-evaluated arguments. - quote_elt (fn* [x] `(quote ~x)) - ] - (fn* [& xs] - (eval (map quote_elt (flat_end xs)))))) + ;; Replace (f a b [c d]) with ('f 'a 'b 'c 'd) then evaluate the + ;; resulting function call (the surrounding environment does not + ;; matter when evaluating a function call). + ;; Use nil as marker to detect deepest recursive call. + (let* [q (fn* [x] (list 'quote x)) + iter (fn* [x acc] + (if (nil? acc) ; x is the last element (a sequence) + (map q x) + (cons (q x) acc)))] + (fn* [& xs] (eval (foldr iter nil xs)))))