Динамические вызовы методов в макросе Clojure?

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

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

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

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

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

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

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

10.11.2009 20:29:11
Вы действительно хотите позвонить dotoс классом в качестве первого аргумента? Вы будете делать что-то с самим объектом Class, а не с экземпляром этого класса.
Brian Carper 10.11.2009 22:02:47
Ах, спасибо - это была опечатка. Я исправил это сейчас.
npad 10.11.2009 23:18:00
4 ОТВЕТА
РЕШЕНИЕ

Хорошая вещь о макросах в том, что вам не нужно копаться в классах или что-то в этом роде. Вам просто нужно написать код, который генерирует правильные 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))

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

19
23.03.2011 20:46:03
Не работает Во-первых, вы не можете принять значение .такого.
Brian Carper 10.11.2009 21:58:38
исправлено, чтобы включить изменения Брайана Карпера
Arthur Ulfeldt 10.11.2009 23:59:47

Вы должны укусить пулю и использовать 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

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

2
10.11.2009 21:40:39
Макрос не обходит отражение при аренде не по духу. он генерирует s-выражения вслепую без учета того факта, что они происходят с ссылочными классами.
Arthur Ulfeldt 11.11.2009 01:48:30
Использование отражения для этого довольно подло. Макросы и рефлексия - это тяжелые инструменты, которые вы должны дважды подумать, прежде чем использовать, но если вы можете указать поля, которые должны быть установлены как литералы, то макросы кажутся намного лучше.
amalloy 23.03.2011 20:34:23

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

(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>
4
10.11.2009 22:20:34

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

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

(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"))
5
12.11.2009 08:15:42
Я не согласен. Использование listможет быть опасным, если вы не понимаете, что происходит, но если вы ограничиваетесь только использованием syntax-quote, гораздо легче потерять связь с тем, как работают макросы внутри, и полагать, что syntax-quoteэто единственный инструмент для построения макросов. Одна вещь, которую я бы посоветовал против (map (fn [[x y]] ...) coll); это более четко выражено с (for [[x y] coll] ...).
amalloy 23.03.2011 20:38:06
Да, я не использую достаточно часто. Однако я придерживаюсь своего мнения относительно списка. Легко сделать цитату против обратной ошибки. И это делается достаточно часто в дикой природе. Даже в clojure.core и в сообщении людей, которые точно знают, как работают макросы. Синтаксическая кавычка просто устраняет целый класс ошибок. Всегда лучше, чем исправление после свершившегося факта.
kotarak 30.03.2011 06:51:57
Хорошо. "класс ошибок", может быть, немного много. Но это делает захват символов явным, а не случайным.
kotarak 30.03.2011 07:47:37
Вы можете достаточно легко использовать как: (list `first some-arg).
amalloy 30.03.2011 16:46:35
Да. И это (list 'first some-arg)вместо того, чтобы печатать , но вряд ли (~'first ~some-arg)вместо (first ~some-arg). Что еще короче в этом случае. Если вам это нравится, используйте listподход. Но я бы посоветовал против этого в 95% случаев. (Представьте себе галочки в нужных местах. Я не получаю форматирование комментариев.)
kotarak 30.03.2011 19:13:24