【问题标题】:Clojure coalesce functionClojure 合并函数
【发布时间】: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)))))

固定。

【问题讨论】:

    标签: clojure coalesce


    【解决方案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 或其他适当的宏解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      相关资源
      最近更新 更多