用于线程宏的 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." )