Деструктурування

Деструктурування — це процес вичитування окремих частин з структурованих даних, наприклад колекцій. В ClojureScript є спеціальний синтаксис для деструктурування індексованих послідовностей та асоціативних структур даних, що використовується у локальних зв'язуваннях та в аргументах функцій.

Давайте розглянемо приклад, щоб зрозуміти в яких випадках деструктурування може бути корисним. Звичайно, якщо вам треба прочитати лише перший та третій елемент з колекції, це можна зробити за допомогою функції nth:

(let [v [0 1 2]
      fst (nth v 0)
      thrd (nth v 2)]
  [thrd fst])
;; => [2 0]

Недоліком такого підходу є його надмірна багатослівність. За допомогою деструктурування це можна зробити у місці створення локальних змінних, використовуючи вектор:

(let [[fst _ thrd] [0 1 2]]
  [thrd fst])
;; => [2 0]

У цьому прикладі [fst _ thrd] — форма деструктурування. Вона представлена у вигляді вектору, що використовується для зв'язування значень в колекції з символами fst і thrd відповідно до індексів 0 та 2 цих значень у колекції. Символ _ використовується як заглушка для значень, які нам не потрібні, у цьому випадку це 1.

Зверніть увагу, що деструктурування не обмежене зв'язуванням у формі let; воно працює майже усюди, де можливо зв'язати значення з символом, наприклад, у формах for та doseq, а також у аргументах функції. За допомогою деструктурування ми можемо створити функцію, що приймає пару значень та міняє їх місцями:

(defn swap-pair [[fst snd]]
  [snd fst])

(swap-pair [1 2])
;; => [2 1]

(swap-pair '(3 4))
;; => [4 3]

Деструктурування за позицією в колекції з допомогою вектора добре підходить для вичитування значень з індексованих послідовностей, але інколи ми не хочемо відкидати інші аргументи послідовності. Символ & використовується в деструктуруванні для групування залишкових аргументів в окрему послідовність так само, як і в аргументах варіаційних функцій:

(let [[fst snd & more] (range 10)]
  {:first fst
   :snd snd
   :rest more})
;; => {:first 0, :snd 1, :rest (2 3 4 5 6 7 8 9)}

У цьому прикладі перше значення в колекції, з індексом 0, зв'язане з символом fst, друге, з індексом 1, — з snd, а усі інші, починаючи з індексу 2 і до кінця, — з символом more.

Іноді разом з деструктуруванням нам також може бути потрібна уся структура даних. Це можна зробити за допомогою ключового слова :as, яке зв'язує початкове значення з символом, що записаний після :as у формі деструктурування:

(let [[fst snd & more :as original] (range 10)]
  {:first fst
   :snd snd
   :rest more
   :original original})
;; => {:first 0, :snd 1, :rest (2 3 4 5 6 7 8 9), :original (0 1 2 3 4 5 6 7 8 9)}

Асоціативні колекції також можна деструктурувати. На відміну від індексованих колекцій, де використовується вектор, така форма деструктурування представлена у вигляді мапи. В цій мапі ключі — це символи, з якими будуть зв'язані значення з оригінальної мапи, а значення — це ключі з оригінальної мапи, значення яких ми хочемо зв'язати з символами у формі деструктурування. Давайте розглянемо наступний приклад:

(let [{language :language} {:language "ClojureScript"}]
  language)
;; => "ClojureScript"

У цьому прикладі ми дістаємо значення по ключу :language і зв'язуємо його з символом language. Коли у мапі немає значення відповідного до ключа, значенням символу буде nil:

(let [{name :name} {:language "ClojureScript"}]
  name)
;; => nil

В асоціативному деструктуруванні можливо задавати значення за замовчуванням для символів, що не мають значення в мапі по зазначеному ключу. Для цього використовується ключове слово :or та мапа зі зв'язуваннями по замовчуванню після нього:

(let [{name :name :or {name "Anonymous"}} {:language "ClojureScript"}]
  name)
;; => "Anonymous"

(let [{name :name :or {name "Anonymous"}} {:name "Cirilla"}]
  name)
;; => "Cirilla"

Асоціативне деструктурування також підтримує зв'язування символу з оригінальним значенням за допомогою ключового слова :as у формі деструктурування:

(let [{name :name :as person} {:name "Cirilla" :age 49}]
  [name person])
;; => ["Cirilla" {:name "Cirilla" :age 49}]

В асоціативних структурах даних ключами можуть бути не лише ключові слова. Числа, рядки, символи та інші структури даних теж можуть бути ключами, тому ми також можемо використовувати їх у деструктуруванні. Якщо ключ — це символ, його треба взяти у лапки, щоб представити як структуру даних, а не змінну, у якої є деяке значення:

(let [{one 1} {0 "zero" 1 "one"}]
  one)
;; => "one"

(let [{name "name"} {"name" "Cirilla"}]
  name)
;; => "Cirilla"

(let [{lang 'language} {'language "ClojureScript"}]
  lang)
;; => "ClojureScript"

Зазвичай значення у деструктуруванні мапи зв'язують з символами з таким же ім'ям, як і ключі у цій мапі (наприклад, значення по ключу :language до символу language). Оскільки ключами звичайно бувають ключові слова, рядки або символи, в ClojureScript для цих випадків є скорочений синтаксис деструктурування.

Ми покажемо приклади для всіх типів ключів, починаючи з ключового слова, з використанням :keys:

(let [{:keys [name surname]} {:name "Cirilla" :surname "Fiona"}]
  [name surname])
;; => ["Cirilla" "Fiona"]

Як ви можете бачити, ключове слово :keys та вектор символів утворюють локальні змінні зі значеннями відповідно до ключів у мапі. Символи у векторі повинні мати таке ж ім'я, як і відповідні ключі. Такий запис є скороченим еквівалентом {name :name surname :surname}.

Скорочена форма для рядків та символів працює точно так, як і :keys, але з використанням :strs та :syms відповідно:

(let [{:strs [name surname]} {"name" "Cirilla" "surname" "Fiona"}]
  [name surname])
;; => ["Cirilla" "Fiona"]

(let [{:syms [name surname]} {'name "Cirilla" 'surname "Fiona"}]
  [name surname])
;; => ["Cirilla" "Fiona"]

Цікавою особливістю деструктурування є те, що форми можна вкладувати для вичитування вкладених значень, при цьому це легко читається, оскільки форма деструктурування описує структуру самої колекції:

(let [{[fst snd] :languages} {:languages ["ClojureScript" "Clojure"]}]
  [snd fst])
;; => ["Clojure" "ClojureScript"]

results matching ""

    No results matching ""