Я бы не использовал 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)))))