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,但在这两种情况下,你只需要从 -> 纸线程到 ->>,就可以在表尾得到threaded值。

当你在一条通常是直线线程中只有一个或两个表单,且不想在开头或结尾进行threading时,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 ...))

然后你可以在一个直接的 -> 线性表达式中使用这个包装器,而不需要使用 as->
哦,对不起,我在评论中表达不清楚。我不理解“为什么”。为什么不就像你俩指出的一样,接受“as-> inside ->”呢?
因为仅仅使用 -> 或仅仅使用 ->-> 的简单链式操作更易于阅读。因此,为函数编写一个简单的包装器可能是提高可读性的最佳途径,特别是如果你需要在多个 -> 链中使用它。
+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->

...