Как назначить несколько констант в одном вызове макроса - PullRequest
4 голосов
/ 23 августа 2011

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

; notes.lisp
(defconstant N_oct0 0)

(defmacro N_defheight(_oct _note _offset)
  `(defconstant ,(read-from-string (concatenate 'string _note _oct))
    ,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
     _offset)))
(defmacro N_octave(_octave)
  `(N_defheight ,_octave "c"   0)
  `(N_defheight ,_octave "c#"  1)
  `(N_defheight ,_octave "des" 1)
  `(N_defheight ,_octave "d"   2)
  `(N_defheight ,_octave "d#"  3)
  `(N_defheight ,_octave "es"  3)
  `(N_defheight ,_octave "e"   4)
  `(N_defheight ,_octave "f"   5)
  `(N_defheight ,_octave "f#"  6)
  `(N_defheight ,_octave "ges" 6)
  `(N_defheight ,_octave "g"   7)
  `(N_defheight ,_octave "g#"  8)
  `(N_defheight ,_octave "as"  8)
  `(N_defheight ,_octave "a"   9)
  `(N_defheight ,_octave "a#"  10)
  `(N_defheight ,_octave "b"   10)
  `(N_defheight ,_octave "h"   11))

(N_octave "0")

После загрузки файла в sbcl у меня есть только константа h0, но ни одна из c0 ..b0 константы.

$ sbcl
This is SBCL 1.0.40.0.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (load "notes")

T
* h0

11
* c0

debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD
                                                   "initial thread" RUNNING
                                                   {1002C34141}>:
  The variable C0 is unbound.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-INT:SIMPLE-EVAL-IN-LEXENV C0 #<NULL-LEXENV>)
0] 

Итак, как мне изменить макрос для выполнения всех defconstant вызовов, а не только последнего?

Ответы [ 3 ]

6 голосов
/ 24 августа 2011

Другие ответы уже указали правильное решение: использовать PROGN.

Вот несколько замечаний о «стиле»:

