【问题标题】:Can I get rid of these eval?我可以摆脱这些评估吗?
【发布时间】:2014-04-08 15:20:59
【问题描述】:

我想构建一个要在 Redis 管道中执行的操作列表。我正在使用accession,因为它比carmine 简单得多,但现在我需要连接池(加入时丢失),因此我再次查看胭脂红(这似乎是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 可能会影响性能(除了我一直被教导要如果可能,避免评估)。我认为 defmacro 是/可以在宏扩展时进行评估,这可能更早(在 AOT 编译期间?)并且不使用 eval。

有什么我可以做的吗?我应该搬到另一个图书馆吗? (我查看了 carmine 的源代码:我的痛苦只是由于作者的一点点方便:*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!)))

显然,我可以写这样的东西,但如果这确实是使用胭脂红的预期方式,那么这些柯里化函数将与(或代替)普通函数一起提供。通过使用语法引用构建 defs 也可以减少很多行,但这是偶然的复杂性,而不是原始问题所固有的。

【问题讨论】:

    标签: macros clojure eval


    【解决方案1】:

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

    (defmacro execute [& forms]
       `(redis/wcar {} ~@forms))
    
    (defn get1 []
      (redis/get 1))
    
    (execute (get1) (get1) ...)
    

    这基本上是胭脂红应该的用法(在自述文件中的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)
    

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

    【讨论】:

    • 这样使用它非常麻烦:我必须:1)为我正在使用的每个 redis 函数创建一个柯里化函数(现在我的代码中有 hset , hsetnx, hincrby, hgetall, sadd, dbsize, scard, smembers, info... 这相当于很多样板文件) 或 2) 在 partial 中包装 redis 命令的每个调用
    • 更不用说你的执行有一个不同的 API:为了把它变成一个更有用的形式,我必须使用类似...(defmacro execute [forms] `(redis/wcar {} ~@(map apply forms))) 但是一旦我这样做了,它实际上可能并不比我当前的解决方案差(必须对任何 redis 命令的每次调用都进行语法引用)
    • 首先,要获得与execute 相同的函数签名,只需在参数列表中省略&amp;。然后它将接受任何命令序列。其次,如何涉及大量样板文件?如果您不想要defn 和参数列表[],只需使用(def get1 #(redis/get 1)) 之类的东西。所以,我只能假设我仍然没有得到你的要求(根本)。您能否用“大量样板”的示例扩展您的问题?
    • 我考虑过引用defs 的语法,但没有考虑定义statements 宏...这似乎是我想做的一个很好的折衷方案。谢谢。但是,如果我的用例不是非常不寻常,您认为将此类辅助函数集成/提交到上游胭脂红会有用吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多