我有一个通用转换库的代码:
(defn using-format [format] {:format format})
(defn- parse-date [str format]
(.parse (java.text.SimpleDateFormat. format) str))
(defn string-to-date
([str]
(string-to-date str (using-format "yyyy-MM-dd")))
([str conversion-params]
(parse-date str (:format (merge (using-format "yyyy-MM-dd") conversion-params)))))
我需要能够这样称呼它:
(string-to-date "2011-02-17")
(string-to-date "2/17/2011" (using-format "M/d/yyyy"))
(string-to-date "2/17/2011" {})
第三种情况有些问题:地图不一定包含对函数至关重要的键:format
。这就是merge
具有默认值的原因。
我需要有十几个类似的函数用于所有其他类型之间的转换。是否有一种更优雅的方式,不需要我复制粘贴,在每个函数中使用merge
等?
理想情况下,寻找这样的东西(宏?):
(defn string-to-date
(wrap
(fn [str conversion-params]
(parse-date str (:format conversion-params))) ; implementation
{:format "yyyy-MM-dd"})) ; default conversion-params
...会产生一个重载函数(一元和二元),在第一个例子中二进制函数有merge
。
答案 0 :(得分:5)
因此,为了更严格地定义它,您需要创建一个创建转换器函数的宏。转换器函数是具有两个arities,一个参数和两个参数的函数。转换器函数的第一个参数是要转换的对象。第二个参数是选项映射,它会以某种方式影响转换(就像示例中的格式字符串一样。)
可以指定默认参数图。使用一个参数调用时,转换器函数将使用默认参数映射。当使用两个参数调用时,转换器函数会将默认参数映射与传入的参数映射合并,以便传入的参数覆盖默认值(如果存在)。
我们称之为宏定义转换器。 Def转换器将采用三个参数,第一个是要创建的函数的名称。第二个是两个参数的匿名函数,它实现了两个转换器,没有默认的parm合并。第三个参数是默认的parm map。
这样的事情会起作用:
(defmacro def-converter [converter-name converter-fn default-params]
(defn ~converter-name
([to-convert#]
(let [default-params# ~(eval default-params)]
(~converter-fn to-convert# default-params#)))
([to-convert# params#]
(let [default-params# ~(eval default-params)]
(~converter-fn to-convert# (merge default-params# params#))))))
然后你就可以使用它:
(def-converter
string-to-date
(fn [to-convert conversion-params]
(parse-date to-convert conversion-params))
(using-format "yyyy-MM-dd"))
但是你必须改变你的一个辅助函数:
(defn- parse-date [str params]
(.parse (java.text.SimpleDateFormat. (:format params)) str))
这是因为宏需要足够通用来处理任意参数映射,所以我们不能指望。可能存在各种方法,但是我不能想到一个非常简单的方法,而不仅仅是将其推送到辅助函数(或者需要传递给def-converter的匿名函数)。
答案 1 :(得分:2)
clojure.contrib.def / defnk 非常方便:
(use 'clojure.contrib.def)
...
(defnk string-to-date [str :format "yyyy-MM-dd"]
(parse-date str format))
(string-to-date "2011-02-17")
(string-to-date "2/17/2011" :format "M/d/yyyy")
答案 2 :(得分:1)
为了记录,这是我晚上晚些时候想到的:
(defmacro defconvert [name f default]
`(defn ~name
([v#] (~name v# ~default))
([v# conversion-params#] (~f v# (merge ~default conversion-params#)))))
它似乎工作并生成我在那里的定义。我可以使用defnk
或其他一些内置机制,拥有默认值的地图并接受某些但不一定全部的覆盖?