用于线程宏的 Clojure 自定义函数
Clojure custom function for threading macro
我有一张地图,我想编写一个自定义函数来更新它。
(-> {:a 1 :b 2}
(fn [x] (update x :a inc)))
这当然是一个简单的示例,无需围绕 update
的函数即可轻松完成,但它显示了我想要执行的操作。但这给了我以下错误。
Syntax error macroexpanding clojure.core/fn at (core.clj:108:1).
{:a 1, :b 2} - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
{:a 1, :b 2} - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n] spec: :clojure.core.specs.alpha/params+body
我不明白为什么这不起作用,因为线程宏应该但我的映射作为函数中的第一个参数,对吗?
您不调用函数本身,而是在没有第一个参数的情况下调用函数,对于您的示例,它将是:
> (-> {:a 1 :b 2}
(update :a inc))
{:a 2, :b 2}
在每种情况下展开宏更容易看到
> (macroexpand-1 '(-> {:a 1 :b 2} (update :a inc)))
(update {:a 1, :b 2} :a inc)
> (macroexpand-1 '(-> {:a 1 :b 2} (fn [x] (update x :a inc))))
(fn {:a 1, :b 2} [x] (update x :a inc))
您可以随时使用 macroexpand
查看发生了什么。在您的情况下,macroexpand 将 return 您:
(fn {:a 1, :b 2} [x] (update x :a inc))
显然这不是一个有效的函数。但是如果你这样调整它:
(-> {:a 1 :b 2}
(#(update % :a inc)))
展开后的表格将生效:
(#(update % :a inc) {:a 1, :b 2})
正如@jas 和@rmcv 所指出的,我给线程宏提供了函数本身,而不是不带参数的函数调用。所以简而言之,解决方案是
(-> {:a 1 :b 2}
((fn [x] (update x :a inc))))
我认为这些解决方案都不是最简单的。我建议选择以下之一:
一个。使用正常穿线形式:
(-> {:a 1, :b 2}
(update :a inc)) => {:a 2, :b 2}
大家看惯了,也很容易理解。由于您已经拒绝了这种方法,我假设您认为使用命名参数的代码更清晰。
乙。使用命名函数
(defn updater [x] (update x :a inc))
(-> {:a 1, :b 2}
updater) => {:a 2, :b 2}
(-> {:a 1, :b 2}
(updater)) => {:a 2, :b 2}
这更符合 ->
表单的设想工作方式。我认为第二个版本是最清晰的,因为它是最一致的,所有函数表达式都有括号(单参数或多参数)。
C。考虑使用来自 the Tupelo Library:
的 it->
宏
(it-> {:a 1, :b 2}
(update it :a inc)) => {:a 2, :b 2}
与命名函数非常相似,表达式是正常的 Clojure 形式,没有将 "invisible" 参数静默插入到 update
表达式中。代词 it
用作线程值的临时占位符(从 Groovy 复制的想法)。简单、明确且灵活,因为 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." )
我有一张地图,我想编写一个自定义函数来更新它。
(-> {:a 1 :b 2}
(fn [x] (update x :a inc)))
这当然是一个简单的示例,无需围绕 update
的函数即可轻松完成,但它显示了我想要执行的操作。但这给了我以下错误。
Syntax error macroexpanding clojure.core/fn at (core.clj:108:1).
{:a 1, :b 2} - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
{:a 1, :b 2} - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n] spec: :clojure.core.specs.alpha/params+body
我不明白为什么这不起作用,因为线程宏应该但我的映射作为函数中的第一个参数,对吗?
您不调用函数本身,而是在没有第一个参数的情况下调用函数,对于您的示例,它将是:
> (-> {:a 1 :b 2}
(update :a inc))
{:a 2, :b 2}
在每种情况下展开宏更容易看到
> (macroexpand-1 '(-> {:a 1 :b 2} (update :a inc)))
(update {:a 1, :b 2} :a inc)
> (macroexpand-1 '(-> {:a 1 :b 2} (fn [x] (update x :a inc))))
(fn {:a 1, :b 2} [x] (update x :a inc))
您可以随时使用 macroexpand
查看发生了什么。在您的情况下,macroexpand 将 return 您:
(fn {:a 1, :b 2} [x] (update x :a inc))
显然这不是一个有效的函数。但是如果你这样调整它:
(-> {:a 1 :b 2}
(#(update % :a inc)))
展开后的表格将生效:
(#(update % :a inc) {:a 1, :b 2})
正如@jas 和@rmcv 所指出的,我给线程宏提供了函数本身,而不是不带参数的函数调用。所以简而言之,解决方案是
(-> {:a 1 :b 2}
((fn [x] (update x :a inc))))
我认为这些解决方案都不是最简单的。我建议选择以下之一:
一个。使用正常穿线形式:
(-> {:a 1, :b 2}
(update :a inc)) => {:a 2, :b 2}
大家看惯了,也很容易理解。由于您已经拒绝了这种方法,我假设您认为使用命名参数的代码更清晰。
乙。使用命名函数
(defn updater [x] (update x :a inc))
(-> {:a 1, :b 2}
updater) => {:a 2, :b 2}
(-> {:a 1, :b 2}
(updater)) => {:a 2, :b 2}
这更符合 ->
表单的设想工作方式。我认为第二个版本是最清晰的,因为它是最一致的,所有函数表达式都有括号(单参数或多参数)。
C。考虑使用来自 the Tupelo Library:
的it->
宏
(it-> {:a 1, :b 2}
(update it :a inc)) => {:a 2, :b 2}
与命名函数非常相似,表达式是正常的 Clojure 形式,没有将 "invisible" 参数静默插入到 update
表达式中。代词 it
用作线程值的临时占位符(从 Groovy 复制的想法)。简单、明确且灵活,因为 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." )