-> Clojure 中的运算符
Posted
技术标签:
【中文标题】-> Clojure 中的运算符【英文标题】:-> operator in Clojure 【发布时间】:2011-09-02 22:51:58 【问题描述】:Clojure 中的 -> 运算符(在 Clojure 中这个运算符是什么?)是否等同于 F# 中的管道运算符 |>?如果是这样,为什么需要这么复杂的宏定义,当(|>)只是定义为
let inline (|>) x f = f x
如果没有,Clojure 中是否存在 F# 的管道运算符,或者您将如何在 Clojure 中定义这样的运算符?
【问题讨论】:
【参考方案1】:不,它们不一样。 Clojure 并不真正需要 |>
,因为所有函数调用都包含在列表中,例如 (+ 1 2)
:没有什么魔法可以让 1 + 2
单独工作。1
->
用于减少嵌套和简化常见模式。例如:
(-> x (assoc :name "ted") (dissoc :size) (keys))
扩展到
(keys (dissoc (assoc x :name "ted") :size))
前者通常更容易阅读,因为从概念上讲,您正在对x
执行一系列操作;前者的代码是这样“塑造”的,而后者需要一些精神上的解开才能解决。
1 你可以编写一个宏来完成这项工作。这个想法是把你的宏包裹在你想要转换的整个源代码树周围,让它寻找|>
符号;然后它可以将源转换为您想要的形状。 Hiredman 使用他的 functional 包,使以非常类似于 Haskell 的方式编写代码成为可能。
【讨论】:
我明白了。看起来线程运算符在 Clojure 中很有用,只是因为 Clojure 语法的工作方式,但在 F# 中没有用处。管道运营商反之亦然。 Clojure in Action 在第 50 页和第 51 页上将这些宏的名称列为“线程优先”(->) 和“线程最后”(->> ) 宏,尽管官方文档似乎没有给出它们的实际名称。 @ShawnHolmes clojure.org/guides/threading_macros 使用术语“线程优先”和“线程最后”。 (该页面是 2012 年以来的新页面。)【参考方案2】:它被称为“线程”运算符。出于性能原因,它被写成宏而不是普通函数,因此它可以提供一个很好的语法 - 即它在编译时应用转换。
它比您描述的 |> 运算符更强大,因为它旨在通过多个函数传递一个值,其中每个连续值作为以下函数调用的第一个参数“插入”。这是一个有些人为的例子:
(-> [1]
(concat [2 3 4])
(sum)
((fn [x] (+ x 100.0))))
=> 110.0
如果你想定义一个与你描述的 F# 运算符完全一样的函数,你可以这样做:
(defn |> [x f] (f x))
(|> 3 inc)
=> 4
不确定它到底有多大用处,但无论如何你都在那里 :-)
最后,如果你想通过一系列函数传递一个值,你总是可以在 clojure 中执行如下操作:
(defn pipeline [x & fns]
((apply comp fns) x))
(pipeline 1 inc inc inc inc)
=> 5
【讨论】:
你为什么说->
是一个“出于性能原因”的宏?这是一个宏,因为作为一个函数它不起作用。
即使 "->" 是一个函数,也可以使用 (-> a #(...) #(...) #(...) ...) 获得类似的结果但使用起来会很慢而且有点难看:-)
(-> b (a c) quote)
如果不是宏,则永远无法评估为(quote (a b c))
,并且还有其他类似的情况。 #(...)
案例将涉及手动完成 ->
已经完成的所有工作;你只会把它变成另一个版本的comp
。
是的,我完全同意,如果不使用宏,您将无法获得完全相同的语法。我的意思是您可以获得相同的功能,尽管开销更大。无论哪种方式,我都已将句法点添加到答案中。【参考方案3】:
还值得注意的是,有一个->> macro 会将表单作为最后一个参数:
(->> a (+ 5) (let [a 5] ))
Clojure 的乐趣,第 8.1 章稍微讨论了这个主题。
【讨论】:
【参考方案4】:在阅读源代码时(尤其是说话时),我总是将->
运算符发音为“thread-first”,将->>
运算符发音为“thread-last”。
请记住,现在有一个运算符as->
,它比->
或->>.
更灵活,形式为:
(as-> val name (form1 arg1 name arg2)...)
值val
被评估并分配给占位符符号name
,用户可以将其放置在以下表格中的任何位置。我通常选择“它”作为占位符符号。我们可以像这样模仿线程优先->
:
user=> (-> :a
(vector 1))
[:a 1]
user=> (as-> :a it
(vector it 1) )
[:a 1]
我们可以像这样模仿 thread-last ->>
:
user=> (->> :a
(vector 2))
[2 :a]
user=> (as-> :a it
(vector 2 it) )
[2 :a]
或者,我们可以将它们组合成一个表达式:
user=> (as-> :a it
(vector it 1)
(vector 2 it))
[2 [:a 1]]
user=> (as-> :a it
(vector it 1)
(vector 2 it)
(vector "first" it "last"))
["first" [2 [:a 1]] "last"]
我经常使用最后一种形式,所以我在the Tupelo Library 中创建了一个新的运算符it->
:
(it-> 1
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." )
【讨论】:
以上是关于-> Clojure 中的运算符的主要内容,如果未能解决你的问题,请参考以下文章