exercices: fix apply again. It must be a function, not a macro.
authorNicolas Boulenguez <nicolas.boulenguez@free.fr>
Sat, 11 May 2019 10:11:20 +0000 (12:11 +0200)
committerNicolas Boulenguez <nicolas.boulenguez@free.fr>
Thu, 30 May 2019 14:46:04 +0000 (16:46 +0200)
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.

docs/exercise.md
examples/exercises.mal

index 266d2c5..3f49933 100644 (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
 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 `count`, `nth`, `map`, `concat` and `conj` with the empty
+  constructor `()`, `empty?`, `cons`, `first` and `rest`.
+
+  Let `count` and `nth` benefit from tail call optimization.
 
-- Implement `map` with a recursion.
+  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.
index 821ad3c..2ba330f 100644 (file)
@@ -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)            ))
 (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.
       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)))))