(defmacro N_defheight(_oct _note _offset)
  `(defconstant ,(read-from-string (concatenate 'string _note _oct))
      ,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
          _offset)))
  • Для чего нужны переменные с начальным подчеркиванием? Это не распространенная практика Lisp в Common Lisp
  • READ-FROM-STRING может быть заменен на INTERN и STRING-UPCASE.
  • Возможно, вы захотите контролировать пакет для символа, генерируемого INTERN (или READ-FOR-STRING).
  • EVAL можно заменить на SYMBOL-VALUE
  • CONCATENATE можно заменить на FORMAT: (формат nil "N-OCT ~ a" oct)
  • для определяющего макроса, DEF должен быть началом имени. Это просто соглашение.
* * Пример тысяча двадцать-одиной: * * 1 022
(defmacro N_octave(_octave)
  `(progn
      (N_defheight ,_octave "c"   0)
      ...
      (N_defheight ,_octave "h"   11))

Выше можно упростить с помощью простой итерации:

`(progn
   ,@(loop for (note offset) in '(("c" 0) ("c#" 1) ... ("h" 11))
           collect (list 'defheight octave note offset)))

или используя MAPCAR

`(progn
   ,@(mapcar (lambda (desc)
               (destructuring-bind (note offset) desc
                 (list 'defheight octave note offset)))
             '(("c" 0) ("c#" 1) ... ("h" 11))))

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

Но есть еще одна проблема: данные закодированы в макрос.

Это неправильно. Макрос должен выполнять преобразование кода и не содержать данных. Опять же, вы можете делать все, но хороший Лисп требует некоторого чувства стиля программирования. Я бы поместил примечания и смещения в виде списка в переменную и использовал это в макросе, или предоставил бы его в качестве параметра:

(defvar *notes-and-offsets*
  '(("c" 0) ("c#" 1) ... ("h" 11)))

(defoctave (octave notes-and-offsets)
  `(progn
     ,@(mapcar (lambda (desc)
                 (destructuring-bind (note offset) desc
                   (list 'defheight octave note offset)))
               (eval notes-and-offsets))))

(defoctave "0" *notes-and-offsets*)

Теперь есть еще одна проблема. Мы определяем константы с именами, такими как C0. Константа в Лиспе всегда относится к глобальному постоянному значению. Переплет не допускается. Это означает, что C0 больше не является допустимым именем локальной переменной в вашей программе. Если вы знаете, что вы никогда не будете использовать C0 в качестве имени переменной, это нормально - но эта проблема может быть не известна позже во время обслуживания. По этой причине целесообразно ставить знаки плюс вокруг имен констант, например: +C0+. Опять же, просто соглашение. Вы также можете использовать свое собственное специализированное соглашение об именах, которое не должно конфликтовать с вашими именами для переменных. как NOTE-C0.

Если вы намерены всегда использовать идентификатор типа c0 в качестве глобального имени для значения постоянной ноты, то у вас нет проблем - вам просто нужно понять это, тогда с DEFCONSTANT вы не сможете использовать c0 больше не является переменной. Тогда, возможно, будет хорошей идеей иметь свой собственный пакет.

Далее: если вы хотите использовать переменные при вычислении расширения макроса, вам необходимо убедиться, что переменные имеют значения. Либо загрузите файл раньше, либо используйте EVAL-WHEN.

Это приводит к этому коду:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *n-oct0* 0)
  (defvar *notes-and-offsets*
    '((c   0) (c#  1) (des 1) (d   2)
      (d#  3) (es  3) (e   4) (f   5)
      (f#  6) (ges 6) (g   7) (g#  8)
      (as  8) (a   9) (a# 10) (b  10)
      (h  11)))
  ) ; end of EVAL-WHEN

(defmacro defheight (oct note offset)
  `(defconstant ,(intern (format nil "~a~a" note oct))
     (+ ,(intern (format nil "*N-OCT~a*" oct))
        ,offset)))

(defmacro defoctave (octave notes-and-offsets)
  `(progn
     ,@(mapcar (lambda (note offset)
                 (list 'defheight octave note offset))
               (mapcar #'first (eval notes-and-offsets))
               (mapcar #'second (eval notes-and-offsets)))))

(defoctave 0 *notes-and-offsets*)
4 голосов
/ 23 августа 2011

Вам необходимо расширить форму progn

(defmacro N_octave(_octave)
  `(progn
     (N_defheight ,_octave "c"   0)
     (N_defheight ,_octave "c#"  1)
     (N_defheight ,_octave "des" 1)
     (N_defheight ,_octave "d"   2)
     (N_defheight ,_octave "d#"  3)
     (N_defheight ,_octave "es"  3)
     (N_defheight ,_octave "e"   4)
     (N_defheight ,_octave "f"   5)
     (N_defheight ,_octave "f#"  6)
     (N_defheight ,_octave "ges" 6)
     (N_defheight ,_octave "g"   7)
     (N_defheight ,_octave "g#"  8)
     (N_defheight ,_octave "as"  8)
     (N_defheight ,_octave "a"   9)
     (N_defheight ,_octave "a#"  10)
     (N_defheight ,_octave "b"   10)
     (N_defheight ,_octave "h"   11)))

Вместо этого ваш макрокод вычисляет все расширения и выбрасывает их, кроме последней (как всегда происходит для всех форм, кроме последней в теле функции).

Обратите внимание, что, вероятно, это один из случаев, когда eval-when вступает в игру, но я не могу ничего предложить по этому поводу, потому что мне еще предстоит по-настоящему понять все его тонкости (и я даже не уверен, что хочу :-))

2 голосов
/ 23 августа 2011

Некоторые выражения не объединяются таким образом в Лиспе.Попробуйте использовать конструкцию progn:

(defmacro N_octave(_octave)
    `(progn (N_defheight ,_octave "c" 0)
            (N_defheight ,_octave "c#" 1)
            ... ))
...