从Clojure映射中初始化Java构建器类的更好方法?

时间:2018-10-05 06:24:49

标签: clojure clojure-java-interop

所有人

我正在尝试编写将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 :(得分:1)

理论上,您可以编写一个宏,将其扩展为所需的代码:

(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,则完全省略该包装器并使用互操作。

答案 1 :(得分:0)

您可以在let中使用destructuring

(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)))

您甚至可以创建一个宏,该宏假定opts映射中的键与应调用的builder方法相同,因此您可以像这样使用它:

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

但这变得越来越神奇并且难以理解,因此在诉诸宏之前我会三思而后行。

答案 2 :(得分:0)

在此问题中,您遇到以下几个因素:

  1. 可选参数
  2. 可变对象
  3. Java互操作

这就是您在每行上复制localeheader 3次的原因。我想不出一种减少重复的简单方法。如果这是应用程序中的常见模式,则可以编写一个宏(编译器扩展名)来简化操作。除非这在您的代码中经常出现,否则成本(复杂性,文档编制,误解等)将大大超过收益。

有点像买新车而不是清洗旧车。除了极端情况外,付出的代价可能不值得。

;)