tags:

views:

94

answers:

1

I have just start reading Let over lambda and I thought I would try and write a clojure version of the block-scanner in the closures chapter.

I have the following so far:

(defn block-scanner [trigger-string]
  (let [curr (ref trigger-string) trig trigger-string]
    (fn [data]
      (doseq [c data]
        (if (not (empty? @curr))
          (dosync(ref-set curr
           (if (= (first @curr) c)
             (rest @curr)
             trig)))))
      (empty? @curr))))
(def sc (block-scanner "jihad"))

This works I think, but I would like know what I did right and what I could do better.

+4  A: 

I would not use ref-set but alter because you don't reset the state to a completely new value, but update it to a new value which is obtained from the old one.

(defn block-scanner
  [trigger-string]
  (let [curr (ref trigger-string)
        trig trigger-string]
    (fn [data]
      (doseq [c data]
        (when (seq @curr)
          (dosync
            (alter curr
                   #(if (-> % first (= c))
                      (rest %)
                      trig)))))
      (empty? @curr))))

Then it is not necessary to use refs since you don't have to coordinate changes. Here an atom is a better fit, since it can be changed without all the STM ceremony.

(defn block-scanner
  [trigger-string]
  (let [curr (atom trigger-string)
        trig trigger-string]
    (fn [data]
      (doseq [c data]
        (when (seq @curr)
          (swap! curr
                 #(if (-> % first (= c))
                    (rest %)
                    trig))))
      (empty? @curr))))

Next I would get rid of the imperative style.

  • it does more than it should: it traverses all data - even if we found a match already. We should stop early.
  • it is not thread-safe, since we access the atom multiple times - it might change in between. So we must touch the atom only once. (Although this is probably not interesting in this case, but it's good to make it a habit.)
  • it's ugly. We can do all the work functionally and just save the state, when we come to a result.
(defn block-scanner
  [trigger-string]
  (let [state    (atom trigger-string)
        advance  (fn [trigger d]
                   (when trigger
                     (condp = d
                       (first trigger)        (next trigger)
                       ; This is maybe a bug in the book. The book code
                       ; matches "foojihad", but not "jijihad".
                       (first trigger-string) (next trigger-string)
                       trigger-string)))
        update   (fn [trigger data]
                   (if-let [data (seq data)]
                     (when-let [trigger (advance trigger (first data))]
                       (recur trigger (rest data)))
                     trigger))]
    (fn [data]
      (nil? (swap! state update data)))))
kotarak
Wow, good stuff now I have to digest this and understand it. Thanks.
Lewis Jubb