This SICP lecture shows some of the power of Lisp by building a symbolic differentiator in a very small amount of code, and is also the first one to introduce the idea that code is data and data is code (watch from the beginning to 29:00 — at 13:00 he starts talking about using Lisp as the data format):
Here is the first symbolic differentiator he writes (but, in clojure)
(defn atom? [x]
(not (instance? clojure.lang.IPersistentCollection x)))
(defn const? [expr val] (and (atom? expr) (not= expr val)))
(defn same-var? [expr val] (and (atom? expr) (= expr val)))
(defn arith? [expr sym] (and (not (atom? expr)) (= (first expr) sym)))
(defn sum? [expr] (arith? expr ‘+))
(defn prod? [expr] (arith? expr ‘*))
(defn a1 [expr] (frest expr))
(defn a2 [expr] (first (rrest expr)))
(defn m1 [expr] (frest expr))
(defn m2 [expr] (first (rrest expr)))
(defn deriv [expr val] (
cond
(const? expr val) 0
(same-var? expr val) 1
(sum? expr)
(list ‘+ (deriv (a1 expr) val) (deriv (a2 expr) val))
(prod? expr)
(list ‘+ (list ‘* (m1 expr) (deriv (m2 expr) val))
(list ‘* (m2 expr) (deriv (m1 expr) val))
)
(:else) nil
)
)
The things to note:
- Quote syntax, which uses ‘
- first and rest which are car and cdr. Clojure also supports combinations like frest (cadr) and rrest (cddr)
- In clojure, extra parens are usually not necessary — look at how cond is implemented to use variable arguments rather than a single list argument (which would require extra parens)
- My first usage of Java interop (using instance? to find out if an expression is atomic or a collection)