13

给定->>这样的管道:

(defn my-fn []
  (->> (get-data)
       (do-foo)
       (do-bar)
       (do-baz)))

我希望使各个阶段成为有条件的。

想到的第一种写法是这样的:

(defn my-fn [{:keys [foo bar baz]}]
  (->> (get-data)
       (if foo (do-foo) identity)
       (if bar (do-bar) identity)
       (if baz (do-baz) identity))

然而,当->>宏试图插入if表单时,这不仅在性能方面看起来很糟糕(有 noopidentity调用),而且实际上无法编译。

写这篇文章的适当、合理干燥的方式是什么?

4

5 回答 5

12

现代 Clojure(即从 1.5 开始)支持多种条件线程选项,但您可能需要cond->>.

条件线程cond->cond->>

Clojure 提供cond->and cond->>,每个都要求一组对:一个测试和一个表达式,如果该测试的计算结果为真。这些与第一次真正的测试非常相似,cond但不会停止。

(cond->> 5
  true inc
  false inc
  nil inc)
=> 6

您的具体示例可能最好这样写:

(defn my-fn [{:keys [foo bar baz]}]
  (cond->> (get-data)
    foo (do-foo)
    bar (do-bar)
    baz (do-baz)))

作为->

值得一提as->,因为它可能是最通用的线程宏。它为您提供了一个名称来指代通过表单传递的“事物”。例如:

(as-> 0 n
  (inc n)
  (if false
    (inc n)
    n))
=> 1

(as-> 0 n
  (inc n)
  (if true
    (inc n)
    n))
=> 2

这在处理需要在参数列表中的不同点对表达式进行线程化的混合函数时提供了很大的灵活性(即从 -> 切换到 ->> 语法)。为了代码的可读性,应该避免使用无关的命名变量,但通常这是表达过程的最清晰和最简单的方法。

于 2016-01-13T12:42:08.600 回答
11

这也有效:

(defn my-fn [{:keys [foo bar baz]}]
  (->> (get-data)
       (#(if foo (do-foo %) %))
       (#(if bar (do-bar %) %))
       (#(if baz (do-baz %) %)) ))
于 2012-08-01T22:46:12.380 回答
9

您可能对这些宏感兴趣https://github.com/pallet/thread-expr

于 2012-08-02T02:08:16.540 回答
5

您可以修复编译部分(尽管不是苦行部分,方法是让if 语句通过放置另一组as来决定运行哪个函数:( )

(defn my-fn [{:keys [foo bar baz]}]
  (->> (get-data)
   ((if foo do-foo identity))
   ((if bar do-bar identity))
   ((if baz do-baz identity)))

会扩展成一系列这样的调用:

   ;  choose function         and call it    
   ( (if foo do-foo identity) args       )
   ( (if bar do-bar identity) args       )
   ( (if baz do-baz identity) args       )
于 2012-08-01T22:39:35.730 回答
5

如果您经常需要这种东西,一种更通用的方法:

(defn comp-opt [& flag-fns]
  (->> 标志-fns
    (分区 2)
    (先过滤)
    (地图二)
    (申请补偿)))

(defn my-fn [{:keys [foo bar baz]}]
  (map (comp-opt foo do-foo
                 酒吧做酒吧
                 巴兹多巴兹)
       (获取数据)))
于 2012-08-02T00:30:57.113 回答