#clojure
#clojure
Вопрос:
У меня есть куча функций, которые сопоставляются с некоторыми кодами и из них, определенными внешней системой:
(defn translate-from-ib-size-tick-field-code [val]
(condp = val
0 :bid-size
3 :ask-size
5 :last-size
8 :volume))
(defn translate-to-ib-size-tick-field-code [val]
(condp = val
:bid-size 0
:ask-size 3
:last-size 5
:volume 8))
Я хотел бы создать макрос для удаления дублирования:
#_ (translation-table size-tick-field-code
{:bid-size 0
:ask-size 3
:last-size 5
:volume 8})
Я запустил макрос следующим образом:
(defmacro translation-table [name amp; vals]
`(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get ~@vals val#)))
Результирующее тело функции кажется правильным, но имя функции неверно:
re-actor.conversions> (macroexpand `(translation-table monkey {:a 1 :b 2}))
(def translate-to-ib-re-actor.conversions/monkey
(.withMeta (clojure.core/fn translate-to-ib-re-actor.conversions/monkey
([val__10589__auto__]
(clojure.core/get {:a 1, :b 2} val__10589__auto__))) (.meta ...
Я бы хотел, чтобы «translate-to-ib-» отображался как часть имени функции, а не как префикс к пространству имен, как оказалось.
Как я могу это сделать с помощью макросов clojure? Если я просто делаю это неправильно и по какой-то причине не должен использовать макросы для этого, пожалуйста, дайте мне знать, но я также хотел бы знать, как создавать имена функций, подобные этому, чтобы просто улучшить мое понимание clojure и макросов в целом. Спасибо!
Ответ №1:
Проблема с макросом двоякая:
1) Вы используете обратную метку при цитировании переданной формы macroexpand
, в какое пространство имен определяет символы внутри:
`(translation-table monkey {:a 1 :b 2})
=> (foo.bar/translation-table foo.bar/monkey {:a 1, :b 2})
где foo.bar
находится любое пространство имен, в котором вы находитесь.
2) Вы создаете имя defn
элемента, используя символ name
, который, когда он указан в пространстве имен, будет преобразован в «foo.bar / monkey». Вот версия, которая будет работать:
(defmacro translation-table [tname amp; vals]
`(defn ~(symbol (str "translate-to-ib-" (name tname))) [val#]
(get ~@vals val#)))
Обратите внимание, что мы получаем имя tname
без части пространства имен, используя name
функцию.
Что касается того, является ли макрос правильным решением здесь, вероятно, нет 🙂 Для такого простого случая, как этот, я мог бы просто использовать maps:
(def translate-from-ib-size-tick-field-code
{0 :bid-size
3 :ask-size
5 :last-size
8 :volume})
;; swap keys amp; vals
(def translate-to-ib-size-tick-field-code
(zipmap (vals translate-from-ib-size-tick-field-code)
(keys translate-from-ib-size-tick-field-code)))
(translate-from-ib-size-tick-field-code 0)
=> :bid-size
(translate-to-ib-size-tick-field-code :bid-size)
=> 0
Если важна скорость, проверьте case
.
Комментарии:
1. Спасибо, это именно то, что я искал по обоим пунктам.
Ответ №2:
Некоторые нежелательные советы по другому вопросу: (get ~@vals val#)
крайне подозрительно. Ваш макрос утверждает, что принимает любое количество аргументов, но если он получит больше двух, он просто сделает что-то, что не имеет никакого смысла. Например,
(translation-table metric {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms})
помимо того, что это ужасная таблица перевода, расширяется до кода, который всегда выдает исключение:
(defn translate-to-ib-metric [val]
(get {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms}
val)))
get
конечно, не принимает так много аргументов, и в любом случае это не совсем то, что вы имели в виду. Самым простым решением было бы разрешить только два аргумента:
(defmacro translation-table [name vals]
(... (get ~vals val#)))
Но обратите внимание, что это означает, что сопоставление значений восстанавливается каждый раз при вызове функции — проблематично, если это дорого для вычисления или имеет побочные эффекты. Итак, если бы я писал это как макрос (хотя см. Ответ Джастина — зачем вам?), Я Бы сделал это как:
(defmacro translation-table [name vals]
`(let [vals# ~vals]
(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get vals# val#))))
Комментарии:
1. Спасибо! Я не хотел ставить там «amp;», и это в конечном итоге вызвало проблему и для меня, пока я не увидел ваш комментарий.