Бесплатный курс по 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
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе