2015-08-29 3 views
3

При принятии классы по CLOS я встретил по той же схеме несколько раз:Используя петлю на макро для создания классов слотов в Common Lisp

(defclass class-name() 
    ((field-1 
     :initarg field-1 
     :initform some-value 
     :accessor field-1) 
    (field-2 
     :initarg field-2 
     :initform another-value 
     :accessor field-2) 
    (...) 
    (field-n 
     :initarg field-n 
     :initform n-value 
     :accessor field-n))) 

(это ли хороший дизайн то, что я буду учиться с время)

Я попытался решить это с помощью макроса, чтобы я мог позвонить, сказать:

(defclass-with-accessors 'class-name 
    (('field-1 some-value) 
    ('field-2 another-value) 
    (...) 
    ('field-n n-value))) 

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

Макрос, чтобы поля аксессоров кажется правильным:

(defmacro make-accessor-field (name form) 
    `(,name 
    :initarg ,(make-keyword name) 
    :initform ,form 
    :accessor ,name)) 

Но я не получаю главное право макросъемки. Моя первая попытка была:

(defmacro defclass-with-accessors (name body) 
    `(defclass ,name() \(
    ,(loop for my-slot in body collect 
     (make-accessor-field (car my-slot) (cadr my-slot))))) 

Но это не действует, SBCL дает мне следующую ошибку в defmacro оценки:

; in: DEFMACRO DEFCLASS-WITH-ACCESSORS 
;  (MAKE-ACCESSOR-FIELD (CAR MY-SLOT) (CADR MY-SLOT)) 
; 
; caught ERROR: 
; during macroexpansion of (MAKE-ACCESSOR-FIELD (CAR MY-SLOT) (CADR MY-SLOT)). 
; Use *BREAK-ON-SIGNALS* to intercept. 
; 
; The value (CAR MY-SLOT) 
; is not of type 
;  (OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING SYMBOL CHARACTER). 
; 
; compilation unit finished 
; caught 1 ERROR condition 
STYLE-WARNING: 
    redefining COMMON-LISP-USER::DEFCLASS-WITH-ACCESSORS in DEFMACRO 

Что именно происходит? Как компилятор может указать тип (автомобильный слот), если слот не определен? Как я могу перейти к правильному определению этого макроса?

+2

Рассмотрите проблему с помощью [yasnippet] (https://github.com/AndreaCrotti/yasnippet-snippets/blob/master/lisp-mode/class). – PuercoPop

+1

Я предполагаю, что каждый прохожий сделал что-то подобное в своем карьере. Вопрос в том, стоит ли вводить такую ​​тонкую обертку вокруг уже довольно сжатой стандартной формы. – Svante

ответ

11

Основные ошибки

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

(defmacro make-accessor-field (name form) 
    `(,name 
    :initarg ,(make-keyword name) 
    :initform ,form 
    :accessor ,name)) 

Макро формы должны расширяться в код. Этот макрос расширяет форму в список, используемый для описания слота. Описание слота - это не код, а часть списка слотов defclass. Таким образом, вы не можете использовать такой макрос, потому что возвращаемое значение должно быть кодом, а не списком описания слотов.

Также обычно не используется MAKE- в имени макроса. Это скорее конвенция. MAKE-SOMETHING должна быть функцией. Всякий раз, когда вы делаете, во время выполнения создается что-то, и это должно быть функцией. Иногда также требуется применить к к списку вещей, а затем снова использовать функцию.

Это тоже неправильно, потому что это символ с скобкой, как его имя:

(defmacro defclass-with-accessors (name body) 
    `(defclass ,name() \( ; <- what is this? 
    ,(loop for my-slot in body collect 
     (make-accessor-field (car my-slot) (cadr my-slot))))) 

Этот код также не является хорошей идеей, потому что цитаты не являются полезными:

(defclass-with-accessors 'class-name 
    (('field-1 some-value) 
    ('field-2 another-value) 
    (...) 
    ('field-n n-value))) 

Если вы посмотрите на defclass, ему не нужно указывать имена. Таким образом, в вашем варианте defclass также не должно быть кавычек.

Давайте попробуем улучшить его

Пример форма из воображаемого кода базы:

(defclass-with-accessors foo 
    ((bar 10) 
    (baz (sin pi))) 

MAKE-ACCESSOR-FIELD теперь функция:

(defun make-accessor-field (name form) 
    `(,name 
    :initarg ,(intern (symbol-name name) "KEYWORD") 
    :initform ,form 
    :accessor ,name)) 

Новый DEFCLASS-WITH-ACCESSORS:

(defmacro defclass-with-accessors (name slot-descriptions) 
    `(defclass ,name() 
    ,(loop for (slot-name form) in slot-descriptions 
      collect (make-accessor-field slot-name form)))) 

Давайте проверим расширение:

macroexpand-1 расширяет форму один раз в верхнем уровне и pprint печатает S-выражение в некоторых автомагический отформатированный образом:

CL-USER 12 > (pprint (macroexpand-1 '(defclass-with-accessors foo 
             ((bar 10) 
              (baz (sin pi)))))) 

(DEFCLASS FOO 
      NIL 
      ((BAR :INITARG :BAR :INITFORM 10 :ACCESSOR BAR) 
      (BAZ :INITARG :BAZ :INITFORM (SIN PI) :ACCESSOR BAZ))) 

Выглядит хорошо.