Thursday, March 1, 2012

On Lisp in Clojure ch 2 (2.5 - 2.6)

This is my third post translating the examples from On Lisp by Paul Graham into Clojure.

Section 2.5 Scope

This section shows an example and describes how it would be evaluated with dynamic scope and lexical scope. Here is the example with Clojure syntax:

(let [y 7]
  (defn scope-test [x]
    (list x y)))

(let [y 5]
  (scope-test 3))

Like CLISP, Clojure uses lexical scope in this example, and like CLISP, there are ways to use dynamic scope. Amit Rathore covers scope in detail in Clojure in Action, but Graham doesn't cover in this part of his book, so I won't either.

Section 2.6 Closures

In the first example, the inner anonymous function gets the value of n from its enclosing function.

(defn list+ [lst n]
  (map (fn [x] (+ x n))
       lst))

(list+ '(1 2 3) 10)

Clojure's data structures are immutable by default, but the next example doesn't work without mutable state, so this version uses a Clojure atom.

;; lexically scoped counter
(let [counter (atom 0)]
  (defn new-id [] (swap! counter + 1))
  (defn reset-id [] (reset! counter 0 )))

(new-id)
(reset-id)

The next example defines a function that returns a function that adds a predetermined number.

(defn make-adder [n]
  (fn [x] (+ x n)))

(def add2 (make-adder 2))
(def add10 (make-adder 10))
(add2 5)
(add10 3)

The next example again creates an adder function, but an optional boolean parameter lets the caller change the amount that gets added with each call. Again we will use a Clojure atom for the mutable variable.

(defn make-adderb [n]
  (let [amt (atom n)]
    (fn [x & change ]
      (if change  (reset! amt x ))
      (+ x @amt))))

(def addx (make-adderb 1))

(addx 3)
(addx 100 true)
(addx 3)

The next example was out of order in the book. In the proper order, we create a make-dbms function that takes a collection of key value pairs and returns a list of functions that operate on that collection. In Clojure the logical data type to use is a map, rather than a list of lists. The coolness of this example, using a list of functions that operate on a collection provided by a closure work just fine with immutable state.

(defn make-dbms [db]
  (list
   (fn [key]
     (db key))
   (fn [key val]
     (assoc db key val))
   (fn [key]
     (dissoc db key))))

(def cities (make-dbms {'boston 'us, 'paris 'france}) )

cities is a list of functions that operate on the map of key value pairs. The comma in the collection is optional, I just think it makes the grouping more clear. Commas are whitespace in Clojure.

Here is how we call the functions:

((first cities) 'boston)
((second cities) 'london 'england)
((last cities) 'boston)

Even for a contrived example, we can't really get away with an immutable database. So here is the mutable version, again using atoms.

(defn make-mutable-dbms [db]
  (let [mdb (atom db)]
    (list
     (fn [key]
       (@mdb key))
     (fn [key val]
       (swap! mdb assoc key val))
     (fn [key]
       (swap! mdb dissoc key)))))

(def citiesx (make-mutable-dbms {'boston 'us, 'paris 'france}) )

((first citiesx) 'boston)
((second citiesx) 'london 'england)
((last citiesx) 'boston)

In a clear victory for hammock driven development, I realized that a map of functions made a lot more sense than a list of functions. This way, instead of calling first, second and last, we can call the functions by name, like so:

(defn make-dbms-map [db]
     (let [mdb (atom db)]
       {:select (fn [key] (@mdb key))
        :insert (fn [key val]
                  (swap! mdb assoc key val))
        :delete (fn [key]
                  (swap! mdb dissoc key))}))

(def citiesm (make-dbms-map {'boston 'us 'paris 'france}))
((:select citiesm) 'boston)
((:insert citiesm) 'london 'england)
((:delete citiesm) 'boston)

viva la clojure

Next time we will finish Chapter 2, covering local functions and tail recursion.

No comments:

Post a Comment