Создание имени функции, определяемого пространством имен, в макросе

#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;», и это в конечном итоге вызвало проблему и для меня, пока я не увидел ваш комментарий.