Clojure中的通用线程宏

32
注意:这与并发无关,而是关于线程宏。
我知道 `->` 将对象放在第二个位置,`->>` 将参数放在最后一个位置。
现在,我很好奇,就像函数的短手符号 `#( ... % )` 一样,是否有线程的短手符号让我可以将参数放在任意位置?
目标是,不再将线程固定在某个位置上... 我可以编写任意形式,并在特殊位置插入 %%,这就是线程插入的位置。
谢谢!

谢谢你的问题。我想问这个问题已经很久了。有时候我不得不使用 (#(func-adaptor arg1 % arg2)) 来获得我想要的效果,而不能使用 -> 或 ->>。 - jbear
4个回答

37
自Clojure 1.5开始,有一个通用的线程宏as->。本推文举例说明其工作原理:https://twitter.com/borkdude/status/302881431649128448
(as-> "/tmp" x
      (java.io.File. x)
      (file-seq x)
      (filter (memfn isDirectory) x)
      (count x))

首先,'x'被绑定到"/tmp",并创建了一个文件。然后,'x'被重新绑定到生成的文件,并通过'file-seq'函数进行处理,等等。


1
Swiss Arrows库很有趣,但自2014年以来,这可能是最好的答案,因为它已经内置于Clojure 1.5版本中。 - David J.

26

Swiss Arrows库中的“钻石魔杖”可以完成您所要求的操作:

(-<> 0
 (* <> 5)
 (vector 1 2 <> 3 4))
; => [1 2 0 3 4]

话虽如此,在我的Clojure使用经验中,这并不是你经常需要的东西(或者在任何情况下都不需要


9
我喜欢这些宏滥用Clojure变量名称的灵活性。 - user1311390
2
更好的是,从1.5版本开始,Clojure提供了as->,如另一个答案所解释的那样。 - David J.
我经常使用 as->,但我喜欢钻石符号的简洁和视觉清晰性。这通常发生在相同线程中使用具有不同参数数量的常用函数时,比如 mapassoc - ctpenrose

15

如果其他人也遇到这个问题,需要知道现有宏的用途,而任意位置的宏则会导致较差的API设计。

->宏将参数放在第一个位置。 这对应于处理某些主题参数的函数,例如conjassoc

->>宏将参数放在最后一个位置。 这对应于处理序列的函数,例如mapreduce

精心设计API,就不太可能需要这样的宏了。


2
合理的想法,Alex。虽然有时候你需要将数据通过你没有设计的函数进行处理。我猜可以使用(#(f a1 % a3))来解决这个问题。 - jbear
nth 是一个很好的核心函数示例,它在集合上运行,但期望它不是作为最后一个参数,而是作为第一个参数。 - M Smith
nth也不像其他函数一样返回一个序列,这使得它在挖掘嵌套序列时表现良好。我认为这就是它被设计成这样的原因。 - deterb

2

有一个库提供了这个功能,但我忘记了是哪个库。它可能在已弃用的clojure-contrib中。它是-$>宏。

但你可以从clojure的核心->宏派生出一个来创建你正在寻找的那个:

(defmacro -$>
    ([x] x)
    ([x form] (if (seq? form)
                (with-meta (map #(if (= %1 '$) x %1) form) (meta form))
                (list form x)))
    ([x form & more] `(-$> (-$> ~x ~form) ~@more)))

使用$表示插入点:

user=> (-$> 2 str (identity $) (println $))
2
nil

从技术上讲,在一个表单中可以使用多个$。但是为了简单起见,这种实现会导致同一表单多次扩展的问题。

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