tipos - macros en excel 2013
Macro que define funciones cuyos nombres se basan en los argumentos de la macro (2)
* Nota: A pesar de haber frecuentado StackOverflow durante mucho tiempo, esta es la primera pregunta que he publicado. Disculpas si es un poco detallado. Crítica constructiva apreciada.
Cuando defino una estructura en Common Lisp usando defstruct, se genera automáticamente una función de predicado que prueba si su argumento es del tipo definido por la defstruct. P.ej:
(defstruct book
title
author)
(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain")))
(book-p huck-finn))
=> True
Sin embargo, al definir una clase que usa defclass, tales funciones aparentemente no se generan por defecto (¿hay alguna manera de especificar esto?), Así que estoy tratando de agregar esta funcionalidad yo mismo, porque me gustaría a) para esta sintaxis ser consistente entre las estructuras y las clases, b) tener una abreviatura de (typep obj ''classname)
, que necesito escribir muy a menudo y es visualmente ruidoso, yc) como un ejercicio de programación, ya que todavía soy relativamente nuevo para Lisp .
Podría escribir una macro que define una función predicada dado el nombre de una clase:
(defclass book ()
((title :initarg :title
:accessor title)
(author :initarg :author
:accessor author)))
;This...
(defmacro gen-predicate (classname)
...)
;...should expand to this...
(defun book-p (obj)
(typep obj ''book))
;...when called like this:
(gen-predicate ''book)
El nombre que necesito pasar para defunir debe tener la forma ''classname-p
. Aquí es donde tengo dificultades. Para crear un símbolo de este tipo, podría usar la función "symb" de On Graf On Lisp (p. 58) de Paul Graham. Cuando se ejecuta en REPL:
(symb ''book ''-p)
=> BOOK-P
Mi macro gen-predicate se ve así hasta ahora:
(defmacro gen-predicate (classname)
`(defun ,(symb classname ''-p) (obj)
(typep obj ,classname)))
(macroexpand `(gen-predicate ''book))
=>
(PROGN
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN ''|''BOOK-P| ''NIL T))
(SB-IMPL::%DEFUN ''|''BOOK-P|
(SB-INT:NAMED-LAMBDA |''BOOK-P|
(OBJ)
(BLOCK |''BOOK-P| (TYPEP OBJ ''BOOK)))
NIL ''NIL (SB-C:SOURCE-LOCATION)))
T
Parece que el símbolo creado por (symb ''book ''-p)
realidad se considera |''BOOK-P|
por la implementación (SBCL), no BOOK-P
. Efectivamente, esto ahora funciona:
(let ((huck-finn (make-instance ''book)))
(|''BOOK-P| huck-finn))
=> True
¿Por qué el símbolo creado por symb se interna como |''BOOK-P|
? En On Lisp (la misma página que la anterior), Graham dice: "Cualquier cadena puede ser el nombre-letra de un símbolo, incluso una cadena que contenga letras minúsculas o macro caracteres como paréntesis. Cuando el nombre de un símbolo contiene tales rarezas, se imprime dentro de la vertical barras." No existen tales rarezas en este caso, ¿verdad? ¿Y estoy en lo correcto al pensar que el "nombre de impresión" de un símbolo es lo que realmente se muestra en la salida estándar cuando se imprime el símbolo, y es, en el caso de tales rarezas, distinto de la forma del símbolo mismo?
Poder escribir macros que definan funciones como gen-predicate
, cuyas funciones definidas se nombran en función de los argumentos pasados a la macro, me parece algo que los hackers de Lisp probablemente han estado haciendo por siglos. El usuario Kaz dice aquí ( Fusionando símbolos en el ceceo común ) que a menudo se puede evitar el "mapeo" de los símbolos, pero eso sería contrario al propósito de esta macro.
Finalmente, suponiendo que pueda hacer que gen-predicate
funcione como yo quiero, ¿cuál sería la mejor manera de garantizar que se llame para cada clase nueva tal como están definidas? De la misma manera que initialize-instance
se puede personalizar para realizar ciertas acciones al instanciar una clase, ¿hay una función genérica llamada por defclass que pueda realizar acciones luego de la definición de una clase?
Gracias.
Ese es un problema habitual: ¿qué pasa a una macro?
Compara llamadas como esta:
(symb ''book ''-p)
y
(symb ''''book ''-p)
Tu forma macro es esta:
(gen-predicate ''book)
GEN-PREDICATE
es una macro. classname
es un parámetro para esta macro.
Ahora, ¿cuál es el valor de classname
dentro de la macro durante la expansión del código? ¿Es book
o ''book
?
En realidad, es el último porque usted escribió (gen-predicate ''book)
. Recuerde: las macros ven el código fuente y la fuente del argumento pasa a la función macro, no el valor. El argumento es ''book
. Por lo tanto, esto se pasa. (QUOTE BOOK)
es el mismo, solo se imprime de manera diferente. Entonces es una lista de dos elementos. El primer elemento es el símbolo QUOTE
y el segundo elemento es el símbolo BOOK
.
Así, la macro ahora llama a la función SYMB
con el valor del argumento (QUOTE BOOK)
o, más corto, ''BOOK
.
Si desea generar el predicado sin el carácter de comillas, debe escribir:
(gen-predicate book)
Alternativamente también puede cambiar la macro:
(symb classname ''-p)
sería:
(symbol (if (and (consp classname)
(eq (first classname) ''quote))
(second classname)
classname))
Comparar
Nosotros escribimos
(defun foo () ''bar)
y no
(defun ''foo () ''bar) ; note the quoted FOO
DEFUN
es una macro y el primer argumento es el nombre de la función. Es un problema similar, entonces ...
Segunda parte de la pregunta
Realmente no conozco ninguna buena respuesta a eso. No recuerdo ninguna manera fácil de ejecutar código (por ejemplo, para definir una función) después de una definición de clase.
Tal vez use el MOP, pero eso es feo.
escriba una macro personalizada
DEFINE-CLASS
que haga lo que desee: se expande aDEFCLASS
yDEFUN
.iterar sobre todos los símbolos en un paquete, encontrar las clases y definir los predicados correspondientes
Para abordar la segunda parte de la pregunta, las clases son en sí mismas objetos, gracias al MOP, por lo que podría ser posible escribir un método: after en initialize-instance especializado en STANDARD-CLASS. Pero debe consultar el MOP para ver si se permite o no definir dicho método.
Si es posible, entonces sí, puede ejecutar código en respuesta a la creación de una clase; sin embargo, dado que no conoce el nombre de la clase que se está creando hasta el tiempo de ejecución, no puede deletrearlo textualmente en la fuente, por lo que no puede usar su macro (a menos que use eval). Prefieres usar algo como
(let ((classname (class-name class)))
(compile (generate-my-predicate-symbol classname)
(lambda (x) (typep x classname))))
Creo que la sugerencia de Rainer de escribir tu propia macro DEFINE-CLASS es el camino a seguir, quiero decir, la forma más probable en que lo haría un Lisper experimentado, si no hay otras consideraciones en juego. Pero no soy realmente un Lisper experimentado, así que podría estar equivocado;)