Clojure:从模板生成函数

时间:2011-02-17 21:46:55

标签: clojure

我有一个通用转换库的代码:

(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

3 个答案:

答案 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或其他一些内置机制,拥有默认值的地图并接受某些但不一定全部的覆盖?