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 молчит, вот туплю и мучаюсь.

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

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

 (defmacro mymac () '(collect i))

(iter (for i from 1 to 10) mymac)

=> PROGN: variable MYMAC has no value

(setq mymac '(collect i))

(iter (for i from 1 to 10) mymac) => NIL 

Что я деляю не так?

говорить о том что могут быть более сложные случаи и не понятно как их написать можно только на примере этих более сложных примеров

 
(defmacro template-macro (in values) 
  `(loop 
    :for key :in keys 
    :for value ,in ,values 
    :do (with-g-value 
             (:value value 
              :g-type (property-type g-object key)) 
      (g-object-set-property g-object 
                (string-downcase key) g-value))))

(defmethod (setf property) (values (g-object g-object) &rest keys) 
     "Usage: (setf (property object :property) value) 
             (setf (property object :prop1 :prop2) (list value1 value2)) 
             (setf (property object :prop1 :prop2) #(value1 value2))" 

(etypecase values 
   (list (template-macro :in values)) 
   (array (template-macro :across values)) 
   (t (template-macro :across (list values))))) 

Твои предложения по рефакторингу?

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

В конце etypecase разумеется (t (template-macro in (list values)))))

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

так чтоли?

(define-syntax (etypecase stx)
  (define-syntax-class values
    [pattern (x ...)
             #:attr type #`:in
             #:attr val #`(x ...)]
    [pattern #(x ...)
             #:attr type #`:across
             #:attr val #`#(x ...)]
    [pattern y
             #:attr type #`in
             #:attr val #`(list y)])
  
  (syntax-parse stx
                [(_ x:values) 
                 #`(loop :for key :in keys 
                         :for value x.type x.val
                         ........)]))

(etypecase `(1 2 3)) -> (... :for value :in `(1 2 3) ...)
(etypecase #(4 5 6)) -> (... :for value :across #(4 5 6))
(etypecase x)        -> (... :for value :in (list x))

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

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

в макросе вестимо... =)

Глядя на...


Я всё больше теряю суть твоих претензий... И ежели твои макросы не сугубо личное дело (а-ля macrolet, да и то это не служит оправданием), код принято документировать. По самому коду догадываться о том что он делает - удел отладки/модификации.

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

> так чтоли?

Ну это мой же вариант с template-macros, только на scheme. Про который сказали «так делать нельзя, потому как можно заменить на функцию»

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

> (defmacro mac-loop (chunk) `(loop :for i ,@chunk collect i)

я понял так, что требовалось, чтобы chunk было макросом. Значит неправильно понял.

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

И ежели твои макросы не сугубо личное дело (а-ля macrolet, да и то это не служит оправданием), код принято документировать

Берем cffi-clisp.lisp:

#-#.(cffi-sys::post-2.45-ffi-interface-p)
(defun %foreign-funcall-aux (name type library)
  `(ffi::foreign-library-function
    ,name ,library nil
    #+#.(cffi-sys::post-2.40-ffi-interface-p)
    nil
    ,type))

(defmacro %foreign-funcall (name args &key library convention)
  "Invoke a foreign function called NAME, taking pairs of
foreign-type/value pairs from ARGS.  If a single element is left
over at the end of ARGS, it specifies the foreign return type of
the function call."
  (multiple-value-bind (types fargs rettype)
      (parse-foreign-funcall-args args)
    (let* ((fn (%foreign-funcall-aux
                name
                `(ffi:parse-c-type
                  ',(c-function-type types rettype convention))
                (if (eq library :default)
                    :default
                    (library-handle-form library))))
          (form `(funcall
                  (load-time-value
                   (handler-case ,fn
                     (error (err)
                       (warn "~A" err))))
                  ,@fargs)))
      (if (eq rettype 'ffi:c-pointer)
          `(or ,form (null-pointer))
          form))))

Это читабельно?

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

> требовалось, чтобы chunk было макросом

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

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

> так чтоли?

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

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

> Да, вполне себе читаемо.

То есть, ты сможешь сказать, во что превратится тривиальное

(%foreign-funcall «printf» (:string «ok» :void))

?

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

(%foreign-funcall «printf» (:string «ok» :void))

С-с С-m тебе в помощь.

Вот например,

(cffi::%foreign-funcall "printf" (:int 5 :void))

Макроэкспандится в

(ALIEN-FUNCALL (EXTERN-ALIEN "printf" (FUNCTION VOID INT)) 5)
anonymous
()
Ответ на: комментарий от anonymous

> Макроэкспандится в

Уверен? Тогда прочитай тот «читабельный» код и скажи из какого куска появляется alien-funcall?

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

> (iter (for i from 1 to 10) (mymac))

Ну хоть что-то. Тогда iter реально рулит тем, что не надо весь цикл целиком в шаблон загонять. Спасибо.

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

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

в cl нельзя делать частичный экспанд? Типа (expand form list-of-terminals)? А если луп в макролет закинуть, то он тоже будет раскрываться до макросов, объявленных в макролете?

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

M - . на %foreign-funcall он перебрасывает к определению функциии.

[code=lisp] (defmacro %%foreign-funcall (name types fargs rettype) «Internal guts of %FOREIGN-FUNCALL.» `(alien-funcall (extern-alien ,name (function ,rettype ,@types)) ,@fargs))

(defmacro %foreign-funcall (name args &key library convention) «Perform a foreign function call, document it more later.» (declare (ignore library convention)) (multiple-value-bind (types fargs rettype) (foreign-funcall-type-and-args args) `(%%foreign-funcall ,name ,types ,fargs ,rettype))) [/code]

А там все тривиально. У меня просто несколько другая версия cffi, судя по всему. Но твой пример все-равно читаемый.

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

M - . на %foreign-funcall он перебрасывает к определению функциии.

(defmacro %%foreign-funcall (name types fargs rettype)
  "Internal guts of %FOREIGN-FUNCALL."
  `(alien-funcall
    (extern-alien ,name (function ,rettype ,@types))
    ,@fargs))

(defmacro %foreign-funcall (name args &key library convention)
  "Perform a foreign function call, document it more later."
  (declare (ignore library convention))
  (multiple-value-bind (types fargs rettype)
      (foreign-funcall-type-and-args args)
    `(%%foreign-funcall ,name ,types ,fargs ,rettype)))

А там все тривиально. У меня просто несколько другая версия cffi, судя по всему. Но твой пример все-равно читаемый.

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

> если луп в макролет закинуть...

... то он обработается макролетом как список, а потом, если то, что вернёт макролет останется лупом, будет уже работать луп.

Но твой пример все-равно читаемый.

..., но за разумное время распарсить вручную я его не могу. Так?

У тебя несколько другая версия лиспа (sbcl). А функция из cffi-clisp.lisp.

Кстати, вот ещё одна причина, почему не всегда достаточно M-. Там ещё условная компиляция (#-, #+) есть для clisp. Ты когда программу в исходниках на C получаешь, тоже, чтоб понять как работает, запускаешь и всё? И если смог прочитать с экрана результат, то считаем программу читабельной?

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

Что я деляю не так?

Во-первых, как уже сказали:

(defmacro mymac () '(collect i))
(iter (for i from 1 to 10) (mymac))

во-вторых, I в MYMAC берётся из внешнего контекста - самый укуренный макростиль (ака анафоричность без тормозов).

Твои предложения по рефакторингу?

На первый взгляд, можно сделать так:

(defmacro with-g-keys-and-values (g-object (key keys) (value in values) &body body)
  "Iterate over G-OBJECT's KEYs and VALUEs from the KEYS list and the VALUES sequence."
  `(loop :for ,key :in ,keys
         :for ,value ,in ,values
         :do (with-g-value (:value ,value
                            :g-type (property-type ,g-object ,key))
                ,@body)))

(defmethod (setf property) ((values list) (g-object g-object) &rest keys)
  (with-g-keys-and-values g-object (key keys) (value :in values)
    (g-object-set-property g-object (string-downcase key) value)))

(defmethod (setf property) ((values array) (g-object g-object) &rest keys)
  (with-g-keys-and-values g-object (key keys) (value :across values)
    (g-object-set-property g-object (string-downcase key) value)))

(defmethod (setf property) ((values t) (g-object g-object) &rest keys)
  (with-g-keys-and-values g-object (key keys) (value :in (list values))
    (g-object-set-property g-object (string-downcase key) value)))

with-g-keys-and-values хорош тем, что имеет нормальное говорящее название, более менее общий (можно в разных местах использовать) и не выхватывает переменных из контекста (замкнутый макрос). В setf методе используется диспетчеризация по первому аргументу (почти мультиметоды).

Но вообще, я не понимаю почему values не может быть просто списком?

(defmacro with-g-keys-and-values (g-object (key keys) (value values) &body body)
  "Iterate over G-OBJECT's KEYs and VALUEs from the KEYS list and the VALUES list."
  `(loop :for ,key :in ,keys
         :for ,value :in ,values
         :do (with-g-value (:value ,value
                            :g-type (property-type ,g-object ,key))
                ,@body)))

(defmethod (setf property) ((values list) (g-object g-object) &rest keys)
  (with-g-keys-and-values g-object (key keys) (value values)
    (g-object-set-property g-object (string-downcase key) value)))

а если такая ситуация возникает, что не может, то можно ещё так:

(defmacro with-g-keys-and-values (g-object (key keys) (value in values) &body body)
  "Iterate over G-OBJECT's KEYs and VALUEs from the KEYS list and the VALUES sequence."
  `(loop :for ,key :in ,keys
         :for ,value ,in ,values
         :do (with-g-value (:value ,value
                            :g-type (property-type ,g-object ,key))
                ,@body)))

(macrolet ((frob-setf-method (type in &optional values-prefix)
             `(defmethod (setf property) ((values ,type) (g-object g-object) &rest keys)
                (with-g-keys-and-values g-object
                    (key keys) (value ,in ,(if values-prefix `(,values-prefix values) 'values))
                  (g-object-set-property g-object (string-downcase key) value)))))
  (frob-setf-method list :in)
  (frob-setf-method array :across)
  (frob-setf-method t :in list))

если frob-setf-method довольно длинный (вот таких кусков в SBCL много).

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

> with-g-keys-and-values хорош тем, что имеет нормальное говорящее название, более менее общий (можно в разных местах использовать) и не выхватывает переменных из контекста (замкнутый макрос)

С моей точки зрения, для ad-hoc макроса (в других местах он не нужен) это всё скорей минусы. Говорящее название предлагает его использовать в других местах, а я как автор предполагаю, что использоваться он будет только в setf property и, поменяв что-нибудь в API (например, решив, что values должен иметь формат typename . sequence) спокойно меняю with-g-keys-and-values, думая что он нигде больше не используется, и ломаю кому-нибудь код. Можно конечно %with-g-keys-and-values, но по мне, уж лучше setf-property-template%. Анафоричность аналогично: у меня макрос = шаблон конкретной функции (в нескольких вариантах), зачем заставлять повторять (key keys) ... value непонятно.

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

Твои предложения по рефакторингу?

(defmethod (setf property) (values (g-object g-object) &rest keys)
  (flet ((set-properties (value key)
           (with-g-value (:value value :g-type (property-type g-object key))
             (g-object-set-property g-object (string-downcase key) g-value))))
    (if (atom values)
      (set-properties values (car keys))
      (map nil #'set-properties values keys)

или, лучше

(flet ((set-properties (value key)
         (with-g-value (:value value :g-type (property-type g-object key))
           (g-object-set-property g-object (string-downcase key) g-value))))
  (defmethod (setf property) ((values sequence) (g-object g-object) &rest keys)
    (map nil #'set-properties values keys))
  (defmethod (setf property) (values (g-object g-object) &rest keys)
    (set-properties values (car keys))))
anonymous
()
Ответ на: комментарий от anonymous

Криво назвал функцию, лучше set-property, и вместо values --- value в последнем методе, но мысль, я полагаю, ясна.

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

> (map nil #'set-properties

Не-не-не... это читерство. Хотя рабочий вариант я почти так и написан, только с if listp, про мультиметоды как-то не подумал.

Смысл в том, что loop, получается почти нигде использовать нельзя. Кроме однострочников с точно известными типами.

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

> Смысл в том, что loop, получается почти нигде использовать нельзя. Кроме однострочников с точно известными типами.

Нельзя. И что? Обычный криво спроектированный макрос, по недоразумению попавший в стандарт и от которого теперь непросто избавиться. Возьми iterate. Или series. Или map. Или используюй рекурсию. А лучше --- научись использовать всё сразу.

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

Лучше не писать макроса, чем писать. В данном случае, если нет потребности в этой итерации по ключам и значениям - то нефиг её вводить. values может быть списком, соответственно, нужен только _один_ setf метод и _одно_ использование loop.

Анафоричность аналогично: у меня макрос = шаблон конкретной функции (в нескольких вариантах), зачем заставлять повторять (key keys) ... value непонятно.

Тебе всё равно будут об этом постоянно говорить (а в рассылках будут молчать :)), потому что так не пишут (может быть, гипотетически, иногда это удобно, в особых случаях кодогенерации). Это связано с тем, что макросы обычно замкнуты и представляют собой законченную по функциональности особую форму, т.е. часть API. Исходя из принципа «лучше их не писать, чем писать» мелкие макросы-шаблоны с незамкнутыми переменными не возникают. За редким исключением - не видел таких макросов, которые бы требовали контекста именно переменных для своего раскрытия.

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

> Обычный криво спроектированный макрос, по недоразумению попавший в стандарт

Вот за это common lisp и ругают в основном. Есть немножко странных вещей в стандартах. Причём если loop ещё полбеды, то, например, требование для defconstant при повторном чтении быть eql, ужасная многословность defclass, отсутствие рефлексии в defstruct, elt и nth имеют аргументы в разном порядке... На всё есть костыли, но дальше другой разброд и шатание: defstar vs def, rdbms vs clsql, computed-class vs cells. В общем, очередной KDE vs Gnome, но с учётом количества людей в каждой нише, получается много-много индивидуальных лиспов. Очень надеюсь, может хоть dwim какую-то пользовательскую базу наберёт, а то совсем грустно.

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

> так не пишут ...

мелкие макросы-шаблоны с незамкнутыми переменными не возникают

К macrolet то тоже относится?

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

> может хоть dwim какую-то пользовательскую базу наберёт

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

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

ибо это просто ужасно, что они там делают


...и это тоже...

Можно ли собрать хотя бы 20 человек, который согласились бы писать на CL в одном стиле?

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

> Лучше бы этому проекту загнуться и побыстрей

Предложи замены hu.dwim.perec и hu.dwim.rdbms

CLSQL работает в два раза медленней.

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

>Можно ли собрать хотя бы 20 человек, который согласились бы писать на CL в одном стиле?

_согласились_ - легко! «Я начальник - ты ...» А вот _выработали_ бы единый стандарт - тяжело. Скорее всего без компромиссов не обошлось бы... =)

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

> Скорее всего без компромиссов не обошлось бы... =)

Скорее всего не обошлось бы без членовредительства :)

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

К macrolet то тоже относится?

Конечно, grep -rn «macrolet» исходники любого крупного проекта - нигде такого не встречается. Например, в SBCL множество macrolet, но все они имеют такой вид:

(macrolet ((foo (...)
             ...))
  (foo ...)
  (foo ...)
  ...)

Т.е. foo раскрывается в верхнем контексте, следовательно просто не может иметь свободных переменных. Сама возможность есть - одни макросы могут иметь свободные переменные в теле, а другие могут их связывать, тогда макросы первого типа можно будет использовать только внутри макросов второго типа, но это неудобно отлаживать, проще добавить и туда и туда параметр. И потом - такие макросы сами по себе не возникают.

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

> CLSQL работает в два раза медленней.

Тебе обязательно нужен orm? Для postgresql есть вполне вменяемый postmodern. А dwim.hu за их издевательства над CL нужно убить - то, что они делают не просто бесполезно, это вредно.

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

..., но за разумное время распарсить вручную я его не могу. Так?

Почему? Там действительн все просто - т.е. я смотрю на код и вижу, что там и как делается. Никаких сверхусилий не прилагаю. Рассказывать построчно - долго и скучно. Если есть какие-то конкретные вопросы - задавай.

Это вам не код с трансформерами монад, тут действуют элементарные правила раскрытия макросов.

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

Например, в SBCL множество macrolet, но все они имеют такой вид

Есть ещё внутренние macrolet, там так:

(defun foo (a b c)
  (macrolet ((bar (a b c)
               ...))
    (bar a b c)))

Т.е. параметры используются явно. Есть ещё несколько мест где они используются неявно, но там стилевые проблемы. И ещё несколько мест с FROB макролетами, где неявные параметры имеют смысл, но таких мест единицы от общего числа.

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

Тебе обязательно нужен orm? Для postgresql есть вполне вменяемый postmodern.

perec/rdbms для postgreSQL через него и работает, вот только помимо него так же успешно работает с SQLite и Oracle.

И для postmodern есть удобный аналог чего-то вроде

[insert tab ,columns ,values]
где columns и values — переменные-списки, содержащие колонки и их значения сооветственно?

ORM тоже было бы неплохо, причём perec позволяет делать вещи типа

(cl-perec:select-instances (r tps-report)
    (cl-perec:where (pathname-match-p
                      (report-path-of r)
                      #p"/share/tps-reports/*"))
хотя, в принципе, любой вменяемый ORM-альтернатива подойдёт.

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