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 symbol body有利于threading,但在这两种情况下,您都可以简单地从->thread到->>,使threaded的值位于表单的最后。

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))
我认为指出 Stuart Sierra 关于不使用 `as->` 的帖子是有用的:[链接](https://stuartsierra.com/2018/07/15/clojure-donts-thread-as)


我坦诚地说,我已经多次阅读这篇文章,但从未真正理解为什么 Stuart 会更喜欢将 `(fn ... var ...)` 形式包裹到自己的函数中,只是为了调整参数的顺序。
哎呀,我应该知道 Stuart 会在这方面有很多智慧的话 -- 这也加强了我提到的 "as-> inside ->" 的观点。

他对那个讨厌的函数的评论意味着这个

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

然后你可以在一个直线 threading 表达式中使用该包装器,而不需要使用 as->
哎呀,对不起,我的评论不够清楚。我不懂“为什么”。为什么不可以像你俩指出的那样接受 "as-> inside ->" 呢?
因为仅仅由 -> 或仅仅由 ->-> 组成的简单链更容易阅读。所以为函数编写一个简单的包装器可能是提高可读性的最好方法,特别是如果你需要在多个 -> 链中使用它。
+11
by

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->。总的来说,对于我正在做的调用集合来说, either -> 或 ->> 就足够了。根据关于 参数顺序 的通用建议,我总是在做一系列集合操作,或者一系列序列操作,但很少是混合的。

+3
by

为什么这不是链式调用函数的方式呢?

我也想过同样的问题,但老实说,我使用 ->->> 的频率远高于 as->。我想这主要归结于简洁性。关于正常线程宏的一个有趣之处在于,如果一个函数只接受一个参数,你可以完全省略括号。所以

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

可以写成

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

但是你可能会把它写得太过分。当我发现自己在写像下面这样的东西时

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

我会停止编码去散步...

+1
by

我一直在使用它,并且还没有注意到任何事情。此外,查看宏的代码,它看起来很简单,没有任何缺点。

0

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

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

...