Статьи

Clojure: разрушение

В «Радости Clojure» (TJoC) деструктуризация описывается как мини-язык в Clojure. Не обязательно изучать этот мини-язык; однако, как отмечают авторы TJoC, деструктуризация упрощает сжатый и элегантный код.

Что такое деструкция?


Clojure поддерживает абстрактную структурную привязку, часто называемую деструктуризацией, в списках привязки let, списках параметров fn и любом макросе, который расширяется до let или fn.

http://clojure.org/special_forms

Простейшим примером деструктуризации является присвоение значений вектора.

user=> (def point [5 7])
#'user/point

user=> (let [[x y] point]
         (println "x:" x "y:" y))
x: 5 y: 7

примечание: я использую let для моих примеров деструктуризации; однако на практике я склонен использовать деструктуризацию в списках параметров функции по крайней мере так же часто, если не чаще.

Я признаю, что не помню, чтобы когда-либо использовался деструктурирование, как в первом примере, но это хорошая отправная точка. Более реалистичным примером является разбиение вектора на голову и хвост. При определении функции с помощью arglist ** вы используете амперсанд. То же самое относится и к разрушению.

user=> (def indexes [1 2 3])
#'user/indexes

user=> (let [[x & more] indexes]
         (println "x:" x "more:" more))
x: 1 more: (2 3)

Также стоит отметить, что вы можете привязать весь вектор к локальному с помощью директивы: as.

user=> (def indexes [1 2 3])
#'user/indexes

user=> (let [[x & more :as full-list] indexes]
(println "x:" x "more:" more "full list:" full-list))
x: 1 more: (2 3) full list: [1 2 3]

Векторные примеры являются самыми простыми; однако на практике я гораздо чаще использую деструктуризацию с картами.

Простая деструктуризация на карте так же проста, как выбор локального имени и предоставление ключа.

user=> (def point {:x 5 :y 7})
#'user/point

user=> (let [{the-x 😡 the-y :y} point]
         (println "x:" the-x "y:" the-y))
x: 5 y: 7

Как показывает пример, значения: x и: y привязаны к местным жителям с именами the-x и the-y. На практике мы бы никогда не добавили «-» к нашим местным именам; однако, использование разных имен обеспечивает немного ясности для нашего первого примера. В рабочем коде вы, скорее всего, захотите, чтобы местные жители с тем же именем, что и ключ. Это прекрасно работает, как показывает следующий пример.

user=> (def point {:x 5 :y 7})
#'user/point

user=> (let [{x 😡 y :y} point]
         (println "x:" x "y:" y))
x: 5 y: 7

Хотя это прекрасно работает, создание локальных пользователей с тем же именем, что и у ключей, становится утомительным и раздражающим (особенно если ваши ключи длиннее одной буквы). Clojure предвидит это разочарование и предоставляет директиву: keys, которая позволяет вам указать ключи, которые вы хотели бы использовать в качестве локальных пользователей с тем же именем.

user=> (def point {:x 5 :y 7})
#'user/point

user=> (let [{:keys [x y]} point]
         (println "x:" x "y:" y))
x: 5 y: 7

Есть несколько директив, которые работают при деструктурировании карт. В приведенном выше примере показано использование ключей:. На практике я использую: ключи чаще всего; однако я также использовал директиву: as при работе с картами.

В следующем примере показано использование директивы: as для привязки локального объекта ко всей карте.

user=> (def point {:x 5 :y 7})
#'user/point

user=> (let [{:keys [x y] :as the-point} point]
         (println "x:" x "y:" y "point:" the-point))
x: 5 y: 7 point: {:x 5, :y 7}

Теперь мы видели директиву: as, используемую как для векторов, так и для карт. В обоих случаях локальное всегда присваивается всему выражению, которое подвергается деструктуризации.

Для полноты я задокументирую директиву: or; Однако я должен признать, что никогда не использовал это на практике. Директива: or используется для назначения значений по умолчанию, когда карта с деструктуризацией не содержит указанный ключ.

user=> (def point {:y 7})
#'user/point
 
user=> (let [{:keys [x y] :or {x 0 y 0}} point]
         (println "x:" x "y:" y))
x: 0 y: 7

Наконец, также стоит отметить, что вы можете деструктурировать вложенные карты, векторы и их комбинацию.

Следующий пример разрушает вложенную карту

user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book

user=> (let [{name :name {pages :pages isbn-10 :isbn-10} :details} book]
         (println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530

Как и следовало ожидать, вы также можете использовать директивы при разрушении вложенных карт.

user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book
user=> 
user=> (let [{name :name {:keys [pages isbn-10]} :details} book]
         (println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530

Разрушение вложенных векторов также очень просто, как показано в следующем примере.

user=> (def numbers [[1 2][3 4]])
#'user/numbers

user=> (let [[[a b][c d]] numbers]
  (println "a:" a "b:" b "c:" c "d:" d))
a: 1 b: 2 c: 3 d: 4


Поскольку формы связывания могут быть произвольно вложены друг в друга, вы можете разделить все что угодно —
http://clojure.org/special_forms

Следующий пример уничтожает карту и вектор одновременно.

user=> (def golfer {:name "Jim" :scores [3 5 4 5]})
#'user/golfer

user=> (let [{name :name [hole1 hole2] :scores} golfer] 
         (println "name:" name "hole1:" hole1 "hole2:" hole2))
name: Jim hole1: 3 hole2: 5

Тот же пример может быть переписан с использованием определения функции, чтобы показать простоту использования деструктуризации в списках параметров.

user=> (defn print-status [{name :name [hole1 hole2] :scores}] 
  (println "name:" name "hole1:" hole1 "hole2:" hole2))
#'user/print-status

user=> (print-status {:name "Jim" :scores [3 5 4 5]})
name: Jim hole1: 3 hole2: 5

Другие (менее используемые) директивы и более глубокие объяснения доступны на
http://clojure.org/special_forms и в
«Радости Clojure» . Я рекомендую оба.

** (определите что-то сделать [xy & more] …)

 

С http://blog.jayfields.com/2010/07/clojure-destructuring.html