Вопрос о clojure пространствах имен и макросах - PullRequest
6 голосов
/ 25 февраля 2010

Предположим, у меня есть куча пространств имен (яблоко, банан, апельсин). В этих пространствах имен я использую макрос eat, который вызывает (не "генерирует", вызывает ) функцию peel. Функция peel различна для каждого фрукта, но макросы одинаковы и довольно большие, поэтому я хотел бы создать пространство имен fruit, содержащее макрос eat. Но когда я вызываю макрос eat из пространства имен apple, макрос eat должен вызывать функцию apple/peel.

Для иллюстрации (но это не работает):

(ns fruit)
(defmacro eat [] (peel))

(ns apple)
(defn peel [] (prn "peeled apple"))
(fruit/eat)

(ns banana)
(defn peel [] (prn "peeled banana"))
(fruit/eat)

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

(ns apple)
(defn peel [] (prn "peeled apple"))
(defmacro eat [] (peel))
(macroexpand-1 '(eat))

Итак, есть ли идеи о том, как сочетать макросы и полиморфизм?

Ответы [ 4 ]

7 голосов
/ 27 февраля 2010

То, что вы описываете, это не полиморфизм, а то, что называется локальный захват . Вы хотите, чтобы макрос eat «захватывал» локальное определение peel .

Это считается плохим стилем в большинстве Лиспов, особенно в Clojure, поскольку это может привести к тонким и непредсказуемым ошибкам.

Лучшее решение - передать правильный peel в макрос eat при вызове:

(ns fruit)
(defmacro eat [peeler] `(~peeler))

(ns apple)
(defn peel [] (prn "Peeled an apple"))
(fruit/eat peel)

Если вы действительно хотите сделать локальный захват, вы можете принудительно сделать это с помощью ~ '(кавычки) в макросе:

(ns fruit)
(defmacro eat [] `(~'peel))
3 голосов
/ 05 марта 2010

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

Это сложно, потому что макросы не оценивают свои аргументы. Даже если вы передадите pee в качестве аргумента eat , в теле макроса это просто символ, а не вызываемая функция.

Единственный способ сделать то, что вы хотите (без использования eval ), - разрешить символ во время компиляции:

(defmacro eat []
   ((var-get (resolve 'peel)))
   ... return the expansion of "eat" ...)

Функция resol принимает символ и возвращает переменную, с которой он сопоставлен, в текущем пространстве имен. Когда у вас есть Var, вы можете получить фактическую функцию (значение Var) с помощью var-get . Дополнительный набор скобок вызывает эту функцию.

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

2 голосов
/ 25 февраля 2010
(defmacro eat [] ((var-get (resolve 'peel))))

Обратите внимание, что вы злоупотребляете пространствами имен.

1 голос
/ 25 февраля 2010

РЕДАКТИРОВАТЬ: Извините.Я уже выложил следующее.Но вы говорите «вызывает, а не генерирует» функцию очистки.Поэтому то, что я написал, вероятно, не то, что вы хотите, хотя кажется, что оно даст желаемый результат.

Простое цитирование (peel) сработало для меня.

...