Почему можно передать пары ключевых значений в функцию, которая разрушает карту?
Я думал, что понял разрушение, но я читал блог clojure, и это меня смутило. Если у вас есть функция, написанная как:
(defn f [& {:keys [foo bar]}]
(println foo " " bar))
Почему вы можете так называть:
(f :foo 1 :bar 2)
Моя первая мысль заключалась в том, что моя функция должна была вызываться вот так:
(f {:foo 1 :bar 2})
IllegalArgumentException No value supplied for key: {:foo 1, :bar 2} clojure.lang.PersistentHashMap.createWithCheck (PersistentHashMap.java:89)
Но, очевидно, это не работает. Я думаю, что это как-то связано с тем, как работает &
. Но я всегда думал, что вещь после него - вектор, и поэтому вам придется разрушить что-либо после него, как вектор.
Может кто-нибудь объяснить мне, как/почему это определение работает так, как оно делает? Благодаря
Ответы
Ответ 1
Форма and и destructuring работают последовательно:
- и собирает любые аргументы после него в коллекцию
- Форма деструктурирования карты затем берет коллекцию, делает карту из нее, если требуется, и связывает имена с ключами, перечисленными в векторе.
Вектор в форме деструктурирования карты является просто синтаксисом, используемым для построения desctructuring/binding, и не подразумевает, что что-либо может быть представлено в форме ввода
Без и в defn вторая форма будет работать, а первая не будет.
С первой и первой формой будет работать, а вторая - нет.
Ответ 2
Вы можете видеть, что происходит под обложками, вызывая destructure
вручную. Начнем с более простого примера:
user> (destructure ['{foo :foo} {:foo 42}])
[map__26147 {:foo 42}
map__26147 (if (clojure.core/seq? map__26147)
(clojure.lang.PersistentHashMap/create
(clojure.core/seq map__26147))
map__26147)
foo (clojure.core/get map__26147 :foo)]
Это соответствует (let [{foo :foo} {:foo 42}] ...)
(как вы можете проверить с помощью (macroexpand-1 '(let [{foo :foo} {:foo 42}] ...))
. Вторая строка вывода - это важный бит. Форма привязки карты может работать двумя способами: если связанное значение является seq, seq будет "выливаться" в хэш-карту (как будто на (apply hash-map the-seq)
. В противном случае это значение считается ассоциативным и используется напрямую. Функция seq "заливки" была добавлена в этот коммит.
Протестируйте это:
user> (let [{foo :foo} {:foo 42}] foo)
42
user> (let [{foo :foo} (list :foo 42)] foo)
42
user> (let [{foo :foo} (apply hash-map (list :foo 42))] foo)
42
В первом случае значение не является seq, поэтому оно используется напрямую. Во втором случае список является seq, поэтому он "выливается" в хэш-карту, прежде чем привязан к {foo :foo}
. Третий случай показывает, что эта заливка семантически эквивалентна (apply hash-map the-seq)
.
Теперь посмотрим на что-то вроде вашего примера:
user> (destructure '[[& {:keys [foo bar]}] args])
[vec__26204 args
map__26205 (clojure.core/nthnext vec__26204 0)
map__26205 (if (clojure.core/seq? map__26205)
(clojure.lang.PersistentHashMap/create
(clojure.core/seq map__26205))
map__26205)
bar (clojure.core/get map__26205 :bar)
foo (clojure.core/get map__26205 :foo)]
Бит nthnext
от &
- в этом случае, поскольку перед &
нет фиксированных параметров, мы имеем (nthnext vec# 0)
, что сводится к преобразованию args
в seq ( если необходимо). Тогда у нас есть деструктуризация карты, как указано выше. Поскольку &
гарантирует, что у нас есть seq, специальный случай для деструктурирования карты всегда будет запущен, и аргументы всегда будут "вылиты" в хэш-карту, прежде чем привязаны к форме карты.
Если связь между этим примером и вашим оригиналом fn не ясна, рассмотрите:
user> (macroexpand-1 '(fn [& {:keys [foo bar]}]))
(fn* ([& p__26214] (clojure.core/let [{:keys [foo bar]} p__26214])))