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]
Упражнение не проходит проверку — что делать? 😶
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя 🤔
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно 🙄
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.