|
| 1 | +(ns otus-15.atom-watcher-validator |
| 2 | + (:gen-class)) |
| 3 | + |
| 4 | +;; |
| 5 | +;; Атомы |
| 6 | +;; |
| 7 | + |
| 8 | +(def oleg |
| 9 | + (atom {:name "Oleg" |
| 10 | + :age 25 |
| 11 | + :wears-glasses? false})) |
| 12 | +; => #'user/oleg |
| 13 | + |
| 14 | +@oleg |
| 15 | + |
| 16 | +(swap! oleg #(update % :age + 3)) |
| 17 | +; => {:age 28, :wears-glasses? false, :name "Oleg"} |
| 18 | + |
| 19 | +(swap! oleg update :age + 3) |
| 20 | +; => {:age 31, :wears-glasses? false, :name "Oleg"} |
| 21 | + |
| 22 | +@oleg |
| 23 | + |
| 24 | +(reset! oleg {:name "Nikita"}) |
| 25 | +; => {:name "Nikita"} |
| 26 | + |
| 27 | +(def resources-by-user |
| 28 | + (atom {1005 {:cpu 35 |
| 29 | + :store 63466734 |
| 30 | + :memory 10442856 |
| 31 | + :pids #{6266, 5426, 6542}}})) |
| 32 | + |
| 33 | +(swap! resources-by-user update-in [1005 :pids] conj 9999) |
| 34 | + |
| 35 | +@resources-by-user |
| 36 | +(deref resources-by-user) |
| 37 | + |
| 38 | +(comment |
| 39 | + ;; Альтернативные варианты записи: |
| 40 | + |
| 41 | + ;; 1) |
| 42 | + (fn [atom-value] |
| 43 | + (update-in atom-value [1005 :pids] conj 9999)) |
| 44 | + |
| 45 | + ;; 2) |
| 46 | + (fn [atom-value] |
| 47 | + (let [pids (get-in atom-value [1005 :pids]) |
| 48 | + new-pids (conj pids 9999) |
| 49 | + new-atom-avalue (assoc-in atom-value [1005 :pids] new-pids)] |
| 50 | + new-atom-avalue)) |
| 51 | + ) |
| 52 | + |
| 53 | +;; Семантика «сравнить и изменить» и повторы |
| 54 | + |
| 55 | +(def accumulator |
| 56 | + (atom 0)) |
| 57 | + |
| 58 | +(defn +slow |
| 59 | + [a b timeout] |
| 60 | + (println (format "current: %s, delta: %s, timeout: %s" a b timeout)) |
| 61 | + (Thread/sleep timeout) |
| 62 | + (+ a b)) |
| 63 | + |
| 64 | +(defn retry-example |
| 65 | + [] |
| 66 | + (future (swap! accumulator +slow 1 2000)) |
| 67 | + (future (swap! accumulator +slow 2 5000))) |
| 68 | + |
| 69 | +(retry-example) |
| 70 | + |
| 71 | +@accumulator |
| 72 | + |
| 73 | +(comment |
| 74 | + "current: 0, delta: 1, timeout: 2000" |
| 75 | + "current: 0, delta: 2, timeout: 5000" |
| 76 | + ;; -> #<Future@257a94bc: :pending> |
| 77 | + "current: 1, delta: 2, timeout: 5000" |
| 78 | + ) |
| 79 | + |
| 80 | +;; |
| 81 | +;; Применение атомов для сохранения результата функции |
| 82 | +;; |
| 83 | + |
| 84 | +;; Замыкание атома внутри анонимной функции. |
| 85 | +(defn memoize |
| 86 | + [f] |
| 87 | + (let [mem (atom {})] |
| 88 | + (fn [& args] |
| 89 | + (if-let [e (find @mem args)] |
| 90 | + (val e) |
| 91 | + (let [ret (apply f args)] |
| 92 | + (swap! mem assoc args ret) |
| 93 | + ret))))) |
| 94 | + |
| 95 | +(def +mem (memoize +slow)) |
| 96 | + |
| 97 | +(time (+mem 1 2 2000)) |
| 98 | +;; => Elapsed time: 2001.006041 msecs |
| 99 | +(time (+mem 1 2 2000)) |
| 100 | +;; => Elapsed time: 0.124338 msecs |
| 101 | + |
| 102 | +;; |
| 103 | +;; Функции-наблюдатели |
| 104 | +;; |
| 105 | + |
| 106 | +;; На момент вызова наблюдателя значение в ref'е уже может быть изменено. |
| 107 | +;; Необходимо опираться на аргументы old и new. |
| 108 | +(defn echo-watch |
| 109 | + [key ref old new] |
| 110 | + (println key ref old "=>" new)) |
| 111 | + |
| 112 | +(add-watch accumulator :echo echo-watch) |
| 113 | + |
| 114 | +;; В наблюдателя передаётся только конечный результат вызова reset!/swap!. |
| 115 | +;; Перезапуски будут проигноированы, т.к. их результат отбрасывается. |
| 116 | +(retry-example) |
| 117 | + |
| 118 | +;; Наблюдатель вызвается даже если в результате вызовов |
| 119 | +;; reset!/swap! значение в атоме не было изменено. |
| 120 | +(reset! accumulator @accumulator) |
| 121 | + |
| 122 | +(defn echo-watch* |
| 123 | + [key ref old new] |
| 124 | + (when (not= old new) |
| 125 | + (println key ref old "=>" new))) |
| 126 | + |
| 127 | +;; Заменяю наблюдателя, зарегистрированного под ключом :echo. |
| 128 | +(add-watch accumulator :echo echo-watch*) |
| 129 | + |
| 130 | +(reset! accumulator @accumulator) |
| 131 | + |
| 132 | +;; Удаляю наблюдателя с ключом :echo. |
| 133 | +(remove-watch accumulator :echo) |
| 134 | + |
| 135 | +(reset! accumulator (inc @accumulator)) |
| 136 | + |
| 137 | +;; Пример логирования при помощи наблюдателей. |
| 138 | + |
| 139 | +(defn log->file |
| 140 | + [filename _ref old new] |
| 141 | + (when (not= old new) |
| 142 | + (spit filename (str new \newline) :append true))) |
| 143 | + |
| 144 | +(def nikita |
| 145 | + (atom {:name "Nikita", :age 23})) |
| 146 | + |
| 147 | +;; Наблюдатель, принимающий аргумент через ключ. |
| 148 | +(add-watch nikita "program.log" log->file) |
| 149 | + |
| 150 | +(swap! nikita #(update % :age inc)) |
| 151 | +(swap! nikita update :age inc) |
| 152 | +(swap! nikita identity) |
| 153 | +(swap! nikita assoc :wears-glasses? true) |
| 154 | +(swap! nikita update :age inc) |
| 155 | + |
| 156 | +(def history |
| 157 | + (atom ())) |
| 158 | + |
| 159 | +(defn log->atom |
| 160 | + [dest-atom key ref old new] |
| 161 | + nil) |
| 162 | + |
| 163 | +;; Наблююатель - частично применённая функция. |
| 164 | +(add-watch nikita :record (partial log->atom history)) |
| 165 | + |
| 166 | +(swap! nikita update :age inc) |
| 167 | +(swap! nikita update :age inc) |
| 168 | + |
| 169 | +@history |
| 170 | + |
| 171 | +;; |
| 172 | +;; Функции-валидаторы |
| 173 | +;; |
| 174 | + |
| 175 | +(def acc |
| 176 | + (atom 0 :validator nat-int?)) |
| 177 | +;; => #'user/acc |
| 178 | + |
| 179 | +(swap! acc dec) |
| 180 | +;; => IllegalStateException |
| 181 | + |
| 182 | +;; Ссылка может иметь только один валидатор. |
| 183 | +;; Установка нового валидатора подменяет исходный. |
| 184 | +(set-validator! acc int?) |
| 185 | +;; => nil |
| 186 | + |
| 187 | +(swap! acc dec) |
| 188 | +;; => -1 |
| 189 | + |
| 190 | +(set-validator! acc (fn [x] |
| 191 | + (or (int? x) |
| 192 | + (throw (IllegalArgumentException.))))) |
| 193 | +;; => nil |
| 194 | + |
| 195 | +(reset! acc "string") |
| 196 | +;; => IllegalArgumentException |
| 197 | + |
| 198 | +(set-validator! acc nil) |
| 199 | +;; => nil |
| 200 | + |
| 201 | +(reset! acc "string") |
| 202 | +;; => "string" |
0 commit comments