Персистентные структуры данных в Clojure, работа с коллекциями.
Всё есть дерево. Даже списки, которые являются вырожденными деревьями.
Подробнее можно почитать тут: Understanding Clojure's Persistent Vectors, pt. 1
Суть: ПСД устроены так, что любое изменение порождает новую версию структуры, которая отличается от оригинала только теми частями, которые были затронуты изменениями.
Односвязные списки внутри. Об этом стоит помнить. Собираются из пар значение + ссылка на "хвост".
(cons 1 (cons 2 (cons 3 ())))
;; сахарок
(list 1 2 3)
Селекторы:
Деревья внутри, но об этом помнить не нужно. Предоставляют быстрый доступ по индексу.
(vector 1 2 3)
(vec '(1 2 3))
;; сахарок
[1 2 3]
Селекторы:
first/last и подобные использовать можно, но стоит помнить о цене.
Хранят соответствие уникальных ключей и неуникальных значений. Предоставляют быстрый доступ по ключу. Порядок добавления ключей не запоминается.
(hash-map :a 1 :b "foo")
;; сахарок
{:a 1 :b "foo"}
Селекторы:
first/last и подобные использовать можно, но стоит помнить о цене.
Помнят о том, что в них было добавлено, но не помнят, в каком порядке. Хранят уникальные значения, быстро проверяют вхождение элемента.
(set '(1 2 3))
;; сахарок
#{1 2 3}
Селекторы:
Соотношение множеств делается с помощью функций в модуле clojure.set
(get [1 2 3] 0) ;; => 1
(get [1 2 3] 5) ;; => nil
(get [1 2 3] 5 :oops) ;; => oops
(get {:a 42} :a) ;; => 42
(get {:a 42} :b) ;; => nil
(get {:a 42} :b :oops) ;; => :oops
(get #{1 2 3} 1) ;; => 1
(get #{1 2 3} 5) ;; => nil
(get #{1 2 3} 5 :oops) ;; => :oops
(conj '(1 2 3) 4) ;; => '(4 1 2 3)
(conj [1 2 3] 4) ;; => [1 2 3 4]
(conj #{1 2 3} 3) ;; => #{1 3 2}
(conj #{1 2 3} 4) ;; => #{1 4 3 2}
(conj {:a 1} [:a 2]) ;; => {:a 2}
(conj {:a 1} [:b 2]) ;; => {:a 1, :b 2}
(assoc [1 2 3] 0 :foo) ;; => :foo
(assoc [1 2 3] 5 :foo) ;; => Execution error
(assoc {:a 1} :a 2) ;; => {:a 2}
(assoc {:a 1} :b 2) ;; => {:a 1, :b 2}
(update [1 2 3] 0 inc) ;; => [2 2 3]
(update [1 2 3] 0 + 10) ;; => [11 2 3]
(update {:a 1} :a inc) ;; => {:a 2}
(dissoc {:a 1 :b 2} :a) ;; => {:b 2}
(def data {:cart [{:type :apple :amount 1}]})
(get-in data [:cart 0 :amount])
;; => 1
(update-in data [:cart 0 :amount] inc)
;; => {:cart [{:type :apple, :amount 2}]}
Подробнее можно почитать тут: Destructuring in Clojure
Суть: в let и в описании параметров функций можно указать желаемую структуру с именованием отдельных её частей. В последствии данные будут сопоставлены с желаемой структурой, и если совпадение будет полным, вы получите именованные части в виде локальных определений.
(def data {:cart [{:type :apple :amount 1}]})
(let [{[{t :type a :amount} & _] :cart} data]
[t a])
;; => [:apple 1]
(map inc '(1 2 3)) ;; => (2 3 4)
(map inc [1 2 3]) ;; => (2 3 4)
(mapv inc [1 2 3]) ;; => [2 3 4]
(map last {:a 1 :b 2}) ;; => (1 2)
(map + [1 2 3] [10 20 30 40]) ;; (11 22 33)
(filter odd? [1 2 3 4 5]) ;; => (1 3 5)
(reduce + [1 2 3 4 5]) ;; => 15
(reduce + 1000 [1 2 3 4 5]) ;; => 1015
(reduce conj [1 2 3] [4 5 6])
;; => [1 2 3 4 5 6]
(for [x [10 20 30]
y [1 2 3]
:let [sum (+ x y)]
:while (< sum 23)]
sum)
;; => (11 12 13 21 22)
(let [deltas [-1 0 1]]
(for [dx deltas
dy deltas
:let [pair [dx dy]]
:when (not= pair [0 0])]
pair))
;; => ([-1 -1] [-1 0] [-1 1] [0 -1] [0 1] [1 -1] [1 0] [1 1])
({:a 1 :b 2} :c) ;; => nil
({:a 1 :b 2} :a) ;; => 1
(["a" "b" "c"] 1) ;; => "b"
(#{1 2 3} 1) ;; => 1
(#{1 2 3} 4) ;; => nil
Ключевые слова работают как селекторы для отображений:
(:a {:a 1 :b 2}) ;; => 1
(:c {:a 1 :b 2}) ;; => nil