Пользовательская функция Clojure для макропотоков - PullRequest
0 голосов
/ 02 февраля 2020

У меня есть карта, и я хочу написать пользовательскую функцию для ее обновления.

(-> {: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

Я не понимаю, почему это не работает, так как макрос многопоточности должен только отображать мою карту в качестве первого параметра в функции, верно?

Ответы [ 4 ]

4 голосов
/ 02 февраля 2020

Вы всегда можете использовать macroexpand, чтобы увидеть, что произошло. В вашем случае macroexpand вернет вам:

(fn {:a 1, :b 2} [x] (update x :a inc))

, очевидно, это недопустимая функция. Но если вы настроите его следующим образом:

(-> {:a 1 :b 2}
    (#(update % :a inc)))

расширенная форма станет действительной:

(#(update % :a inc) {:a 1, :b 2})
2 голосов
/ 02 февраля 2020

Вы не помещаете саму функцию для вызова, но вызываете функцию без первого параметра. Для вашего примера это будет:

> (-> {: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))
1 голос
/ 02 февраля 2020

Я не думаю, что какое-либо из этих решений является самым простым. Я бы предложил выбрать один из следующих вариантов:


A. Используйте обычную форму потоков:

(-> {:a 1, :b 2} 
  (update :a inc))   => {:a 2, :b 2}

Все привыкли видеть это и могут легко это понять. Поскольку вы уже отказались от этого подхода, я полагаю, что вы думаете, что код понятнее с использованием именованного параметра.


B. Используйте именованную функцию

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

Это больше, чем предполагалось работать с формой ->. Я думаю, что 2-я версия является самой ясной, поскольку она наиболее последовательна, когда все выражения функций имеют круглые скобки (один аргумент или несколько аргументов).


C. Подумайте об использовании макроса it-> из библиотеки Tupelo :

(it-> {:a 1, :b 2} 
      (update it :a inc))   => {:a 2, :b 2}

Как и в случае с именованной функцией, выражение представляет собой обычную форму Clojure без параметра «невидимый», который вставляется в * без вывода сообщений. 1023 * выражение. Местоимение 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." )
1 голос
/ 02 февраля 2020

Как указали @jas и @rmcv, я давал макросу потоков саму функцию, а не вызов функции без аргумента. Таким образом, в краткосрочной перспективе решение будет

(-> {:a 1 :b 2}
    ((fn [x] (update x :a inc))))
...