Лучший способ сделать init класс Java-конструктора из карты clojure? - PullRequest
0 голосов
/ 05 октября 2018

все

Я пытаюсь написать функцию для переноса CsvReadOptions.Builder.html в clojure.

Функция примет карту следующим образом: {: header true: locale "US"}, функция настроит компоновщик в соответствии с картой.

(defn reader-options [ opts ]
  (let [ b (CsvReadOptions$Builder.)]
    (cond-> b
      (contains? opts :locale ) (.locale (:locale opts))
      (contains? opts :header ) (.header (:header opts))
        true (.build ))
  )
)

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

      (contains? opts :locale ) (.locale (:locale opts))    

Еще раз спасибо за любые предложения.

Ответы [ 3 ]

0 голосов
/ 05 октября 2018

В этой задаче есть несколько факторов:

  1. Необязательные параметры
  2. Изменяемые объекты
  3. Java-взаимодействие

Этопричина, по которой вы получаете locale и header, реплицированные 3 раза на каждую строку.Я не могу придумать простой способ уменьшить это дублирование.Если бы это был общий шаблон в вашем приложении, вы могли бы написать макрос (расширение компилятора), чтобы упростить его.Если это не очень частое явление в вашем коде, стоимость (сложность, документация, недоразумения и т. Д.) Значительно превысит выгоду.

Вроде как покупка новой машины вместо чистки старой машины.,За исключением чрезвычайных обстоятельств, стоимость, вероятно, не стоит выгоды.

;)

0 голосов
/ 05 октября 2018

Теоретически вы можете написать макрос, который расширяется до необходимого вам кода:

(defmacro defbuilder [fn-name builder-class fields]
  (let [opts (gensym)]
    `(defn ~fn-name [~opts]
       (cond-> (new ~builder-class)
         ~@(mapcat
            (fn [field-sym]
              (let [field-kw (keyword (name field-sym))]
                `((contains? ~opts ~field-kw)
                  (. ~field-sym (get ~opts ~field-kw)))))
            fields)
         true (.build)))))

Теперь

(defbuilder options-from-map CsvReadOptions$Builder
  [header locale...])

сгенерирует:

(clojure.core/defn options-from-map [G__12809]
  (clojure.core/cond-> (new CsvReadOptions$Builder)
    (clojure.core/contains? G__12809 :header)
    (. header (clojure.core/get G__12809 :header))
    (clojure.core/contains? G__12809 :locale)
    (. locale (clojure.core/get G__12809 :locale))
    ...
    true (.build)))

На практике, однако, этот код:

  • менее читабелен и менее удобен в обслуживании (есть библиотеки, которые интенсивно используют макросы и их трудно читать), и
  • вы можетеВы хотите добавить дополнительную специфическую обработку для некоторых методов (например, преобразование строки локали в объект Locale).

Таким образом, вам гораздо лучше написать обертку вручную - или, есливам нужно использовать Builder только один раз в своем коде, полностью опустить обертку и использовать взаимодействие.

0 голосов
/ 05 октября 2018

Вы можете использовать деструктурирование в let:

(let [{:keys [a b c]} {:a 1 :b false}]
  [a b c])
;; => [1 false nil]

или в аргументах функции:

(defn args-demo [{:keys [a b c]}]
  [a b c])
(args-demo {:a 1 :b false})
;; => [1 false nil]

Проблема заключается в том, что он связывается с nil если конкретный ключ отсутствует на карте.Если ваши значения могут иметь значения nil, это не сработает.

Вы можете использовать некоторые значения «маркера» для отсутствующих значений:

(let [{:keys [a b c] :or {a ::absent b ::absent c ::absent}} {:a 1 :b nil}]
  (cond-> {}
    (not= a ::absent) (assoc :a2 a)
    (not= b ::absent) (assoc :b2 b)
    (not= c ::absent) (assoc :c2 c)))
;; => {:a2 1, :b2 nil}

Вы также можете создать макрос:

(defmacro when-key-present
  [k m & body]
  `(let [{:keys [~k] :or {~k ::not-found}} ~m]
     (when (not= ~k ::not-found)
       ~@body)))

(when-key-present a {:a false :b nil}
  (println "a was" a))
;; prints "a was false"

(when-key-present b {:a false :b nil}
  (println "b was" b))
;; prints "b was nil"

(when-key-present c {:a false :b nil}
  (println "c was" c))
;; doesn't print anything

И ваша функция станет такой:

(defn reader-options [opts]
  (let [builder (CsvReadOptions$Builder.)]
    (when-key-present locale opts
      (.locale builder locale))
    (when-key-present header opts
      (.header builder header))
    (.build builder)))

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

(let [builder (CsvReadOptions$Builder.)]
  (doseq [k (keys opts)]
    (set-builder-property builder k opts)))

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

...