Разрушается карта ClojureScript со значениями по умолчанию? - PullRequest
2 голосов
/ 19 апреля 2020

Предлагает ли Clojure / Script способ построения деструктурированной карты из аргументов плюс заполненные значения по умолчанию в случае, если ключи не были указаны в вызове?

Рассмотрим этот пример (это не достаточно сделать то, что код предполагает быстрый взгляд). Обеспечивает ли clojure способ построения карты prompt с этими четырьмя ключами и значениями из вызывающего или по умолчанию. Ненавижу думать, что мне приходится повторять эти ключевые имена еще два раза, чтобы получить то, что я хочу.

Ответы [ 3 ]

2 голосов
/ 20 апреля 2020

это выполнимо, может быть, не очень практично, но хорошо для самообразования:

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

скажем, мы хотим для передачи векторов длины 2 или 3, где вектор 2 будет представлять пару ключ-значение простой карты привязки, например [:as abc] или [a :a], а вектор размером 3 будет равен kv-default triple: [a :a "my default"]. Пример использования:

(bindings-preproc [['a 1 "asd"]
                   ['b 2 "ddd"]
                   [:or {'x 10}]
                   [:as 'whole]])

, в результате чего

{a 1, b 2, :or {x 10, a "asd", b "ddd"}, :as whole}

эта функция может выглядеть следующим образом:

(defn bindings-preproc [decls]
  (let [defaults (into {} (keep (fn [decl]
                                  (when (and (not (keyword? (first decl)))
                                             (= 3 (count decl)))
                                    (let [[nm _ default] decl]
                                      [nm default])))
                                decls))
        all-kvs (apply assoc {} (mapcat (partial take 2) decls))]
    (update all-kvs :or merge defaults)))

(эта ошибка не включает проверяет ради иллюстративной простоты)

Следующим шагом является использование его внутри макросов привязки. Идея сделать bindings-preproc макросом не удалась, потому что формы привязки проверяются на достоверность до , когда оцениваются внутренние макросы.

Но все же у нас есть функция, которая поможет, а именно теги считывателя . Они используются, например, когда вы используете синтаксис #inst. Поскольку эти теги читателя обрабатываются во время чтения, прежде чем какие-либо макросы будут расширены, мы можем подключить наш препроцессор.

(здесь я буду использовать реальное обновление ссылок, чтобы продемонстрировать его из repl, но в реальных проектах Вы бы объявили эти теги в специальном файле)

user> (alter-var-root
       #'default-data-readers
       assoc 'my/reader #'user/bindings-preproc)

;;=> {uuid #'clojure.uuid/default-uuid-reader,
;;    inst #'clojure.instant/read-instant-date,
;;    my/reader #'user/bindings-preproc}

, поэтому теперь мы можем попытаться заставить его работать:

(defn f [#my/reader [[a :a 10]
                     [b :b 20]
                     [z :z]
                     [:keys [k1 k2 k3]]
                     [[c1 c2 & cs] :c]
                     [:or {z 101
                           k3 :wooo}]
                     [:as whole]]]
  {:a a :b b :c1 c1 :c2 c2 :cs cs :z z :k1 k1 :k2 k2 :k3 k3 :whole whole})

user> (f {:a 1000 :c [:one]})
;;=> {:cs nil,
;;    :c2 nil,
;;    :z 101,
;;    :c1 :one,
;;    :k3 :wooo,
;;    :b 20,
;;    :whole {:a 1000, :c [:one]},
;;    :k1 nil,
;;    :k2 nil,
;;    :a 1000}


user> (let [a 10
            b 20
            #my/reader [[x :x 1]
                        [y :y 2]
                        [z :z 100]] {:z 432}]
        [a b x y z])
;;=> [10 20 1 2 432]
2 голосов
/ 19 апреля 2020

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

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

(def my-map {:text "Some text"})
(let
  [{title :title
    :or {title "Confirm"}
    :as prompt} my-map]
  (str "I got " title " from " prompt))
;; => "I got Confirm from {:text \"Some text\"}"

(macroexpand '(let
                  [{title :title
                    :or {title "Confirm"}
                    :as prompt} my-map]
                (str "I got " title " from " prompt)))
;; => (let*
;;     [map__12555
;;      my-map
;;      map__12555
;;      (if
;;          (clojure.core/seq? map__12555)
;;        (clojure.lang.PersistentHashMap/create
;;         (clojure.core/seq map__12555))
;;        map__12555)
;;      prompt
;;      map__12555
;;      title
;;      (clojure.core/get map__12555 :title "Confirm")]
;;     (str "I got " title " from " prompt))

Итак, как вы можете видеть, после расширения макроса механизм :or, который позволяет указывать значение по умолчанию, полагается на clojure.core/get.

В В этом конкретном примере на title влияет форма (clojure.core/get map__12555 :title "Confirm"). Это способ избежать повторения переменной title, но стоит ли это того?

Вы также можете проверить исходный код макроса деструктуры , чтобы получить полную информацию об этом, но лично Мне было довольно трудно справиться с ^^ '.

1 голос
/ 19 апреля 2020

Мне нравится составлять карту со всеми значениями по умолчанию, а затем использовать into или подобное, чтобы объединить введенные пользователем значения с картой значений по умолчанию. Например:

(ns tst.demo.core
  (:use tupelo.core tupelo.test) )

(def stuff-default {:a 1 :b 2})

(defn apply-defaults
  [arg]
  (let [stuff (glue stuff-default arg)] ; or use `into`.  Last one wins, so put defaults first
    (with-map-vals stuff [a b]
      (newline)
      (spyx a)
      (spyx b))
    stuff))

(dotest
  (is= (apply-defaults {}) ; no inputs => all default values
    {:a 1, :b 2})
  (is= (apply-defaults {:a 100}) ; some inputs => partial defaults
    {:a 100, :b 2})
  (is= (apply-defaults {:a 100, :b 200}) ; all inputs => no defaults used
    {:a 100, :b 200}))

Здесь glue похоже на into, но с дополнительной проверкой ошибок. Мы также используем tupelo.core/with-map-vals для разрушения карты, с меньшим количеством повторений, чем для нативной деструктуризации Clojure (vals->map делает наоборот).

Вывод:

-------------------------------
   Clojure 1.10.1    Java 14
-------------------------------

a => 1
b => 2

a => 100
b => 2

a => 100
b => 200

Ran 2 tests containing 3 assertions.
0 failures, 0 errors.
...