我可以摆脱这些评估吗?

时间:2014-04-08 15:20:59

标签: macros clojure eval

我想构建一个在Redis管道中执行的操作列表。我使用accession因为它比carmine简单得多,但现在我需要连接池(从加入中丢失),因此我再次看到胭脂红(似乎是-to和最完整的redis clojure库)

我设法让这个工作:

; require [taoensso.carmine :as redis]
(defn execute1 [request] (redis/wcar {} (eval request)))
(defmacro execute [body] `(redis/wcar {} ~@(eval body)))

(def get1 `(redis/get 1))
(execute1 get1)
(execute [get1])

但鉴于我将构建成千上万个元素的向量,我有点担心eval可能的性能影响(除了我&#的事实) 39;如果可能的话,我们被教导要始终避免评估。我认为defmacro代替是/可以在宏扩展时进行评估,这可能是更早(在AOT编译期间?)而不使用eval。

我能做些什么吗?我应该搬到另一个图书馆吗? (我看了一下胭脂红的源代码:我的痛苦只是因为作者的一个小便利:一个*context*用来避免传递一些额外的论点:摆脱它应该很简单足够了,但我没有投入足够的资金......我可能决定将来转移到另一个数据存储区)

编辑:我已经被要求写一个我认为是我希望避免在我的实际代码中编写的样板的示例,所以:(以下未经测试,它只是一个POC)

(defn hset [id key val]
  #(redis/hset id key val))

(defn hsetnx [id key val]
  #(redis/hsetnx id key val))

(defn hincrby [id key increment]
  #(redis/hincrby id key increment))

(defn hgetall [id key]
  #(redis/hgetall id key))

(defn sadd [id el]
  #(redis/sadd id el))

(defn scard [id]
  #(redis/scard id))

(defn smembers [id]
  #(redis/smembers id))

(defmacro execute [forms]
  `(redis/wcar {} ~@(map apply forms)))

; end boilerplate

(defn munge-element [[a b c]]
  (conj
    (mapcat #(hincrby a :whatever %) b)
    (sadd c b)
    (hsetnx a c))

(defn flush-queue! [queue_]
  (execute queue_)
  [])

(defn receive [item]
  (if (< (count @queue) 2000)
    (swap! queue conj (munge-element item))
    (swap! queue flush-queue!)))

显然,我可以写这样的东西,但如果这确实是使用胭脂红的有条理的方式,那么这些曲线函数将提供(或代替)正常的函数。通过使用语法引用构建def s也可以减少许多行,但这是偶然的复杂性,而不是原始问题的固有。

1 个答案:

答案 0 :(得分:2)

以下代码与您的代码没有太大区别:

(defmacro execute [& forms]
   `(redis/wcar {} ~@forms))

(defn get1 []
  (redis/get 1))

(execute (get1) (get1) ...)

这基本上是如何使用carmine (在README中跟随this suggestion)。如果它不适合您的需求,您能否澄清原因?


在编辑问题之后,要更清楚地说明应该完成什么,我想我有一个解决方案。您正在尝试创建将在某个特定时间执行的语句列表。为了达到这个目的,你将每个语句包装在一个函数中,我同意,这是很多样板。

但不必要。你可以通过一个自动为你创建thunk的宏来获得相同的结果:

(defmacro statements [& forms]
  `(vector
     ~@(for [f forms]
         `(fn [] ~f))))

现在,无论你传递给这个宏,都会产生一个零参数函数的向量,可以对其进行求值,例如:使用:

(defn execute-statements [fns]
   (redis/wcar {} (mapv #(%) fns))

你的例子变成了以下几点:

(defn munge-element [[a b c]]
  (statements
    (mapcat #(redis/hincrby a :whatever %) b)
    (redis/sadd c b)
    (redis/hsetnx a c))

(defn flush-queue! [queue_]
  (mapv execute-statements queue_)
  [])

编辑:这会在自己的管道中执行每个批处理。如果您想在一个帐户中执行此操作,请使用concat代替conj来构建您的队列(receive)和(execute-statements queue_)而不是(mapv execute-statements queue_)

注意: IIRC正确,这:

(redis/wcar {} a [b c]])

返回与此相同的结果:

(redis/wcar {} a b c)

即,胭脂红会在某处收集结果并始终返回所有这些结果的向量。即使不是,我认为你仍然可以通过稍微调整这里提供的内容来避免你可怕的样板。