2024 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面以了解更多关于它是如何工作的信息。

+4
Clojure

我断断续续地使用 Clojure 接近两年,为了业余项目读了几本书,但前几天我又重新阅读了 threading 宏的指南,终于注意到了 as->。也许我是瞎了,但我觉得我以前见过的例子都没有用它,也没有任何书籍进行过重点介绍。和其它不那么灵活的 threading 宏相比,使用它是否有显著的性能损失?

我一直都喜欢通过将事物放入 -> 或 ->> 来构建数据处理管道的体验(能够通过注释掉一行来移除步骤以进行测试真是太好了!),但我却陷入了希望所有我想要的函数都能和 -> 或 ->> 顺利合作的困境。我必须承认,我已经到了一个环节需要将函数封装以改变参数顺序。我对 as-> 的喜爱如此之深,以至于我确信肯定有一些我疏忽的缺点。为什么这不是连接函数的 正确 方式呢?

5 个答案

+13

我惊讶没有人提到 as-> 是设计成在 ->(所有的 threading 宏都可以在 -> 中工作)内部使用的。在阅读其他人的回答后,这是我所期待的:

(-> 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

as-> 非常优秀,但其实现方式与 ->->> 不同,可能会产生不同的行为。 ->->> 递归嵌套调用,而 as-> 创建一系列 let 绑定。例如,(-> a long-running future) 转换为 (future (long-running a)),而 (as-> a $ (long-running $) (future $)) 变为 (let [$ a, $ (long-running $)] (future $))(其实并不像看起来那样异步运行)。

实际上,我没有发现这成问题,但这是关于在特定情况下使用 as-> 的一种警告。

...