2009-11-10 3 views
14

Я пытаюсь написать макрос, который вызовет java setter методы, основанные на аргументах, данных ему.Динамический вызов метода в макросе Clojure?

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

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

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

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

Как бы вы рекомендовать решать это?

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

+0

Вы действительно хотите назвать 'doto' с классом в качестве первого аргумента? Вы собираетесь делать вещи самому объекту класса, а не экземпляру этого класса. –

+0

Ах, спасибо - это была опечатка. Я исправил это сейчас. – npad

ответ

20

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

Первая функция для генерации S-выражение как (.setName 42)

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

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

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

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

+0

Не работает. Во-первых, вы не можете использовать значение '.'. –

+0

исправлено, чтобы включить изменения Брайана Карпера –

2

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

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

+0

Макрос не обходит отражение в аренде не по духу. он генерирует s-выражения вслепую, не учитывая тот факт, что они встречаются с ссылочными классами. –

+0

Использование отражения для этого довольно мерзкое. Макросы и рефлексия - это тяжелые инструменты, которые вы должны дважды подумать перед использованием, но если вы можете указать поля для установки в качестве литералов, макросы выглядят намного лучше. – amalloy

4

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

(defmacro set-all [obj m] 
    `(doto ~obj [email protected](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> 
+0

ok, i undeleated it. –

5

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

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

(defmacro configure 
    [object options] 
    `(doto ~object 
    [email protected](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"))
+0

Я не согласен. Использование 'list' может быть опасным, если вы не понимаете, что происходит, но если вы ограничиваете себя использованием только синтаксиса-цитаты, гораздо легче потерять связь с тем, как макросы работают внутри, и считают, что' синтаксис-цитата 'является единственным инструментом построения макросов. Одна вещь, которую я * * советовал бы против is '(map (fn [[x y]] ...) coll)'; это более чисто выражено с помощью '(для [[x y] coll] ...)'. – amalloy

+0

Да, я не использую достаточно часто. Однако я придерживаюсь своего мнения относительно списка. Легко сделать цитату против ошибки обратного хода. И это делается достаточно часто в дикой природе. Даже в clojure.core и contrib от людей, которые точно знают, как работают макросы. синтаксис-цитата просто исключает целый класс ошибок. Всегда лучше, чем фиксировать после факта. – kotarak

+0

Хорошо. «класс ошибок», возможно, немного. Но вместо случайности он делает захват символов явным. – kotarak