Пусть над лямбда-блок-сканером в ближайшем будущем - PullRequest
3 голосов
/ 22 июля 2010

Я только начал читать Let over lambda, и я подумал, что попытаюсь написать версию clojure блочного сканера в главе о закрытиях.

Пока у меня есть следующее:

(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"))

Я думаю, это работает, но я хотел бы знать, что я сделал правильно и что я мог бы сделать лучше.

1 Ответ

8 голосов
/ 22 июля 2010

Я бы не использовал ref-set, но alter, потому что вы не сбрасываете состояние до совершенно нового значения, а обновляете его до нового значения, полученного из старого.

(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))))

Тогда нет необходимости использовать ссылки, так как вам не нужно координировать изменения. Здесь атом лучше подходит, поскольку его можно изменить без всякой церемонии STM.

(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))))

Далее я бы избавился от императивного стиля.

  • он делает больше, чем должен: он пересекает все данные - даже если мы уже нашли совпадение. Мы должны остановиться рано.
  • это не потокобезопасно, так как мы обращаемся к атому несколько раз - между ними может произойти изменение. Поэтому мы должны касаться атома только один раз. (Хотя это, вероятно, не интересно в этом случае, но хорошо, чтобы это стало привычкой.)
  • это безобразно. Мы можем выполнить всю работу функционально и просто сохранить состояние, когда придем к результату.
(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)))))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...