1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 13:55:55 +03:00

exercices: fix apply again. It must be a function, not a macro.

It is more interesting to ask an implementation of count from empty?
than the reverse.

Ask for nth, map, concat and conj.

Allow core.mal in answers. Currently, requires the branch with foldr.
This commit is contained in:
Nicolas Boulenguez 2019-05-11 12:11:20 +02:00
parent 304930e749
commit 1ca3ee3dcd
2 changed files with 48 additions and 30 deletions

View File

@ -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 Some solutions are given in the `examples` directory. Feel free to
submit new solutions, or new exercises. submit new solutions, or new exercises.
- Implement `nil?`, `true?`, `false?` and `sequential?` with other
- Implement the following functions with other built-in functions. built-in functions.
- `nil?`, `true?` and `false?`
- `empty?`
- `sequential?`
- Implement `>`, `<=` and `>=` with `<`. - Implement `>`, `<=` and `>=` with `<`.
- Implement the following non-recursive functions. - Implement `hash-map`, `list`, `prn` and `swap!` as non-recursive
- `hash-map` functions.
- `list`
- `prn`
- `swap!`
- 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 - Implement the `do` special as a non-recursive function. The special
form will hide your implementation, so in order to test it, you will 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. - Implement `let*` as a macro that uses `fn*` and recursion.
The same remark applies. The same remark applies.
- Implement `apply` as a macro. - Implement `apply`.
- Implement maps using lists. - Implement maps using lists.
- Recall how maps must be evaluated. - Recall how maps must be evaluated.

View File

@ -1,11 +1,11 @@
;; These are the answers to the questions in ../docs/exercise.md. ;; These are the answers to the questions in ../docs/exercise.md.
(load-file "../core.mal")
(def! nil? (fn* [x] (= x nil ))) (def! nil? (fn* [x] (= x nil )))
(def! true? (fn* [x] (= x true ))) (def! true? (fn* [x] (= x true )))
(def! false? (fn* [x] (= x false))) (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! sequential? (fn* [x] (if (list? x) true (if (vector? x) true false))))
(def! > (fn* [a b] (< b a) )) (def! > (fn* [a b] (< b a) ))
@ -17,13 +17,33 @@
(def! prn (fn* [& xs] (println (apply pr-str xs)))) (def! prn (fn* [& xs] (println (apply pr-str xs))))
(def! swap! (fn* [a f & xs] (reset! a (apply f (deref a) 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 (def! map
(fn* [f xs] (fn* [f xs]
(if (empty? xs) (let* [iter (fn* [x acc] (cons (f x) acc))]
() (foldr iter () xs))))
(cons (f (first xs)) (map f (rest 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 (defmacro! let2
;; Must be a macro because the first argument must not be evaluated. ;; Must be a macro because the first argument must not be evaluated.
@ -32,20 +52,19 @@
form form
;; This let* increases the readability, but the values could ;; This let* increases the readability, but the values could
;; easily be replaced below. ;; easily be replaced below.
(let* [key (nth binds 0) (let* [key (first 0)
val (nth binds 1) val (nth binds 1)
more (rest (rest binds))] more (rest (rest binds))]
`((fn* [~key] (let2 ~more ~form)) ~val))))) `((fn* [~key] (let2 ~more ~form)) ~val)))))
(def! apply (def! apply
(let* [ ;; Replace (f a b [c d]) with ('f 'a 'b 'c 'd) then evaluate the
;; (a b [c d]) -> (a b c d) ;; resulting function call (the surrounding environment does not
flat_end (fn* [xs] ;; matter when evaluating a function call).
(if (= 1 (count xs)) ;; Use nil as marker to detect deepest recursive call.
(first xs) ; [c d] above (let* [q (fn* [x] (list 'quote x))
(cons (first xs) (flat_end (rest xs))))) iter (fn* [x acc]
;; x -> 'x to protect the already-evaluated arguments. (if (nil? acc) ; x is the last element (a sequence)
quote_elt (fn* [x] `(quote ~x)) (map q x)
] (cons (q x) acc)))]
(fn* [& xs] (fn* [& xs] (eval (foldr iter nil xs)))))
(eval (map quote_elt (flat_end xs))))))