Monday, June 25, 2012

On Lisp in Clojure chapter 11 (11.3 - 11.6)

I am continuing to translate the examples from On Lisp by Paul Graham into Clojure. The examples and links to the rest of the series can be found on github.

Section 11.3 Conditional Evaluation

Graham makes such a great point in this section that I wanted to start with it: "When faced with a choice between a clean idiom and an efficient one, we go between the horns of the dilemma by transforming the former into the latter."

I decided to write Graham's if3 macro using true, false and nil for 3 value truthiness. Normally in an if statement nil and false are evaluated the same. The nif macro, is very similar. In the Clojure form, the test is evaluated in a let binding, so that it only has to be evaluated once.

(defmacro if3 [test t-case nil-case f-case]
  `(let [expr# ~test]
     (cond
      (nil? expr#) ~nil-case
      (false? expr#) ~f-case
      :default ~t-case)))

(defmacro nif  [expr pos zero neg]  `(let [expr# ~expr]
    (cond
     (pos? expr#) ~pos
     (zero? expr#) ~zero
     :default ~neg)))

Graham presents us with an `in` macro which tests for membership using a series of tests of equality joined in an or expression.

(defmacro in? [needle & haystack]  ( let [target# needle]
    `(or ~@(map (fn [x#] `(= ~x# ~target#)) haystack))))

(macroexpand-1
 '(in? 1 1 2 3))

;;(clojure.core/or (clojure.core/= 1 1) (clojure.core/= 2 1) (clojure.core/= 3 1))


;; Just to make sure it is working the way we hope
(in? 1 1 (do (println "called") 2) 3)

The Clojure function `some` uses `or` recursively to find the first match.

Clojure's lazy sequences provide another way to get the same functionality. The member? function below returns the first match in the sequence. The argument list is different in this implementation because i wanted the caller to pass a collection, rather than several elements that I wrap in to a collection, because this allows me to work with an infinite collection, such as all positive integers.

;; lazy function
(defn member? [needle haystack]
  (take 1 (filter (partial = needle) haystack)))

(member? 2 (iterate inc 1) )

in-f is almost the same as in?, except that it allows us to pass the function to use for the comparison.

(defmacro in-if [func needle & haystack]
  (let [target# needle]
    `(or ~@(map (fn [x#] `(~func ~x# ~target#)) haystack))))

Graham creates a >case macro that applies a case statement with expressions as keys instead of constants. Each key will be evaluated until a match is found. Once a match is found, no more keys will be evaluated. Clojure's cond statement already behaves like that.

(cond
 (do (println "First cond") false) (println "one")
 (do (println "Second cond") true) (println "two")
 (do (println "Third cond") true) (println "three")
 :default (println "Nothing matched"))

Section 11.4 iteration

Clojure's partition function makes breaking the source parameter into chunks pretty easy. Of course, partition makes the macro easier, it also makes for a short inline invocation. Here is do-tuples-o, followed by an example call to the macro, and an example of writing the map expression directly.

(defmacro do-tuples-o [parms source & body]
  (let [src# source]
    `(map (fn [~parms] ~@body)
          (partition (count (quote ~parms)) 1 ~src#))))

(do-tuples-o [x y] [1 2 3 4 5] (+ x y))

(map
     (fn [[x y]] (+ x y))
     (partition 2 1 [1 2 3 4 5]))

If we use partition in conjunction with cycle, we can create our parameter list that wraps around. The only change we have to make for do-tuples-c is to change partition to partition-round. I also changed my sample call, to show it can work with a function of any arity.

(defn partition-round [size step source]
  (partition size step
             (take (- (+ size (count source)) step)
                   (cycle source))))
(defmacro do-tuples-c [parms source & body]
  (let [src# source]
    `(map (fn [~parms] ~@body)
          (partition-round (count ( quote ~parms)) 1 ~src#))))

(do-tuples-c [x y z] [1 2 3 4 5] (+ x y z))

(map
     (fn [[x y z]] (+ x y z))
     (partition-round 3 1 [1 2 3 4 5]))

Section 11.5 Iteration with Multiple Values

In this section, Graham shows us a macro that executes a do loop that increments several variables in parallel and allows for multiple return values. He then shows us an example of a game that might use this sort of construct to track moving objects.

Multiple return values in a list based language really seems like a non-issue to me.

The sample game that Graham describes looks interesting. I hope to do a Clojure implementation of it soon, and then I will have a better context to evaluate the need for a multi-varibale do.

Section 11.6 Need for Macros

In earlier sections, Graham described using macros for conditional evaluation and iteration. Here he shows us how the some of the same things can be done with functions.

(defn fnif [test then else]
  (if test (then) (else)))

(fnif true (fn [] (do (println "true") (+ 1 2))) (fn []  (do (println "false")  (- 2 1))))

(defn forever [fn]
  (if true
    (do
      (fn)
      (recur fn))
    'done))

#_(forever (fn [] (println "this is dumb")))

We have to wrap the code we want executed within an anonymous function which we invoke when we want the code evaluated. Graham points out that in these situations, the macro solution is much cleaner, if not strictly necessary. He also says simple binding situations can be handled with map, but that more complicated situations are only possible with macros.

No comments:

Post a Comment