请在 2024 Clojure 问卷调查! 中分享您的想法。

欢迎!请参见 关于 页面以获取有关如何工作的更多信息。

+4
Clojure

我已经断断续续地使用 Clojure 几年了,在休闲项目上使用,也读过几本书,但就在前几天,我再次阅读了 threading macros 指南,并终于注意到了 as->。也许我只是瞎了,但我发誓我从没在任何一个例子中看到过它被使用,也没有在任何书中被突出显示。使用它比使用其他更不灵活的 threading macros 会带来很大的性能损失吗?

我总是喜欢构建数据处理管道的经历,将东西放入 -> 或 ->> 中(能够通过取消一行脚本来删除测试步骤真是太好了!),但我陷入了一个困境,即要让所有我想与 -> 或 ->> 配合使用的函数。我必须承认我已经将一个函数封装起来以改变其参数顺序。我在 as-> 上玩得很开心,这让我觉得一定有什么我遗漏的缺点。为什么这不是连接函数的方法呢?

5 个答案

+13

我惊奇地发现没有人提到 as-> 是设计来在 -> 内部工作的(threading macros 都是设计在 -> 内部工作的)。阅读其他人的答案,以下是我的预期:

(-> 2
    (+ 1)
    (as-> x (* 2 x)) ; or just (->> (* 2))
    (range))

(-> thing
    (->> (fn2 bleh)) ; or (as-> x (fn2 bleh x))
    (fn3 blah))

如您所见,as->的参数顺序——表达式符号体——使其适合进行线程化,但在这两种情况下,您可以直接从->变为->>,在表单末尾获得线程化值。

as->在您有一个或两个形式的直线线程,而这些形式不想在线程的首尾有线程化值时最为出色。

(-> thing
    (fn1 arg2 arg3)
    (+ 13)
    (as-> q (fn2 arg1 q arg3))
    (fn3 second-arg))

或者,也许您需要let来进行解构或其他操作

(-> foo
    (processs-it :expand)
    (as-> m (let [{:keys [start end]} m]
              (- end start)))
    (categorize-interval))
by
我认为指出Stuart Sierra关于不要使用`as->`的帖子很有用:https://stuartsierra.com/2018/07/15/clojure-donts-thread-as


我坦白地说,我已经看了这篇博文好几次,但我从未真正理解为什么Stuart会只为了修复参数顺序,就将其`(fn ... var ...)`形式包裹在他的自定义函数中。
by
啊,我本该知道的,Stuart对此会有智慧的话——并且也加强了我提到的“as-> inside ->”点。

他关于包裹那个讨厌的函数的评论意味着这个

(defn wrapper [ctx other args ...]
(irritating other ctx args ...))

然后您可以仅在不需要使用as->的情况下,在普通的->线程表达式中使用该包裹器
by
哦,对不起,我的评论不太清楚。我不懂“为什么”。为什么不能像您们指出的那样,接受“as-> inside ->”呢?
by
因为只包含 -> 或 ->> 的简单链更容易阅读。所以为函数编写一个简单的包装器可能是一个不错的选择,尤其是在您需要将它用于多个 -> 链时。
+11

as-> 在 Clojure 1.5 中被添加,所以一些较老的入门书籍可能没有提及它,但它在创建具有混合顺序的函数的管道时非常有用。

由于这些线程宏是宏,这些线程宏不会对性能造成影响——它们都会在编译时重写代码,因此在执行时,您所做的基本上与直接嵌套函数时相同。

user=> (as-> 2 x (+ x 1) (* 2 x) (range x))
(0 1 2 3 4 5)
user=> (macroexpand-1 '(as-> 2 x (+ x 1) (* 2 x) (range x)))
(clojure.core/let [x 2 x (+ x 1) x (* 2 x)] (range x))

就我个人而言,我很少使用 as->。一般来说,对于我正在调用的函数集,-> 或 ->> 就足够了。由于一般建议在论证顺序上,我要么执行一系列集合操作,要么执行一系列序列操作,但通常不混合使用。

+3

这不是连接函数的方法吗?

我也同样想过这个问题,但说实话,我使用 -> 和 ->> 的频率比使用 as-> 高。我觉得这主要是简短的原因。正常线程宏的一个很方便之处是,如果一个函数只接受一个参数,你可以完全省略括号。所以

(-> 0 (dec) (* 5) (Math/abs))

可以直接写成

(-> 0 dec (* 5) Math/abs)

但确实有时可能写得太多。当我发现自己写出像这样的代码时

(->> thing fn1 (fn2 bleh) (#(fn3 % blah)))

我就停下来去散步了...

+1

我经常使用它并且没有注意到任何问题。此外,查看宏的代码似乎它很直接,没有缺点。

0
by

as-> 挺好的,但其实现方式与 ->->> 不同,可能会产生不同的行为。->->> 会递归嵌套调用,而 as-> 会创建一系列 let 绑定。例如,(-(-> 长时间运行的未来)) 转换为 (future -(长时间 -(a))),而 (as-> -(a) -(长时间 -(b)) -(future -(c))) 则变成 (let [a, b (长时间 b)] (future c))(其异步执行效果并不像表面上看起来那么明显)。

实际上,我没发现这有什么问题,但这是关于在某些情况下使用 as-> 的一个注意事项。

...