从 clojure 映射中使 init 成为 java 构建器 class 的更好方法?
Better way to make init a java builder class from clojure map?
大家
我正在尝试编写一个函数来将 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 ))
)
)
抱歉,如果问的太多了,在 clojure 中有没有更好的方法来完成这个?因为密钥在一行中重复。
(contains? opts :locale ) (.locale (:locale opts))
再次感谢您的任何建议。
您可以在 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
个值,那么它将不起作用。
您可以使用一些 "marker" 值作为不存在的值:
(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 映射中的键与应调用的构建器方法相同,这样您就可以像这样使用它:
(let [builder (CsvReadOptions$Builder.)]
(doseq [k (keys opts)]
(set-builder-property builder k opts)))
但这变得越来越神奇,也越来越难以理解,所以在求助于宏之前我会三思而后行。
在这个问题中你有几个因素:
- 可选参数
- 可变对象
- Java 互操作
这就是您在每行上复制 locale
和 header
3 次的原因。我想不出减少这种重复的直接方法。如果这是您应用程序中的常见模式,您可以编写一个宏(编译器扩展)以使其更容易。除非这在您的代码中经常发生,否则成本(在复杂性、文档、误解等方面)将大大超过收益。
有点像买新车而不是清洗旧车。除非在极端情况下,否则可能得不偿失。
;)
理论上,您可以编写一个扩展为您需要的代码的宏:
(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
对象)。
因此,您最好手动编写包装器 - 或者,如果您只需要在代码中使用一次构建器,则完全省略包装器并使用互操作。
大家
我正在尝试编写一个函数来将 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 ))
)
)
抱歉,如果问的太多了,在 clojure 中有没有更好的方法来完成这个?因为密钥在一行中重复。
(contains? opts :locale ) (.locale (:locale opts))
再次感谢您的任何建议。
您可以在 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
个值,那么它将不起作用。
您可以使用一些 "marker" 值作为不存在的值:
(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 映射中的键与应调用的构建器方法相同,这样您就可以像这样使用它:
(let [builder (CsvReadOptions$Builder.)]
(doseq [k (keys opts)]
(set-builder-property builder k opts)))
但这变得越来越神奇,也越来越难以理解,所以在求助于宏之前我会三思而后行。
在这个问题中你有几个因素:
- 可选参数
- 可变对象
- Java 互操作
这就是您在每行上复制 locale
和 header
3 次的原因。我想不出减少这种重复的直接方法。如果这是您应用程序中的常见模式,您可以编写一个宏(编译器扩展)以使其更容易。除非这在您的代码中经常发生,否则成本(在复杂性、文档、误解等方面)将大大超过收益。
有点像买新车而不是清洗旧车。除非在极端情况下,否则可能得不偿失。
;)
理论上,您可以编写一个扩展为您需要的代码的宏:
(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
对象)。
因此,您最好手动编写包装器 - 或者,如果您只需要在代码中使用一次构建器,则完全省略包装器并使用互操作。