Деструктуризация

Учимся вытаскивать значения из коллекций компактно — прямо в момент связывания.

Деструктуризация — синтаксис, позволяющий разобрать коллекцию на части и сразу присвоить им имена, не доставая элементы по одному.

Зачем это нужно

Часто из вектора или map нужно достать несколько значений. Без деструктуризации приходится писать (nth v 0), (nth v 1), (:имя m) и так далее. Деструктуризация делает это в одну строку и читается куда яснее.

Разбор вектора

Чтобы разобрать вектор, в позиции имени ставим вектор имён:

(let [[a b c] [10 20 30]]
  (println a b c))

Вывод:

10 20 30

Можно пропускать ненужное через _ и собрать «хвост» через &:

(let [[первый & остальные] [1 2 3 4]]
  (println первый остальные))

Вывод:

1 (2 3 4)

Разбор отображения (map)

Для map указываем, какие ключи достать. Удобнее всего :keys:

(def человек {:имя "Лена" :возраст 28})

(let [{:keys [имя возраст]} человек]
  (println имя возраст))

Вывод:

Лена 28

Дополнительные приёмы: :as сохраняет весь map целиком, :or задаёт значения по умолчанию для отсутствующих ключей:

(let [{:keys [имя город] :or {город "неизвестен"} :as всё}
      {:имя "Лена"}]
  (println имя город))

Вывод:

Лена неизвестен

Деструктуризация в параметрах функций

Самое полезное — разбирать аргумент прямо в сигнатуре функции:

(defn приветствие [{:keys [имя возраст]}]
  (str "Привет, " имя ", тебе " возраст))

(приветствие {:имя "Макс" :возраст 40})

Вывод:

"Привет, Макс, тебе 40"

Как работает под капотом

Деструктуризация — это синтаксический сахар. Компилятор раскрывает её в обычные вызовы nth, get и связывания через let. То есть {:keys [имя]} превращается во что-то вроде имя (get m :имя). Никакой магии в рантайме — всё разворачивается на этапе компиляции, поэтому деструктуризация ничего не стоит по скорости сверх обычного доступа.

Частые ошибки

  • Путать :keys и значения. В :keys перечисляют ключи без двоеточия: :keys [имя], а получают значения по ключу :имя.
  • Забывать :or для необязательных полей. Без него отсутствующий ключ даст nil.
  • Слишком глубокая деструктуризация. Разбор на пять уровней вглубь читается хуже, чем явные get-in; знайте меру.

Итоги

  • Деструктуризация разбирает коллекцию и сразу даёт имена частям.
  • Векторы разбираются позиционно ([a b], & для хвоста), map — по ключам (:keys).
  • :as сохраняет всю коллекцию, :or задаёт значения по умолчанию.
  • Особенно удобна в параметрах функций; это синтаксический сахар над get/nth.
Проверьте себя
1. Что напечатает (let [[a b] [1 2 3]] (println a b))?
A1 2 3
B1 2
C[1 2 3]
DОшибку: размеры не совпадают
2. Для чего служит :keys при деструктуризации map?
AЧтобы перечислить ключи и получить переменные с их значениями
BЧтобы отсортировать ключи
CЧтобы удалить ключи из map
DЧтобы превратить map в вектор
3. Что задаёт :or в деструктуризации map?
AЛогическое ИЛИ
BЗначения по умолчанию для отсутствующих ключей
CСписок запрещённых ключей
DПорядок сортировки