假设我有几个向量
(def coll-a [{:name "foo"} ...])
(def coll-b [{:name "foo"} ...])
(def coll-c [{:name "foo"} ...])
,我想看看第一个元素的名称是否相等。
我可以
(= (:name (first coll-a)) (:name (first coll-b)) (:name (first coll-c)))
,但是随着更多功能的组合,这很快变得很累人并且过于冗长。 (也许我想比较第一个元素名称的最后一个字母?)
直接表达计算的本质似乎很直观
(apply = (map (comp :name first) [coll-a coll-b coll-c]))
但是让我想知道对于这种事情是否有更高层次的抽象。
我经常发现自己正在比较/否则将通过应用于多个元素的单个合成来计算事物,但是map语法对我来说似乎有点不足。
如果我要自煮某种类型的运算符,我会想要类似
的语法(-op- (= :name first) coll-a coll-b coll-c)
因为大多数计算是用(= :name first)
表示的。
我想将抽象既应用于运算符,又应用于每个参数的函数。也就是说,它应该与比较一样容易求和。
(def coll-a [{:name "foo" :age 43}])
(def coll-b [{:name "foo" :age 35}])
(def coll-c [{:name "foo" :age 28}])
(-op- (+ :age first) coll-a coll-b coll-c)
; => 106
(-op- (= :name first) coll-a coll-b coll-c)
; => true
类似
(defmacro -op-
[[op & to-comp] & args]
(let [args' (map (fn [a] `((comp ~@to-comp) ~a)) args)]
`(~op ~@args')))
答案 0 :(得分:7)
对于您的添加示例,我经常使用transduce
:
(transduce
(map (comp :age first))
+
[coll-a coll-b coll-c])
您的相等用例比较棘手,但是您可以创建一个自定义的归约函数来维持相似的模式。这是一个这样的功能:
(defn all? [f]
(let [prev (volatile! ::no-value)]
(fn
([] true)
([result] result)
([result item]
(if (or (= ::no-value @prev)
(f @prev item))
(do
(vreset! prev item)
true)
(reduced false))))))
然后将其用作
(transduce
(map (comp :name first))
(all? =)
[coll-a coll-b coll-c])
语义与您的-op-
宏非常相似,但它们既是惯用的Clojure,又是可扩展的。其他Clojure开发人员将立即了解您对transduce
的用法。他们可能必须研究自定义归约功能,但是此类功能在Clojure中非常常见,读者可以看到它如何适合现有模式。而且,对于简单的映射和应用无法使用的用例,如何创建新的归约函数应该是相当透明的。如果您具有更复杂的初始数据结构,则转换功能还可以与其他转换(例如filter
和mapcat
)组合在一起。
答案 1 :(得分:2)
您可能正在寻找every?
函数,但是我将其分解并命名子元素来提高清晰度:
(let [colls [coll-a coll-b coll-c]
first-name (fn [coll] (:name (first coll)))
names (map first-name colls)
tgt-name (first-name coll-a)
all-names-equal (every? #(= tgt-name %) names)]
all-names-equal => true
我会避免使用DSL,因为没有必要,这会使其他人更难阅读(因为他们不了解DSL)。保持简单:
(let [colls [coll-a coll-b coll-c]
vals (map #(:age (first %)) colls)
result (apply + vals)]
result => 106
答案 2 :(得分:1)
我认为您不需要宏,只需要参数化op
函数和compare
函数即可。对我来说,您的(apply = (map (comp :name first) [coll-a coll-b coll-c]))
版本非常接近。
这是使它更通用的一种方法:
(defn compare-in [op to-compare & args]
(apply op (map #(get-in % to-compare) args)))
(compare-in + [0 :age] coll-a coll-b coll-c)
(compare-in = [0 :name] coll-a coll-b coll-c)
;; compares last element of "foo"
(compare-in = [0 :name 2] coll-a coll-b coll-c)
我实际上不知道您可以在字符串上使用get
,但是在第三种情况下,您可以看到我们比较了每个foo
的最后一个元素。
这种方法不允许将to-compare
参数用作任意函数,但似乎您的用例主要用于挖掘要比较的元素,然后将任意函数应用于这些值。
我不确定这种方法是否优于上面提供的换能器版本(肯定不那么有效),但是我认为当不需要这种效率时,它提供了一种更简单的选择。
答案 3 :(得分:0)
我将这个过程分为三个阶段:
(map :name coll)
; (map = transf-coll-a transf-coll-b transf-coll-c)
(first calculated-coll)
在玩收藏时,我尝试将多个物品放入收藏中:
(def coll-a [{:name "foo" :age 43} {:name "bar" :age 45}])
(def coll-b [{:name "foo" :age 35} {:name "bar" :age 37}])
(def coll-c [{:name "foo" :age 28} {:name "bra" :age 30}])
例如,匹配:name中第二个字符的项目,并返回第二个项目的结果:
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp #(nth % 1) :name)
op =
fetch second]
(fetch (apply map op (map #(map transf-fn %) colls))))
;; => false
在换能器世界中,您可以使用 sequence 函数,该函数也可用于多个集合:
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp (map :name) (map #(nth % 1)))
op =
fetch second]
(fetch (apply sequence (map op) (map #(sequence transf-fn %) colls))))
计算年龄总和(对于同一级别的所有商品):
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp (map :age))
op +
fetch identity]
(fetch (apply sequence (map op) (map #(sequence transf-fn %) colls))))
;; => (106 112)