Бесплатный курс по clojure. Зарегистрируйтесь для отслеживания прогресса →

Clojure: Агенты

Теперь поговорим про агентов. Что их отличает от атомов? Дело в том, что любое изменение состояния атома является синхронным, в то время как состояние агента меняется асинхронно. В остальном агенты похожи на атомы, в них можно добавлять валидации, настраивать обработку ошибок.

Рассмотрим несколько примеров:

(def my-agent (agent 0))

@my-agent ; => 0

(send my-agent inc)
#object[clojure.lang.Agent 0x59c25f30 {:status :ready, :val 1}]

@my-agent ; => 1

(send my-agent dec)
#object[clojure.lang.Agent 0x59c25f30 {:status :ready, :val 1}]

@my-agent ; => 0

Как видно из в последнем примере, при попытке уменьшить значение агента на единицу, вернулось состояние агента с неизмененным значением, затем мы извлекли состояние агента вручную, однако в нем уже хранился 0, почему? Как упоминалось выше, изменения к состоянию агента применяются асинхронно, важно помнить эту особенность, при работе с ними. Если же нужно получить значение агента после обновления, то можно использовать функции await и await-for.

Рассмотрим еще пару примеров с обработкой ошибок в агентах:

(def broken-agent (agent 0))

(send broken-agent (fn  [_] (throw      
  (Exception. "Houston we have a problem!"))))
#object[clojure.lang.Agent 0xd76bd8c {:status :ready, :val 0}]

(send broken-agent inc)
Execution error at user/eval2175$fn (REPL:1).
Houston we have a problem!

; Теперь настроим агента так, чтобы при возникновении ошибок процесс изменения не прерывался

(def not-broken-agent (agent 0 :error-mode :continue))

(send not-broken-agent (fn  [_] (throw      
  (Exception. "Houston we have one more problem!!!"))))
#object[clojure.lang.Agent 0x44b27f64 {:status :ready, :val 0}]

(send not-broken-agent inc)
#object[clojure.lang.Agent 0x44b27f64 {:status :ready, :val 0}]

; ошибки не возникло!
@not-broken-agent ; => 1

Задание

Реализуйте функцию transit, которая ведет себя так же, как в упражении с атомами, только с помощью агентов. Функция принимает два агента, которые представляют счета в банках и число денег, которое нужно перевести с первого на второй аккаунт, в результате выполнения функции, верните счета в виде вектора (помните, изменения в агентах применяются асинхронно!).

(transit (agent 100) (agent 20) 20)
; => [80 40]
(transit (agent 50) (agent 30) 50)
; => [0 80]

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе