Clojure中嵌套if-let的更好方法

11

假设我有这样一个地图:

(def m {:a "A" :b "B"})

如果:a:b都不是nil,我想要执行一些操作:

(if-let [a (:a m)]
  (if-let [b (:b m)]
      ... etc ))
或者
(if (and (:a m) (:b m))
  (let [{a :a b :b} m]
      ... etc ))

甚至还可以

(if (every? m [:a :b])
  (let [{a :a b :b} m]
      ... etc ))

有没有更简洁(即一行代码)的方法来实现这个?

5个回答

7
我认为在此处可能需要一个宏来创建您想要的行为。虽然我从未编写过宏(尚未),但以下表示让我觉得这可能相当简单:
(let [{:keys [a b]} m]
   (when (every? identity [a b])
      (println (str "Processing " a " and " b))))

使用解构绑定的:keys形式和every?函数能够实现对一组键进行解构和检查,并使得绑定的本地变量在随后的代码块中可用。
这可以用来创建一个宏,例如:(when-every? [keys coll] code-with-bindings) 如果我有时间,我可能会更新这个答案并提供宏代码。

6
你可以使用Clojure的有用特性之一——map destructuring。这还利用了两个事实,即and是短路运算符,第一个映射中任何未在第二个映射中找到的键都会得到nil,即假值。请注意保留HTML标记。
(let [{a :a b :b} {:a 1 :b "blah"}] 
  (and a b (op a b)))

好的,所以这是两行而不是一行......还有这并没有区分nil和其他假值。


1

顺便说一下,我发现了一个丑陋的一行代码,它可以工作是因为and如果它们都为真,则返回其参数列表中的最后一项:

(if-let [[a b] (and (:a m) (:b m) [(:a m)(:b m)])]
    (println "neither " a " nor " b " is falsey")
    (println "at least one of " a " or " b " is falsey"))

1

not-any?是一个很好的快捷方式:

user> (not-any? nil? [(m :a) (m :b)])
true
user> (not-any? nil? [(m :a) (m :b) (m :d)])
false
user> 

(every? some? ... )”不是更好吗?这样你就可以避免双重否定。 - mutantacule
not-any? 可以在找到一个符合条件的元素时停止检查,在许多情况下可以稍微提高一些时间效率。 - Arthur Ulfeldt

1

我不太确定您想要做什么,如果键具有非nil值,或者您是否希望返回非nil键或值。因此,我只解决了返回非nil键的问题。

您可以将以下内容用作最终解决方案的中间步骤。

我展示了我使用的所有步骤,不是为了卖弄,而是为了提供完整的答案。命名空间是repl-test。它有一个与之关联的主函数。

repl-test.core=> (def m {:a "A" :b "B" :c nil})
#'repl-test.core/m

repl-test.core=> (keys m)
(:a :c :b)

最后,最终:
; Check key's value to determine what is filtered through.
repl-test.core=> (filter #(if-not (nil? (%1 m)) (%1 m)) (keys m) )
(:a :b)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接