Clojure

Атомы

Атомы позволяют управлять общими, синхронными, независимыми состояниями. Они являются ссылочными типами как ссылки и переменные. Вы создаете атом с помощью atom и можете осуществлять доступ к его состоянию с помощью deref/@. Как ссылки и агенты, атомы поддерживают валидаторы. Чтобы изменить значение атома, вы можете использовать swap!. Также предоставляется низкоуровневая функция compare-and-set!. Изменения в атомах всегда защищены от состояния гонки.

Как и у всех ссылочных типов, предполагаемое использование атомов - это хранение одной из неизменяемых структур Clojure. Также вы можете изменять атом применяя к старому значению некоторую функцию. Это можно сделать с помощью swap!. Внутри себя swap! читает текущее значение, применяет функцию к нему и пытается выполнить compare-and-set!. Так как другой поток может изменить значение в тоже самое время, возможно придется повторить попытку, а возможно и не один раз. В результате значение всегда будет равно результату применения предоставленной функции к текущему значению, в каждый момент времени. Заметим, что так как функция может быть вызвана несколько раз, она не должна иметь побочных эффектов.

Атомы являются эффективным средством представления некоторого состояния, которое не координируется ни с каким другим состоянием и для которого вы хотите реализовать синхронные изменения (в отличие от агентов, которые также независимы, но асинхронны). Типичный пример использования атомов - мемоизация:

(defn memoize [f]
  (let [mem (atom {})]
    (fn [& args]
      (if-let [e (find @mem args)]
        (val e)
        (let [ret (apply f args)]
          (swap! mem assoc args ret)
          ret)))))

(defn fib [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2)))))

(time (fib 35))
user=> "Elapsed time: 941.445 msecs"

(def fib (memoize fib))

(time (fib 35))

user=> "Elapsed time: 0.044 msecs"