mirror of
https://github.com/kanaka/mal.git
synced 2024-09-11 13:55:55 +03:00
exercises: rebase, improve quasiquote and let*, avoid circular dependencies.
* Avoid including `lib/*.mal` to prevent unexpected circular dependencies (bitten by `foldr` using `nth`). * Ask `quote` before `let*`, the latter requires the former. Answer previous questions without `let*`. * Tell the reader that `let*` has various levels of difficulty. * Drop implicit dependency on `or`, soon out of step files. * Allow simple recursion in `let*` via a combinator.
This commit is contained in:
parent
7977c2cb5e
commit
9c20ca3f39
@ -27,9 +27,7 @@ implementations does not matter much.
|
||||
|
||||
You may easily check your answers by passing them directly to the
|
||||
interpreter. They will hide the built-in functions carrying the same
|
||||
names, and the usual tests (with REGRESS=1) will check them. The
|
||||
`runtest.py` script provide a convenient command-line parameter to
|
||||
pass a command like 'load-file' before running the testsuite.
|
||||
names, and the usual tests will check them.
|
||||
```
|
||||
make REGRESS=1 TEST_OPTS='--hard --pre-eval=\(load-file\ \"../answer.mal\"\)' test^IMPL^stepA
|
||||
```
|
||||
@ -60,13 +58,17 @@ make REGRESS=1 TEST_OPTS='--hard --pre-eval=\(load-file\ \"../answer.mal\"\)' te
|
||||
form will hide your implementation, so in order to test it, you will
|
||||
need to give it another name and adapt the test accordingly.
|
||||
|
||||
- Implement `let*` as a macro that uses `fn*` and recursion.
|
||||
- Implement quoting with macros.
|
||||
The same remark applies.
|
||||
|
||||
- Implement most of `let*` as a macro that uses `fn*` and recursion.
|
||||
The same remark applies.
|
||||
A macro is necessary because a function would attempt to evaluate
|
||||
the first argument.
|
||||
|
||||
- Implement quoting with macros.
|
||||
The same remark applies.
|
||||
Once your answer passes most tests and you understand which part is
|
||||
tricky, you should search for black magic recipes on the web. Few of
|
||||
us mortals are known to have invented a full solution on their own.
|
||||
|
||||
- Implement `apply`.
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
;; These are the answers to the questions in ../docs/exercise.md.
|
||||
|
||||
(load-file "../lib/folds.mal") ; foldr reduce
|
||||
(load-file "../lib/trivial.mal") ; dec identity
|
||||
;; In order to avoid unexpected circular dependencies among solutions,
|
||||
;; this files attempts to be self-contained.
|
||||
(def! identity (fn* [x] x))
|
||||
(def! reduce (fn* (f init xs)
|
||||
(if (empty? xs) init (reduce f (f init (first xs)) (rest xs)))))
|
||||
(def! foldr (fn* [f init xs]
|
||||
(if (empty? xs) init (f (first xs) (foldr f init (rest xs))))))
|
||||
|
||||
;; Reimplementations.
|
||||
|
||||
(def! nil? (fn* [x] (= x nil )))
|
||||
(def! true? (fn* [x] (= x true )))
|
||||
@ -10,7 +17,7 @@
|
||||
|
||||
(def! sequential?
|
||||
(fn* [x]
|
||||
(or (list? x) (vector? x))))
|
||||
(if (list? x) true (vector? x))))
|
||||
|
||||
(def! > (fn* [a b] (< b a) ))
|
||||
(def! <= (fn* [a b] (not (< b a))))
|
||||
@ -22,65 +29,62 @@
|
||||
(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 (or (empty? xs) (< index 0))
|
||||
;; (throw "nth: index out of range")
|
||||
;; (if (zero? index)
|
||||
;; (first xs)
|
||||
;; (nth (rest xs) (dec index))))))
|
||||
(fn* [xs]
|
||||
(if (nil? xs)
|
||||
0
|
||||
(reduce (fn* [acc _] (+ 1 acc)) 0 xs))))
|
||||
(def! nth
|
||||
(fn* [xs index]
|
||||
(if (if (<= 0 index) (not (empty? xs))) ; logical and
|
||||
(if (= 0 index)
|
||||
(first xs)
|
||||
(nth (rest xs) (- index 1)))
|
||||
(throw "nth: index out of range"))))
|
||||
(def! map
|
||||
(fn* [f xs]
|
||||
(let* [iter (fn* [x acc] (cons (f x) acc))]
|
||||
(foldr iter () xs))))
|
||||
(foldr (fn* [x acc] (cons (f x) acc)) () xs)))
|
||||
(def! concat
|
||||
(let* [concat2 (fn* [xs ys] (foldr cons ys xs))]
|
||||
(fn* [& xs] (foldr concat2 () xs))))
|
||||
(fn* [& xs]
|
||||
(foldr (fn* [xs ys] (foldr cons ys xs)) () 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)))))
|
||||
(reduce (fn* [xs x] (cons x xs)) xs ys))))
|
||||
|
||||
(def! do2 (fn* [& xs] (nth xs (dec (count xs)))))
|
||||
(def! do2 (fn* [& xs] (nth xs (- (count xs) 1))))
|
||||
(def! do3 (fn* [& xs] (reduce (fn* [acc x] x) nil xs)))
|
||||
;; do2 will probably be more efficient when lists are implemented as
|
||||
;; arrays with direct indexing, but when they are implemented as
|
||||
;; linked lists, do3 may win because it only does one traversal.
|
||||
|
||||
(defmacro! let2
|
||||
(defmacro! quote (fn* [ast] (list (fn* [] ast))))
|
||||
(def! _quasiquote_iter (fn* [x acc]
|
||||
(if (if (list? x) (= (first x) 'splice-unquote)) ; logical and
|
||||
(list 'concat (first (rest x)) acc)
|
||||
(list 'cons (list 'quasiquote x) acc))))
|
||||
(defmacro! quasiquote (fn* [ast]
|
||||
(if (list? ast)
|
||||
(if (= (first ast) 'unquote)
|
||||
(first (rest ast))
|
||||
(foldr _quasiquote_iter () ast))
|
||||
(if (vector? ast)
|
||||
;; TODO: once tests are fixed, replace 'list with 'vector.
|
||||
(list 'apply 'list (foldr _quasiquote_iter () ast))
|
||||
(list 'quote ast)))))
|
||||
|
||||
;; FIXME: mutual recursion.
|
||||
;; http://okmij.org/ftp/Computation/fixed-point-combinators.html
|
||||
(def! _c_combinator (fn* [x] (x x)))
|
||||
(def! _d_combinator (fn* [f] (fn* [x] (f (fn* [v] ((x x) v))))))
|
||||
(defmacro! let*
|
||||
(fn* [binds form]
|
||||
;; Each expression may refer to previous definitions, so a single
|
||||
;; function with many parameters would not have the same effect
|
||||
;; than a composition of functions with one parameter each.
|
||||
(if (empty? binds)
|
||||
form
|
||||
;; This let* increases the readability, but the values could
|
||||
;; easily be replaced below.
|
||||
(let* [key (first binds)
|
||||
val (nth binds 1)
|
||||
more (rest (rest binds))]
|
||||
`((fn* [~key] (let2 ~more ~form)) ~val)))))
|
||||
|
||||
(defmacro! quote2 (fn* [ast] (list (fn* [] ast))))
|
||||
(defmacro! quasiquote2
|
||||
(fn* [ast]
|
||||
(let* [
|
||||
is-pair (fn* [x] (if (sequential? x) (not (empty? x))))
|
||||
f (fn* [ast] ; evaluating its arguments
|
||||
(if (is-pair ast)
|
||||
(let* [a0 (first ast)]
|
||||
(if (= 'unquote a0)
|
||||
(nth ast 1)
|
||||
(if (if (is-pair a0) (= 'splice-unquote (first a0)))
|
||||
(list 'concat (nth a0 1) (f (rest ast)))
|
||||
(list 'cons (f a0) (f (rest ast))))))
|
||||
(list 'quote ast)))
|
||||
]
|
||||
(f ast))))
|
||||
(list (list 'fn* [(first binds)] (list 'let* (rest (rest binds)) form))
|
||||
(list '_c_combinator
|
||||
(list '_d_combinator
|
||||
(list 'fn* [(first binds)] (first (rest binds)))))))))
|
||||
|
||||
(def! apply
|
||||
;; Replace (f a b [c d]) with ('f 'a 'b 'c 'd) then evaluate the
|
||||
@ -94,6 +98,8 @@
|
||||
(cons (q x) acc)))]
|
||||
(fn* [& xs] (eval (foldr iter nil xs)))))
|
||||
|
||||
;; Folds
|
||||
|
||||
(def! sum (fn* [xs] (reduce + 0 xs)))
|
||||
(def! product (fn* [xs] (reduce * 1 xs)))
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
(reduce f (f init (first xs)) (rest xs)))))
|
||||
|
||||
;; Right fold (f x1 (f x2 (.. (f xn init)) ..))
|
||||
;; The natural implementation for `foldr` is not tail-recursive, so we
|
||||
;; The natural implementation for `foldr` is not tail-recursive, and
|
||||
;; the one based on `reduce` constructs many intermediate functions, so we
|
||||
;; rely on efficient `nth` and `count`.
|
||||
(def! foldr
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user