Вопрос о новичке, но я действительно не понимаю, почему существует столько операций для построения карт в clojure.
Вы имеете conj
, assoc
и merge
, но они, кажется, более или менее делают то же самое?
(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})
Каково действительно различие и почему все эти методы требуются, когда они делают более или менее то же самое?
assoc
и conc
ведут себя по-разному для других структур данных:
user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]
Если вы пишете функцию, которая может обрабатывать несколько типов коллекций, тогда ваш выбор будет иметь большое значение.
Рассматривать слияние
как функцию только для карт (аналогично конъюнкту
для других коллекций).
Мое мнение:
На самом деле эти функции ведут себя совершенно по-разному при использовании с картами.
conj
:
Во-первых, пример (conj {:a 1 :b 2} :c 3)
из текста вопроса вообще не работает (ни с 1.1, ни с 1.2; IllegalArgumentException
выбрасывается). Существует всего несколько типов, которые можно соединить
с картами, а именно двухэлементные векторы, clojure.lang.MapEntry
ы (которые в основном эквивалентны двухэлементным векторам) и карты.
Обратите внимание, что seq
карты состоит из множества MapEntry
s. Таким образом, вы можете сделать, например,
(into a-map (filter a-predicate another-map))
(обратите внимание, что into
использует conj
-- или conj!
, когда это возможно -- внутренне). Ни merge
, ни assoc
не позволяют этого сделать.
merge
:
Это почти точно эквивалентно conj
, но заменяет аргументы nil
на {}
-- пустые хэш-карты -- и, таким образом, возвращает карту, когда первая "карта" в цепочке оказывается nil
.
(apply conj [nil {:a 1} {:b 2}])
; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList
(apply merge [nil {:a 1} {:b 2}])
; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
Обратите внимание, что ничто (кроме docstring...) не мешает программисту использовать merge
с другими типами коллекций. Если это сделать, возникнут странности; не рекомендуется.
assoc
:
Опять же, пример из текста вопроса - (assoc {:a 1 :b 2} {:c 3})
- не сработает; вместо этого будет выброшен IllegalArgumentException
. assoc
принимает аргумент map, за которым следует четное количество аргументов - те, которые находятся в нечетных позициях (допустим, map находится в позиции 0), являются ключами, те, которые находятся в четных позициях, являются значениями. Я нахожу, что assoc
на карты чаще, чем conj
, хотя когда я conj
, assoc
будет казаться громоздким. ;-)
merge-with
:
Для полноты картины, это последняя базовая функция, работающая с картами. Я нахожу ее чрезвычайно полезной. Она работает так, как указано в docstring; вот пример:
(merge-with + {:a 1} {:a 3} {:a 5})
; => {:a 9}
Обратите внимание, что если карта содержит "новый" ключ, который не встречался ни в одной из карт слева от нее, функция слияния не будет вызвана. Это иногда расстраивает, но в 1.2 умный reify
может предоставить карту с не nil
"значениями по умолчанию".
Поскольку карты являются такой вездесущей структурой данных в Clojure, имеет смысл иметь несколько инструментов для работы с ними. Все различные функции синтаксически удобны в немного разных обстоятельствах.
Мой личный взгляд на конкретные функции, которые вы упомянули: