如何从多方法中提取共享逻辑?

时间:2013-03-03 19:22:43

标签: clojure

鉴于下面的多种方法 -

(defmulti group-data :group-by)

(defmethod group-data :day
  [kv]
  (->> kv :data (group-by
                 (fn [kv]
                   (let [date (:time kv)]
                     (str
                      (month date) "-" (day date) "-" (year date)))))))

(defmethod group-data :month
  [kv]
  (->> kv :data (group-by
                 (fn [kv]
                   (let [date (:time kv)]
                     (str
                      (month date) "-" (year date)))))))

(defmethod group-data :year
  [kv]
  (->> kv :data (group-by
                 (fn [kv]
                   (let [date (:time kv)]
                     (year date))))))

在所有三种多方法中,唯一的区别是字符串函数。如何创建一个更高阶的函数,它只需要用不同的表达式来创建字符串?

还有减少重复代码的更好的想法吗?

4 个答案:

答案 0 :(得分:4)

有时您不需要多方法:

(def dmap {:day #(str (month %) "-" (day %) "-" (year %))
           :month #(str (month %) "-" (year %))
           :year #(year %)})

(defn group-data [kv]
  (->> kv :data (group-by
                 (fn [kv]
                   (let [date (:time kv)]
                     ((dmap (:group-by kv)) date))))))

答案 1 :(得分:3)

最简单的答案是定义一个包含所有共享逻辑的函数,该函数接收另一个用于填充变化的“漏洞”的函数:

(defn helper [kv time-fn]
  (->> kv :data (group-by
                 (fn [kv]
                   (let [date (:time kv)]
                     (timefn data))))))

(defmulti group-data :group-by)

(defmethod group-data :day
  [kv]
  (helper kv (fn [date]
               (str (month date) "-" (day date) "-" (year date)))))

(defmethod group-data :month
  [kv]
  (helper kv
          (fn [date]
            (str (month date) "-" (year date)))))

(defmethod group-data :year
  [kv]
  (helper kv year))

答案 2 :(得分:2)

@amalloy打败了我,但我们解决方案的结构有点不同,所以我也会添加我的。

与往常一样,当存在重复的逻辑时,是时候考虑将其包装在一个函数中了。在我们的例子中,不是直接返回值,而是多方法可以返回用于group-by的算法。然后,group-data可以写成常规函数,使用多方法确定 如何

(defmulti group-by-algorithm :group-by)

(defn group-data
  [kv]
  (->> kv :data (group-by (group-by-algorithm kv))))

(defmethod group-by-algorithm :day
  [_]
  (fn [{date :time}] ; destructuring kv to save us a `let`
    (str
      (month date) "-" (day date) "-" (year date))))

 (defmethod group-by-algorithm :month
  [_]
  (fn [{date :time}]
     (str
       (month date) "-" (year date))))

(defmethod group-by-algorithm :year
 [_]
 (fn [{date :time}]
   (year date)))

此解决方案允许算法返回任何内容并且通常适用。但是,假设每个算法都根据日期返回一些以破折号分隔的值,我们可以通过引入一个函数来减少样板,这个函数接受产生值的函数并通过依次调用它们来生成破折号分隔的字符串。 / p>

(defmulti fns-to-group-by :group-by)

(defn group-by-algorithm
  [group-by-fns]
  (fn [{date :time}]
    (->>
      (map #(%1 date) group-by-fns) ; Call each function on the date
      (interpose "-") ; Separate by dashes
      (apply str)))) ; And mush them into a string

(defn group-data
  [kv]
  (->> kv :data (group-by (group-by-algorithm (fns-to-group-by kv)))))

(defmethod fns-to-group-by :day
  [_]
  [month day year])

 (defmethod fns-to-group-by :month
  [_]
  [month year])

(defmethod fns-to-group-by :year
 [_]
 [year])

答案 3 :(得分:1)

已经有3个答案而且没有提到宏:)我知道有更明显的理由喜欢更高阶的函数,但宏至少应该得到一个答案,所以这里是:

(defmacro group-by-template [date-symbol expression coll]
  `(group-by
      (fn [kv#]
        (let [~date-symbol (:time kv#)]
          ~expression)) ~coll))

(defmulti group-data :group-by)

(defmethod group-data :day
  [kv]
  (->> kv :data (group-by-template date (str (month date) "-" (day date) "-" (year date)))))

(defmethod group-data :month
  [kv]
  (->> kv :data (group-by-template date (str (month date) "-" (year date)))))

(defmethod group-data :year
  [kv]
  (->> kv :data (group-by-template date (year date))))