Хранение и обработка состояния всегда было краеугольным камнем в программировании. Существуют целые фреймворки по работе с состоянием (например, 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]
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1(ns about-state-test
2 (:require [test-helper :refer [assert-solution]]
3 [index :refer [transit]]))
4
5(assert-solution
6 [[(atom 100) (atom 50) 20] [(atom 10) (atom 100) 10] [(atom 50) (atom 30) 50]]
7 [[80 70] [0 110] [0 80]]
8 transit)
9
Решение учителя откроется через: