Clojure 合并函数

Posted

技术标签:

【中文标题】Clojure 合并函数【英文标题】:Clojure coalesce function 【发布时间】:2011-05-04 11:12:22 【问题描述】:

SQL 提供了一个名为 coalesce(a, b, c, ...) 的函数,如果它的所有参数都为 null,则返回 null,否则返回第一个非 null 参数。

您将如何在 Clojure 中编写这样的内容?

它将被这样调用:(coalesce f1 f2 f3 ...) 其中fi只有在需要时才应评估的表单。如果f1 不为零,则不应评估f2——它可能有副作用。

也许 Clojure 已经提供了这样的函数(或宏)。

编辑:这是我想出的解决方案(改编自 Stuart Halloway 的 Programming Clojure,第 206 页上的(and ...) 宏):

(defmacro coalesce
  ([] nil)
  ([x] x)
  ([x & rest] `(let [c# ~x] (if c# c# (coalesce ~@rest)))))

似乎有效。

(defmacro coalesce
  ([] nil)
  ([x] x)
  ([x & rest] `(let [c# ~x] (if (not (nil? c#)) c# (coalesce ~@rest)))))

固定。

【问题讨论】:

【参考方案1】:

你想要的是“或”宏。

从左到右一次计算一个表达式。如果一个表格 返回逻辑真值,或返回该值但不返回 评估任何其他表达式,否则返回 最后一个表达式的值。 (or) 返回 nil。

http://clojuredocs.org/clojure_core/clojure.core/or

如果您只想要 nil 而不是 false,请重写 and 并将其命名为 coalesce。

编辑:

这不能作为一个函数来完成,因为函数首先评估它们的所有参数。这可以在 Haskell 中完成,因为函数是惰性的(不是 100% 确定 Haskell 的事情)。

【讨论】:

我想要 first 非零值,而不是最后一个,但是 (or ...) 可以满足我的需要。我没有意识到 (and ...)(or ...) 返回值。我以为他们返回的是假的或真的。但即使是这些也不会返回我想要输入 false 的值。 哦,我肯定会改变的。【参考方案2】:

您可以使用 1.2 中引入的 keep:

编辑:扩展答案一点点。直接调用的宏。例如的助手。应用 + 惰性序列生成值。

(defn coalesce*
  [values]
  (first (keep identity values)))

(defmacro coalesce
  [& values]
  `(coalesce* (lazy-list ~@values)))

但是,为了防止对价值进行评估,我们需要一些本土化的方法。

丑陋的:

(lazy-cat [e1] [e2] [e3])

代码更复杂但更漂亮:

(defn lazy-list*
  [& delayed-values]
  (when-let [delayed-values (seq delayed-values)]
    (reify
      clojure.lang.ISeq
      (first [this] @(first delayed-values))
      (next  [this] (lazy-list* (next delayed-values)))
      (more  [this] (or (next this) ())))))

(defmacro lazy-list
  [& values]
  `(lazy-list* ~@(map (fn [v] `(delay ~v)) values))

【讨论】:

我明白为什么非宏解决方案可能会更好,因为宏解决方案不能与其他功能组合。 @ralph:当然接受的解决方案更快,但我的解决方案更灵活。你应该选择什么取决于你的需要。如果您不需要速度,但想要合并一个延迟创建的序列,我的解决方案可以解决问题。如果您需要快速处理几个已知值。然后是arjan的救援解决方案。 YMMV。 :) 我不是在批评。无论如何,这更像是一项学术活动。我在考虑如何在 Scala 中实现“elvis”运算符,这让我想到了 Clojure 中类似的东西。【参考方案3】:

基于 nickik 的回答和“或”clojure 宏:

(defmacro coalesce
    ([] nil)
    ([x] x)
    ([x & next]
       `(let [v# ~x]
           (if (not (nil? v#)) v# (coalesce ~@next)))))

【讨论】:

@user128186:不确定您是否需要在(if ...) 语句中使用(not (nil? v#)),因为任何不是falsenil 的东西都会计算为true。否则我们的解决方案是相同的。 为什么要对“或”宏进行 1:1 重写? @Ralph:所以你想要 first not nil 或 first not false 值? @ajan:非常好的观点!我想我需要明确地测试nil @nickik: :-) 没有意识到我有。【参考方案4】:

也许我误解了这个问题,但这不只是第一个过滤元素吗?

例如:

user=> (first (filter (complement nil?) [nil false :foo])) 错误的 user=> (first (filter (complement nil?) [nil :foo])) :foo user=> (first (filter (complement nil?) [])) 零 user=> (first (filter (complement nil?) nil)) 零

可以缩短为:

(定义合并 [& vals] (first (filter (complement nil?) vals))) 用户=>(合并零假:foo) 错误的 用户=>(合并零:foo) :foo 用户=>(合并为零) 零 用户=>(合并) 零

【讨论】:

这是另一种 seq-y 方式。第三个是(first (remove nil? ...))。然而,这并没有解决要合并的表达式只能根据需要进行评估的问题。对于(repeatedly #(generate-something)) 之类的东西,这适用于开箱即用,但不适用于“文字”值:[(do-something) (do-otherthing) (do-thirdthing)]。在这里,所有内容都在过滤器看到之前进行评估。 是的,我错过了对 args 要求的惰性评估。【参考方案5】:

如果您希望避免使用宏,请使用一些函数版本的 coalesce:

(defn coalesce
  "Returns first non-nil argument."
  [& args]
  (first (keep identity args)))

(defn coalesce-with
  "Returns first argument which passes f."
  [f & args]
  (first (filter f args)))

用法:

=> (coalesce nil "a" "b")
"a"
=> (coalesce-with not-empty nil "" "123")
"123"

与规范不同,这将评估所有参数。如果要进行短路评估,请使用or 或其他适当的宏解决方案。

【讨论】:

以上是关于Clojure 合并函数的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 基于键的地图合并列表

markdown 列表交叉合并类似Python中的zip功能(clojure clj ruby​​ scala map vector list)

惯用的 Clojure 函数别名

Clojure - 1 个函数的 2 个版本。哪个更惯用?

什么是Clojure内在函数

为啥没有窥视! clojure 瞬态向量的函数?