Почему можно передать пары ключевых значений в функцию, которая разрушает карту?

Я думал, что понял разрушение, но я читал блог 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])))