请在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-> 的参数顺序(expr 标识符号体)有利于线程化,但在这两种情况下,您只需从 -> 线程到 ->>,就可以将线程化值放在表单的末尾。

当您有一个或两个位于通常线性线程中的表单,且不想让线程化值位于开始或结束时,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-> 在 -> 内部" 的观点。

关于包装那个令人烦恼的函数的评论意味着这

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

然后您可以使用这个包装器在直 -> 线程表达式中使用,而无需使用 as->
by
哦,对不起,我在评论中表述不清楚。我没有理解“为什么”。为什么不直接接受 “as-> 在 -> 内部”,正如您所指出的那样呢?
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-> 高。我想这归结为简洁性。关于普通的threading宏的妙处在于,如果一个函数只接受一个参数,你可以省略括号。所以

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

可以写成

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

但是,你也可以过度使用。当我发现自己写着像

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

这样的东西时,我会停止编码,去找散步...

+1

我一直在使用它,并且没有注意到任何问题。而且,在宏的代码中看起来很简单,没有缺点。

0

...