Динамические вызовы методов в макросе Clojure? - PullRequest
14 голосов
/ 10 ноября 2009

Я пытаюсь написать макрос, который будет вызывать методы установки Java на основе переданных ему аргументов.

Так, например:

(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})

может расшириться до следующего:

(doto (new MyClass)
  (.setUsername "fred")
  (.setPassword "wilma"))

Как бы вы порекомендовали заняться этим?

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

Ответы [ 4 ]

20 голосов
/ 11 ноября 2009

Хорошая вещь в макросах - вам не нужно копаться в классах или чем-то в этом роде. Вам просто нужно написать код, который генерирует правильные s-выражения .

Сначала функция для генерации s-выражения типа (.setName 42)

(defn make-call [name val]
  (list (symbol (str ".set" name) val)))

затем макрос для генерации выражений и вставьте (~ @) их в doto выражение.

(defmacro map-set [class things]
  `(doto ~class ~@(map make-call things))

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

5 голосов
/ 12 ноября 2009

Пожалуйста, не создавайте s-выражения с list для макросов. Это серьезно повредит гигиене макроса. Очень легко сделать ошибку, которую трудно отследить. Пожалуйста, всегда используйте синтаксис-цитата! Хотя в данном случае это не проблема, полезно привыкнуть использовать только синтаксическую кавычку!

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

(defmacro configure
  [object options]
  `(doto ~object
     ~@(map (fn [[property value]]
              (let [property (name property)
                    setter   (str ".set"
                                  (.toUpperCase (subs property 0 1))
                                  (subs property 1))]
                `(~(symbol setter) ~value)))
            options)))

Это может быть использовано как:

user=> (macroexpand-1 '(configure (MyClass.) {:username "fred" :password "wilma"}))
(clojure.core/doto (MyClass.) (.setUsername "fred") (.setPassword "wilma"))
4 голосов
/ 11 ноября 2009

Кто-то (я полагаю, Артур Ульфельдт) опубликовал ответ, который был почти верным, но теперь он удален Пожалуйста, примите это вместо моего, если он отправит снова. (Или примите pmf's.) Это рабочая версия:

(defmacro set-all [obj m]
  `(doto ~obj ~@(map (fn [[k v]]
                       (list (symbol (str ".set" k)) v))
                     m)))

user> (macroexpand-1 '(set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009}))
(clojure.core/doto (java.util.Date.) (.setMonth 0) (.setDate 1) (.setYear 2009))

user> (set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009})
#<Date Fri Jan 01 14:15:51 PST 3909>
2 голосов
/ 11 ноября 2009

Вы должны укусить пулю и использовать clojure.lang.Reflector/invokeInstanceMethod так:

(defn do-stuff [obj m]
  (doseq [[k v] m]
    (let [method-name (str "set" k)]
      (clojure.lang.Reflector/invokeInstanceMethod
        obj
        method-name
        (into-array Object [v]))))
   obj)

(do-stuff (java.util.Date.) {"Month" 2}) ; use it

Нет необходимости в макросе (насколько я знаю, макрос также не позволяет обойти отражение; по крайней мере, для общего случая).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...