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

Clojure: О состоянии

Хранение и обработка состояния всегда было краеугольным камнем в программировании. Существуют целые фреймворки по работе с состоянием (например, Redux во фронтенде), дополнительные механизмы в языках программирования (горутины, акторная модель в Elixir), даже в операционных системах есть такие инструменты, например, семафоры. Все эти инструменты позволяют организовать конкуретный доступ к ресурсу (его состоянию), для чтения и изменения каким-либо образом. В Clojure реализован механизм MVCC (multiversion concurrency control), который часто используется в базах данных, его основная идея заключается в предоставлении каждому пользователю так называемого «снимка» базы, обладающего тем свойством, что вносимые пользователем изменения невидимы другим пользователям до момента фиксации транзакции. Этот способ управления позволяет добиться того, что пишущие транзакции не блокируют читающих, и читающие транзакции не блокируют пишущих. Для создания транзакционной ссылки (refs) используется функция atom, для модификации ссылок используются функции swap! и reset!, для чтения значения, хранящегося по ссылке, используется deref или @, рассмотрим несколько примером:

(def resource (atom 10)) ; объявляем транзакционную ссылку
; попробуем прочесть значение, которое хранится в resource

(deref resource) ; => 10
@resource        ; => 10
; оба варианта подходят для чтения транзакционных ссылок
; теперь попробуем внести изменения

(swap! resource + 10) ; прибавляем 10
; => 20
@resource ; => 20

(swap! resource - 5) ; отнимем 5
; => 15
(deref resource) ; => 15
; теперь перезапишем значение, которое хранится по ссылке
(reset! resource -1) => -1
@resource ; => -1

(reset! resource 12) ; => 12
(deref resource)     ; => 12

Как видно из примеров, функция swap! требует атом, функцию модификатор (обычная функция, более сложные модификации рассмотрим чуть позже) и значение для функции модификатора. Для функции reset! достаточно атома и значения, на которое оно заменится. Удобство в том, что эти функции потокобезопасны и транзакционны, если произойдет ошибка, то транзакция откатится.

Задание

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

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

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