LINUX.ORG.RU

[Lisp] туплю с defmacro

 


0

2

Есть код:


(defun get-action-name (list)
	(mapcar #'(lambda (def-list) (list (first def-list) (second def-list))) list))

(defmacro with-new-actions (parent actions-list
							&body body)
  (get-action-name actions-list))

(defparameter *workspace-actions-list* 
  '((aNew "&New" #'new-page-handler "tab- new" "Ctrl+N")
	(aOpen "&Open" #'open-page-handler "open" "Ctrl+O")))

(with-new-actions 'parent *workspace-actions-list*) ;; 1

(with-new-actions 'parent ((aNew "&New" #'new-page-handler "tab- new" "Ctrl+N")
	(aOpen "&Open" #'open-page-handler "open" "Ctrl+O"))) ;; 2

Проблема в том что строка (2) раскрывается, а строка (1) - нет.

Вопрос как раскрыть *workspace-actions-list* внутри defmacro ? Google молчит, вот туплю и мучаюсь.

★★★
Ответ на: комментарий от monk

Ну тут же недавно показывали, как использовать макросы в роли обычных функций. (apply (macro-function 'run-func) (cons name params))

anonymous
()
Ответ на: комментарий от anonymous

А в racket есть object expressions?

Это когда можно создавать объект с методами на-лету. Методы вычисляются. Буквально означает, что мы специально для этого объекта наследуем новый класс с переопределенными методами, а затем инстанцируемся. Такая штука есть с некоторыми ограничениями в F# и в более полном объеме в Scala.

let h1 = ...  // вычисляется
let h2 = ...  // вычисляется
let h  =
    { new IDisposable with
         member x.Dispose () =

             h1.Dispose ()
             h2.Dispose () }

Здесь создается объект h, реализующий интерфейс IDisposable. Все три значения h, h1, h2 вычисляются. Например, они могут быть вычислены внутри цикла.

dave ★★★★★
()
Ответ на: комментарий от anonymous

> Ну тут же недавно показывали, как использовать макросы в роли обычных функций. (apply (macro-function 'run-func) (cons name params))

Это естественно не сработает, потому что macro-function это совсем из другой оперы.

anonymous
()
Ответ на: комментарий от anonymous

>Зачем этот макрос, если он просто напросто делает

(apply (function name) params)

http://common-lisp.net/project/cffi/manual/html_node/foreign_002dfuncall.html...

Macro: foreign-funcall name-and-options &rest arguments ⇒ return-value

Хочу передать в printf достаточно длинный список чисел.

как использовать макросы в роли обычных функций. (apply (macro-function 'run-func) (cons name params))

И вернется исходник того, что надо выполнить. То есть опять (eval (apply (macro-function 'run-func) (cons name params)))

monk ★★★★★
()
Ответ на: комментарий от anonymous

> почему не сработает-то?

Ну потому что не сработает, как минимум потому что macro-function возвращает форму, которую нужно eval'ить.

anonymous
()
Ответ на: комментарий от monk

> Хочу передать в printf достаточно длинный список чисел.

Сейчас, подумаю :)

anonymous
()
Ответ на: комментарий от dave

Вопрос снимается. Можно.

(define (test)
  (for ([i '(1 2)])
    (for ([j '(3 4)])
      (let ([o (new (class object%
                      (define/public (m) (+ i j))
                      (super-new)))])
        (display (send o m))
        (newline)))))

---

> (test)
4
5
5
6
dave ★★★★★
()
Ответ на: комментарий от dave

> Это когда можно создавать объект с методами на-лету. Вообще-то можно, классы в Racket - объекты первого класса, как и функции, так что их можно создавать и модифицировать в рантайме. Попробовав, однако, написать конкретно твой пример, обнаружил баг - (define/public name body) не работает, если body - не лямбда-форма. Надо будет не забыть багрепорт кинуть, спасибо за наводку :)

anonymous
()
Ответ на: комментарий от anonymous

eval медленный

пока придумал такую фигню

(apply (intern (format nil «~a-~a» «RUN-» (length param))) (cons prog param))

+ куча функций

(defun run-1 (prog par1) (run-func prog par1))

(defun run-2 (prog par1 par2) (run-func prog par1 par2))

(defun run-3 (prog par1 par2 par3) (run-func prog par1 par2 par3))

....

надо ещё макрос-генератор придумать... и ощущение вырезания гландов через анус остается. Хотя в C++.Boost такие куски сплошь и рядом.

monk ★★★★★
()
Ответ на: комментарий от anonymous

>(defmacro apply-macro (macro args) `(,macro . ,(eval args)))

(defun test (x) (apply-macro mymacro x)) = ?

Вроде как он x захочет выполнить при компиляции

monk ★★★★★
()
Ответ на: комментарий от monk

Вроде как он x захочет выполнить при компиляции

Так если тебе не надо вычислять это во время компиляции, то зачем вообще макрос, если можно обойтись обычным apply?

anonymous
()
Ответ на: комментарий от monk
(apply (intern (format nil "~a-~a" "RUN-" (length param))) (cons prog param))

+ куча функций

(defun run-1 (prog par1) (run-func prog par1))

(defun run-2 (prog par1 par2) (run-func prog par1 par2))

(defun run-3 (prog par1 par2 par3) (run-func prog par1 par2 par3))

работает так же, как:

(apply run-func lst)

anonymous
()
Ответ на: комментарий от monk

> Вроде как он x захочет выполнить при компиляции

Конечно, потому что макросы раскрываются в compile-time. Макрос преобразует в compile-time одну структуру кода в другую, ничего более.

Тут 3 варианта - либо генерировать для каждого отдельного macros'a функции, либо вычислять в compile-time аргументы, либо раскрытие макроса вместе с аргументами в run-time.

Аналог apply fun args для макроса - это (macro . args), но args - должен быть известен на момент раскрытия, т.е. в compile-time.

Это еще одна причина, почему прежде, чем пользоваться макросами нужно думать 10 раз.

И я совершенно не вижу причин, зачем было делать foreign-funcall именно макросом, а не функцией.

anonymous
()
Ответ на: комментарий от anonymous

> И я совершенно не вижу причин, зачем было делать foreign-funcall именно макросом, а не функцией

Полностью согласен. Но что делать: такое API у CFFI. Особенно обидно, с учётом того, что внизу оно превращается в _функцию_ alien-funcall на sbcl.

Аналогично иногда хочется сделать что-то вроде

(defmacro get-range (a b) `(loop :for i :from a ,(if (< a b) :to :downto) b :collect i))

но в виде функции. И опять только eval. :-(

monk ★★★★★
()
Ответ на: комментарий от monk

Проблема в том, что с коммонлисповыми FFI ты не можешь сделать APPLY к сишной функции. Я имею ввиду, количество передаваемых в сишную функцию параметров должно быть известно во время компиляции.

По идее, то, что ты хочешь сделать, нельзя сделать даже в Си, без грубых хаков(без явных манипуляций стеком).

Такое можно разве что в Forth.

Особенно обидно, с учётом того, что внизу оно превращается в _функцию_ alien-funcall на sbcl.

Но тем не менее, alien-funcall требует alien-функцию(которую надо получать с помощью _макросов_ extern-alien, cast и т.п.). А у последних количество аргументов точно так же фиксировано.

anonymous
()
Ответ на: комментарий от anonymous

>И я совершенно не вижу причин, зачем было делать foreign-funcall именно макросом, а не функцией.

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

anonymous
()
Ответ на: комментарий от anonymous

Конкретно в данном случае можно использовать не printf, а vprintf(который вторым параметром принимает указатель на va_list) Но тут надо знать про выравнивание стека на конкретной платформе, как минимум.

Вот для x86 пример:

(let* ((string "Hello, world!")
       (length (length string)))
  (with-foreign-pointer (args (* length (foreign-type-size :uint32)))
    (dotimes (i length)
      (setf (mem-aref args :uint32 i)
            (char-code (aref string i))))
    (foreign-funcall "vprintf"
      :string (with-output-to-string (format)
                (dotimes (i length)
                  (write-string "%c" format)))
      :pointer args
      :int)))

;; Hello, world!
;; ==> 13

anonymous
()
Ответ на: комментарий от monk

Полностью согласен. Но что делать: такое API у CFFI. Особенно обидно, с учётом того, что внизу оно превращается в _функцию_ alien-funcall на sbcl.

Не функцию, а функцию от макроса - (alien-funcall (extern-alien ...)), extern-alien должен разобрать свои аргументы типов и вернуть код аллокации статического alien-а в куче, а alien-funcall компилируется в код вызова сторонней функции.

(in-package :sb-alien)

(alien-funcall (extern-alien "printf" (function int c-string)) "foo\n")
;=> 4

(alien-funcall (extern-alien "printf" (function int c-string int)) "foo %i\n" 5)
;=> 6

(alien-funcall (extern-alien "printf" (function int c-string (car '(int)))) "foo %i\n" 5)
;=> unknown alien type: (CAR '(INT))
quasimoto ★★★★
()
Ответ на: комментарий от monk

(defmacro get-range (a b) `(loop :for i :from a ,(if (< a b) :to :downto) b :collect i))

;;; Такого рода вещи - всегда макросы.
;;;
(defmacro range (a b &key up-p)
  `(loop :for i :from ,a ,(if up-p :to :downto) ,b :collect i))

;;; В таких случаях нужна функция и проверка в runtime, так как в общем случае
;;; a и b могут зависеть от I/O, например.
;;;
(defun get-range (a b)
  (if (< a b) (range a b :up-p t) (range a b :up-p nil)))

;;; Если нужно добавить макро-подобные правила раскрытия к функциям,
;;; то делается макрос компиляции, а не обычный макрос.
;;;
(define-compiler-macro get-range (&whole form a b)
  (if (and (numberp a) (numberp b))
      (get-range a b)
      form))

;;; Раскрыли на этапе компиляции:
;;;
(funcall (compiler-macro-function 'get-range) '(get-range 10 5) nil)
;=> (10 9 8 7 6 5)

;;; А тут будет выбрана та или иная ветвь в runtime, исходя из начения a.
;;; 
(funcall (compiler-macro-function 'get-range) '(get-range a 5) nil)
;=> (GET-RANGE A 5)
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Зачем тут макрос? И зачем явным образом направление указывать?

(defun range (a b &optional (step 1))
  (declare (type real a b step))
  (if (< a b)
    (loop :for x :from a :upto b :by step :collect x)
    (loop :for x :from a :downto b :by step :collect x)))

anonymous
()
Ответ на: комментарий от quasimoto

К тому же, результат compiler-макроса компилятор может посчитать за константу, что может сильно боком выйти, при попытке одну из cons-яйчеек полученного списка изменить.

anonymous
()
Ответ на: комментарий от anonymous

> Зачем тут макрос? И зачем явным образом направление указывать?

(if (< a b)

(loop :for x :from a :upto b :by step :collect x) (loop :for x :from a :downto b :by step :collect x))

В жизни часто попадаются куски такого рода, но намного больше (например главный цикл программы). С учетом того, что куски идентичные с точностью до :to/:downto, но при этом большие и требующие отладки, приходится вносить уйму синхронных правок.

Реально хочется возможности (loop :for x :from a (if (< a b) :upto :downto) b :by step :collect x), чтобы не копипастить руками.

А приходиться делать фигню типа (if (< a b) (template-macro :upto) (template-macro :downto)).

monk ★★★★★
()
Ответ на: комментарий от monk

>А приходиться делать фигню типа (if (< a b) (template-macro :upto) (template-macro :downto)).

Не надо так делать. Это плохой стиль и, вообще, идиотизм.

В крайнем случае, если не понравился пример, можно сделать так:

(defun range (a b &optional (step 1))
  (declare (type real a b step))
  (let* ((reverse (> a b))
         (min (if reverse b a))
         (max (if reverse a b))
         (list (loop :for x :from min :upto max :by step
                 :collect x)))
    (if reverse (nreverse list) list)))

Запомни: там, где можно использовать функцию, макрос использовать _крайне_ нежелательно.

anonymous
()
Ответ на: комментарий от anonymous

> там, где можно использовать функцию, макрос использовать _крайне_ нежелательно

где можно — да. А здесь для ухода от макроса добавлено 3 проверки условия + один возможный реверс списка при каждом выполнении. Аналогичная ситуация кстати с обобщенными функциями, где на входе может быть массив/список. И, соответственно, :in/:across. Тоже предложишь на входе принудительно делать копирование?

monk ★★★★★
()
Ответ на: комментарий от monk

>где можно — да. А здесь для ухода от макроса добавлено 3 проверки условия + один возможный реверс списка при каждом выполнении.
Ну да, первый пример был красивее.
Фишка в том, что, тем не менее, сделать это функцией мало того, что можно, так даже нужно(и, кстати, функцию можно будет использовать в функциях высшего порядка, в отличие от ...).

А вот что-нибудь наподобие define-constant из alexandria - нельзя. И как раз вот это и есть назначение макросов. Делать то, что функциями сделать нельзя.

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

Вот что Vladimir Sedach, известный русскоязычный лиспер, на этот счет говорит:

Lisp macros are not syntactic extensions! They are a full-fledged metaprogramming facility. Don't use macros to make new, stupider ways of writing for loops, use them to express domain concepts.


Тоже предложишь на входе принудительно делать копирование?

Не понял, какое копирование?

anonymous
()
Ответ на: комментарий от anonymous

> Не понял, какое копирование?

(defun foo (seq) (etypecase seq (list (loop :for i :in seq ....)) (array (loop :for i :across seq ....)))

Как избежать дублирования? В контексте предыдущего решения я предположил, что будет предложено что-то вроде

(loop :for i :in (coerce seq 'list) ....)

что подразумевает копирование всего массива.

Многие вещи можно сделать без макросов, но неэффективно.

monk ★★★★★
()
Ответ на: комментарий от anonymous

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

Мой вариант был

(defmacro template-macro (dir)
  `(loop :for i :from a ,dir b collect i))

(defun range (a b)
  (if (< a b) (template-macro :upto) (template-macro :downto)) 

или даже

(defun range (a b)
  (macrolet ((template-macro (dir)
  `(loop :for i :from a ,dir b collect i)))

  (if (< a b) (template-macro :upto) (template-macro :downto)) 

Почему так нельзя-то?

monk ★★★★★
()
Ответ на: комментарий от monk

Чёто я не врубаюсь, вроде тут всё должно работать прямо вот так: (loop :for i :from ,a ,(if up-p :to :downto) ,b :collect i) Правда, значения a и b должны быть известны во время компиляции. т.е., что-то типа

; код не запускался
(defmacro range (a b &key up-p &environment env )
  (assert (and (numberp a) (numberp b)))
  `(loop :for i :from ,a ,(if (< a b) :to :downto) ,b :collect i))

Если значения неизвестны, то так:

; код не запускался
(defmacro with-range (a b &body body)
   - тут ещё нужен макрос типа once-only для a и b и можно навести
   - гигену для step, с помощью with-gensyms
  `(iter 
    (with step = (sign (- ,b ,a))
    (for i from ,a to ,b step step) ; или как там step задаётся
    ,@body)))

И вообще, лучше пользоваться iterate, а особенно, iterate-keywords (хотя я его давно не обновлял).

den73 ★★★★★
()
Ответ на: комментарий от anonymous

Зачем тут макрос?

Можно и без макроса - просто пример. Главное что обратные кавычки, кавычки и подобные макросы чтения служат просто для конструирования списков и могут быть заменены на cons/list/list*/append/и т.п. Если их использоват в функции, то такая функция просто вернёт список (expression в данном случае), он может быть подставлен в макрос; если же использовать в макросе то этот список будет тем кодом который заменет при раскрытии этот макрос.

К тому же, результат compiler-макроса компилятор может посчитать за константу

Почему? (quote 10 9 8 7 6 5) - нормальный список, такой же как и результат функции get-range.

quasimoto ★★★★
()
Ответ на: комментарий от den73

Ну, как обычно, часть скобок я забыл, ну и ладно.

den73 ★★★★★
()
Ответ на: комментарий от anonymous

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

Да, Vladimir Sedach считает что макросы следует использовать только при построении особых DSL, а не на «каждый чих». Но есть и другие мнения. Если почитать код SBCL, то можно заметить что там часто любой рисунок повторяющийся три и более раз сразу делается макросом, иногда даже локальным macrolet обёртывается, по месту. В итоге, чтобы разобраться что происходит нужно сначало разобраться с макросами - там их достаточно много, сам язык (CL) оказывается расширенным. На самом деле можно писать ещё более лаконично, если использовать макросы мапящие куски кода в свои &rest куски кода.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

>любой рисунок повторяющийся три и более раз сразу делается макросом, >иногда даже локальным macrolet обёртывается, по месту.
Я тоже так делаю, хотя, последнее время прихожу к выводу, что если кусок маленький, то лучше сделать порог макротизации равным 10-20 применениям. Чем меньше кусок, тем выше должен быть порог.

den73 ★★★★★
()
Ответ на: комментарий от den73

1. В итоге всё-равно должен быть defun. Делать defmacro range — потом удивляться результату (range a b). Завязываться на данные при компиляции неочевидно. Если точно есть при компиляции, то уж лучше втыкать #.(if (< a b) :to :downto) — удивления меньше в случае чего.

2. (iter (for i from 10 to 1 by -1) (collect i)) => nil

Правильно (iter (for i from 10 downto 1) (collect i)) со всеми вытекающими последствиями (выбор ключевого слова внутри макроса по условию)

monk ★★★★★
()
Ответ на: комментарий от anonymous

А в чем проблема использовать в подобных случаях функции вместо макросов?

Разное назначение функций и макросов. Функции работают во время выполнения, они относятся к предметной области и делают те «дела» которые требуется делать программе. А макросы это средство оперировать кусками кода (т.е. ровно тем кодом который виден на экране, ничего от предметной области, только если мы не делаем полнофункциональный DSL), можно ими код рисовать. Опять же функции _будут_ работать по стратегии call-by-value на этапе выполнения, макросы _уже работают_ по стратегии call-by-macro-expansion во время компиляции.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

А a и b известны аж на этапе чтения, да :)

А в

(defmacro range (a b &key up-p &environment env )
  (assert (and (numberp a) (numberp b)))
  `(loop :for i :from ,a ,(if (< a b) :to :downto) ,b :collect i))

(range a b)
это подразумевается. И если вдруг их нет, то лучше увидеть это в короткой строке #.(), а не в большом макросе

monk ★★★★★
()
Ответ на: комментарий от monk
(defun range (a b)
  (if (< a b)
      (loop :for i :from a :to b collect i)
      (loop :for i :from a :downto b collect i)))

(define-compiler-macro range (&whole form a b)
  (if (and (numberp a) (numberp b))
      (range a b)
      form))

всё остальное от лукавого :)

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

> всё остальное от лукавого :)

А если вместо «collect i» 10 экранов кода, то копи-пастить? А если потом этот код надо изменить (добавить обработку нового условия или ещё что), то дважд писать одно и то же?

monk ★★★★★
()
Ответ на: комментарий от monk

А если вместо «collect i» 10 экранов кода, то копи-пастить? А если потом этот код надо изменить (добавить обработку нового условия или ещё что), то дважд писать одно и то же?

range это well-known функция типа (Integer -> Integer -> List), там нет 10 экранов кода. Если есть 10 экранов проблемного кода - то это уже что-то другое, тогда показывайте именно его. Скорее всего решается локальным macrolet, как и при всяких локальных повторах кода.

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Мой вариант и был с макролет

http://www.linux.org.ru/jump-message.jsp?msgid=5651533&cid=5684566

А анонимус утверждал, что макросы зло и надо делать всякие извращения с nreverse.

А в целом, моя идея была в том, что любой более-менее обобщённый код на базе loop приходится заворачивать в макрос, чтобы побороть выбор :to/:downto :in/:across, необязательные условия фильтра и прочие мелочи. А это неудобно, потому что условие выбора остается в функции, а применение в макросе...

monk ★★★★★
()
Ответ на: комментарий от monk

>А в целом, моя идея была в том, что любой более-менее обобщённый код на базе loop

далеко не любой, а, фактически, только с константными параметрами, что на много меньше _всех_ применений loop...

...приходится заворачивать в макрос, чтобы побороть выбор :to/:downto :in/:across, необязательные условия фильтра и прочие мелочи.


...и как правильно показали, если код «чистый», то «правильнее» вычислять его тогда в compile-time. А при «нечистой» обработке кода внутри loop вместо `collect i` нафигачь ещё один macrolet - и нет проблем

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


«нераспарсил»...

yyk ★★★★★
()
Ответ на: комментарий от yyk

А при «нечистой» обработке кода внутри loop вместо `collect i` нафигачь ещё один macrolet - и нет проблем

Э... можно вместо куска loop-выражения макрос вставить? Как???

 
(macrolet ((my-macro () '(collect i))) 
  (loop for i from 1 to 10 my-macro)) 

не работает. Или что имеется в виду?

неудобно, потому что условие выбора остается в функции, а применение в макросе

Глядя на

 
(defmacro template-macro (dir) 
  `(loop :for i :from a ,dir b collect i)) 

не очевиден текст loop, особенно, если кроме dir ещё пару ключевых слов также подменять.

Глядя на

 
(defun range (a b) 
  (if (< a b) (template-macro :upto) (template-macro :downto)) 
вообще сложно сказать, что делает функция

monk ★★★★★
()
Ответ на: комментарий от monk

Э... можно вместо куска loop-выражения макрос вставить? Как???

Использовать iterate - он более гомоиконичный.

Глядя на

Ещё раз предлагаю забыть про range - он как раз простой и пишется более менее понятно как (функция if (...) (loop ...) (loop ...) + макрос компиляции для пущей производительности). А говорить о том что могут быть более сложные случаи и не понятно как их написать можно только на примере этих более сложных примеров.

quasimoto ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.