Joh*_*ian 4 functional-programming clojure operators higher-order-functions
假设我有几个向量
(def coll-a [{:name "foo"} ...])
(def coll-b [{:name "foo"} ...])
(def coll-c [{:name "foo"} ...])
Run Code Online (Sandbox Code Playgroud)
并且我想看看第一个元素的名称是否相等.
我可以
(= (:name (first coll-a)) (:name (first coll-b)) (:name (first coll-c)))
Run Code Online (Sandbox Code Playgroud)
但随着更多功能的组合,这很快就会变得疲惫和过于冗长.(也许我想比较第一个元素名称的最后一个字母?)
直接表达计算的本质似乎是直观的
(apply = (map (comp :name first) [coll-a coll-b coll-c]))
Run Code Online (Sandbox Code Playgroud)
但它让我想知道这种事情是否有更高级别的抽象.
我经常发现自己比较/以其他方式操作通过应用于多个元素的单个合成来计算的东西,但是地图语法看起来有点偏离我.
如果我要回家酿造某种运算符,我会想要语法
(-op- (= :name first) coll-a coll-b coll-c)
Run Code Online (Sandbox Code Playgroud)
因为大多数计算都表达在(= :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
Run Code Online (Sandbox Code Playgroud)
就像是
(defmacro -op-
[[op & to-comp] & args]
(let [args' (map (fn [a] `((comp ~@to-comp) ~a)) args)]
`(~op ~@args')))
Run Code Online (Sandbox Code Playgroud)
对于您的附加示例,我经常使用transduce:
(transduce
(map (comp :age first))
+
[coll-a coll-b coll-c])
Run Code Online (Sandbox Code Playgroud)
您的相等用例比较棘手,但您可以创建自定义缩减函数来维护类似的模式.这是一个这样的功能:
(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))))))
Run Code Online (Sandbox Code Playgroud)
然后用它作为
(transduce
(map (comp :name first))
(all? =)
[coll-a coll-b coll-c])
Run Code Online (Sandbox Code Playgroud)
语义与您的-op-宏非常相似,同时更具惯用性Clojure和更具可扩展性.其他Clojure开发人员将立即了解您的使用情况transduce.他们可能不得不研究自定义缩减功能,但这些功能在Clojure中很常见,读者可以看到它如何适应现有模式.此外,如果为简单的map-and-apply不起作用的用例创建新的缩减函数,应该是相当透明的.换能功能也可与其它变换,例如可以由filter和mapcat,对于情况下,当你具有更复杂的初始数据结构.