我很难理解->
和->>
如何扩展。
如果我有以下内容:
(-> query
(write-query-to-excel db csv-file)
(archiver archive-password)
(send-with-attachment send-to subject body)
(clean-up))
这是如何扩展的,我了解->
是一个thread-first
宏,它将值second
作为first
表单插入,并在查看之后例子我还不确定。
->>
如何区别,我知道它会将该项作为函数的last
参数插入,但这句话再次让我感到困惑。
(->> query
(write-query-to-excel db csv-file)
(archiver archive-password)
(send-with-attachment send-to subject body)
(clean-up))
展开时这两个宏看起来如何?
答案 0 :(得分:4)
->
和->>
总是让我想起Matryoshka dolls,因为它们会嵌套传递给它们的所有表达式。
让我们考虑(-> a (b c d) (e f g))
。我们现在可以在每个表达式的第一个参数位置用视觉剪切它们(用管道),但第一个:(-> a (b | c d) (e | f g))
。现在表达式被切成两半,我们可以嵌套它们:首先是(-> (b a c d) (e | f g))
,然后是(-> (e (b a c d) f g))
,这简化为(e (b a c d) f g)
。
->>
以非常类似的方式工作,但插入点位于末尾(最后一个参数位置):(-> a (b c d |) (e f g |))
,它先后扩展为(-> (b c d a) (e f g |))
,(-> (e f g (b c d a)))
和{ {1}}。
答案 1 :(得分:3)
使用clojure.walk/macroexpand
展开宏:
(clojure.walk/macroexpand-all '(-> query
(write-query-to-excel db csv-file)
(archiver archive-password)
(send-with-attachment send-to subject body)
(clean-up)))
...生产
(clean-up (send-with-attachment (archiver (write-query-to-excel query db csv-file) archive-password) send-to subject body))
而
(clojure.walk/macroexpand-all '(->> query
(write-query-to-excel db csv-file)
(archiver archive-password)
(send-with-attachment send-to subject body)
(clean-up)))
...生产
(clean-up (send-with-attachment send-to subject body (archiver archive-password (write-query-to-excel db csv-file query))))
由于结果没有意义,我们可以使用更简单的例子来说明差异:
(clojure.walk/macroexpand-all '(-> a b c d))
;(d (c (b a)))
,与->
或->>
相同。但如果有论据:
(clojure.walk/macroexpand-all '(-> a (b 1 2 3)))
;(b a 1 2 3)
而
(clojure.walk/macroexpand-all '(->> a (b 1 2 3)))
;(b 1 2 3 a)
答案 2 :(得分:0)
@cgrand的答案很好,但我想补充一点。
通过将您的线程优先示例放入(let ...)表单中,您可以实现非常相似的功能:
; better if descriptive names used for intermediate results
(let [tmp1 (write-query-to-excel db csv-file)
tmp2 (archiver tmp1 archive-password)
tmp3 (send-with-attachment tmp2 send-to subject body)
tmp4 (clean-up tmp3) ]
<process result in tmp4>
...)
这通常是最佳解决方案,因为您可以为中间结果使用描述性变量名称。但是,如果您没有真正从命名中间结果中获益,则可以使用通用变量it
作为占位符,并且仍然可以确切地查看下一个表单中每个中间结果的确切位置。这就是来自the Tupelo Core library的it->
宏的工作原理:
想象一下,使用变量it
标记每个表单中插入前一个表达式结果的位置。然后在您的->
示例中,我们得到:
(ns my.proj
(:require [tupelo.core :refer [it->]]
... ))
(it-> (write-query-to-excel db csv-file)
(archiver it archive-password)
(send-with-attachment it send-to subject body)
(clean-up it))
然后在我的->>
示例中,我们会:
(it-> (write-query-to-excel db csv-file)
(archiver archive-password it)
(send-with-attachment send-to subject body it)
(clean-up it))
但是,如果表达式不是在第一个或最后一个参数位置都链接在一起,我们该怎么办?请考虑以下代码:
(it-> 1
(inc it)
(+ it 3)
(/ 10 it)
(str "We need to order " it " items." )
;=> "We need to order 2 items." )
在这种情况下,我们有表达式,其中前一个结果被链接到唯一,第一,最后和中间参数位置。在这种情况下,->
和->>
都不会起作用,并且必须使用it->
或通用(let ...)
形式。
因此,对于简单表达式管道,使用it->
(或类似的as->
)可以使代码更明确地表达表达式如何链接在一起。它还允许您链接不是所有“线程优先”或“线程最后”的表达式。对于更复杂的处理链,使用let表单(可能是嵌套的)是最佳答案,因为您可以命名中间结果,然后以任意复杂的方式组合它们。
答案 3 :(得分:0)
在Ruby世界中,有一种通常的做法是制作所谓的&#34;方法链&#34; 。它本质上是:
Ruby是面向对象的,这样的链看起来像这样:
(-5..5).map(&:abs)
.select(&:even?)
.count
.to_s
在Ruby中,集合是对象,map
,count
和select
等方法是它们的方法。 map
会返回一个集合,您可以在其上调用select
。在select
的结果上,我们调用count
输出一个整数。然后我们在该整数上调用to_s
将其转换为字符串。 object.method
的点呼会在method
上调用object
。熟悉的东西。
在某些语言中,您可能会接触到一个可怕的事实,即对象的方法可能只是一个简单的函数,它将一个对象作用于和参数 。有些语言完全隐藏了这个事实作为实现细节,但这种约定实际上可能是一个限制吗?
obj.increase(obj, 5)
obj:increase(5)
它是在语言级别强制执行的约定:方法是将其所有者对象作为第一个参数的函数。
在一个类型中定义所有可能需要的方法已经被证明是不可能的,所以有些语言已经采用了#34;静态扩展&#34;它允许你创建一个函数,该对象采取&#34;对象作用于& #34;作为它的第一个参数(无法访问类型本身的来源!)。然后使用一些语法糖,您可以使用x.ext(y)
来代替ext(x, y)
:
x.f(y)
和f(x, y)
的语义相似性该功能允许我们以稍微清洁的方式将操作链接起来。
g(f(x, y), z) # Before
x.f(y).g(z) # After
我们又回来了!那么这与->
和->>
有什么关系呢?它们提供了类似外观的语法,在较长的链中看起来很好。
我们确信我们的map
和filter
(与Ruby select
相同)只是将其作为最后<< / strong>(哎呀,这是出乎意料的!)论点。
(->> (range -5 6) (map #(Math/abs %)) ; 6 not included
(filter even?)
(count)
(str))
哦,所以我们在这里看到了一些不同的方式:我们在这里视为方法的函数将它们作为 last 参数作用的对象。但我们只是做了#34;链#34;使用->>
并且这个事实变得不那么明显了。
如果我们的功能需要&#34;对象采取行动&#34;正如我们在Lua中看到的第一个论点,我们使用->
。根据您的链接功能,->
可能更有意义。
我们可以双管齐下,我们甚至可以将它们与更加冗长的as->
混合起来。