Clojure努力想象箭头宏如何工作

时间:2015-09-04 10:45:09

标签: clojure

我很难理解->->>如何扩展。

如果我有以下内容:

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

展开时这两个宏看起来如何?

4 个答案:

答案 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 libraryit->宏的工作原理:

想象一下,使用变量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; 。它本质上是:

  • 调用方法
  • 在结果上调用方法
  • 调用该方法的方法&#39; s

Ruby是面向对象的,这样的链看起来像这样:

(-5..5).map(&:abs)
       .select(&:even?)
       .count
       .to_s
  • 取-5到5之间的整数范围
  • 将它们映射到绝对值
  • 只选择那些甚至
  • 的那些
  • 算你有多少
  • 将结果数字转换为字符串

在Ruby中,集合是对象,mapcountselect等方法是它们的方法。 map会返回一个集合,您可以在其上调用select。在select的结果上,我们调用count输出一个整数。然后我们在该整数上调用to_s将其转换为字符串。 object.method的点呼会在method上调用object。熟悉的东西。

在某些语言中,您可能会接触到一个可怕的事实,即对象的方法可能只是一个简单的函数,它将一个对象作用于参数 。有些语言完全隐藏了这个事实作为实现细节,但这种约定实际上可能是一个限制吗?

的Lua

Lua是一个很好的小而诚实的语言,不会躲避你对象的方法是什么。考虑这两个等价的行:

obj.increase(obj, 5)
obj:increase(5)

它是在语言级别强制执行的约定:方法是将其所有者对象作为第一个参数的函数。

...以及其他一些&#34;静态扩展&#34;

在一个类型中定义所有可能需要的方法已经被证明是不可能的,所以有些语言已经采用了#34;静态扩展&#34;它允许你创建一个函数,该对象采取&#34;对象作用于& #34;作为它的第一个参数(无法访问类型本身的来源!)。然后使用一些语法糖,您可以使用x.ext(y)来代替ext(x, y)

该功能允许我们以稍微清洁的方式将操作链接起来。

g(f(x, y), z) # Before
x.f(y).g(z)   # After

Clojure的

我们又回来了!那么这与->->>有什么关系呢?它们提供了类似外观的语法,在较长的链中看起来很好。

我们确信我们的mapfilter(与Ruby select相同)只是将其作为最后<< / strong>(哎呀,这是出乎意料的!)论点。

(->> (range -5 6) (map #(Math/abs %)) ; 6 not included
                  (filter even?)
                  (count)
                  (str))

哦,所以我们在这里看到了一些不同的方式:我们在这里视为方法的函数将它们作为 last 参数作用的对象。但我们只是做了#34;链#34;使用->>并且这个事实变得不那么明显了。

如果我们的功能需要&#34;对象采取行动&#34;正如我们在Lua中看到的第一个论点,我们使用->。根据您的链接功能,->可能更有意义。

我们可以双管齐下,我们甚至可以将它们与更加冗长的as->混合起来。