2013-03-03 48 views
3

鉴于多方法如下 -如何从多方法中提取共享逻辑?

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

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

还有什么更好的想法,以减少重复的代码?

回答

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)) 
+0

感谢@amalloy,这是一个整洁的解决方案。 – murtaza52 2013-03-04 14:17:30

2

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

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

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

该解决方案可让算法返回任何内容并且通常适用。然而,假设每个算法都会返回一些基于日期的由破折号分隔的字符串,我们可以通过引入一个函数来减少样板,该函数接受生成值的函数并通过依次调用它们来生成破折号分隔的字符串。

(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]) 
+0

这真的很酷。你正在把复杂性放在它应该是的。 – murtaza52 2013-03-04 14:19:08

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)))))) 
+0

嘿,这真的很酷!使用地图存储功能,然后根据需要检索它们。所以我的下一个问题是,我应该在什么时候选择一种多方法来处理上述问题? – murtaza52 2013-03-04 14:11:13

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)))) 
+1

宏比这个问题的必要性更强大,但至少如果你要使用宏,你应该尝试减少浪费的输入量:https://gist.github.com/amalloy/5084918 – amalloy 2013-03-04 19:48:02

+0

@amalloy:尼斯..我不知道关于宏。 – Ankur 2013-03-05 04:11:42