programacion - ¿Cómo funcionan las palabras clave en Common Lisp?
lisp descargar (3)
La pregunta no es sobre el uso de palabras clave, sino sobre la implementación de palabras clave. Por ejemplo, cuando creo una función con parámetros de palabras clave y hago una llamada:
(defun fun (&key key-param) (print key-param)) => FUN
(find-symbol "KEY-PARAM" ''keyword) => NIL, NIL ;;keyword is not still registered
(fun :key-param 1) => 1
(find-symbol "KEY-PARAM" ''keyword) => :KEY-PARAM, :EXTERNAL
¿Cómo se utiliza una palabra clave para pasar un argumento? Las palabras clave son símbolos, mientras que los valores son ellos mismos. ¿Cómo se puede vincular un parámetro correspondiente utilizando una palabra clave?
Otra pregunta sobre las palabras clave: las palabras clave se utilizan para definir paquetes. Podemos definir un paquete nombrado con una palabra clave ya existente:
(defpackage :KEY-PARAM) => #<The KEY-PARAMETER package, 0/16 ...
(in-package :KEY-PARAM) => #<The KEY-PARAMETER package, 0/16 ...
(defun fun (&key key-param) (print key-param)) => FUN
(fun :KEY-PARAM 1) => 1
¿Cómo distingue el sistema el uso de :KEY-PARAM
entre el nombre del paquete y el nombre del parámetro de función? También podemos hacer algo más complicado, si definimos la función KEY-PARAM
y la exportamos (en realidad no funciona, sino el nombre):
(in-package :KEY-PARAM)
(defun KEY-PARAM (&key KEY-PARAM) KEY-PARAM) => KEY-PARAM
(defpackage :KEY-PARAM (:export :KEY-PARAM))
;;exporting function KEY-PARAM, :KEY-PARAM keyword is used for it
(in-package :CL-USER) => #<The COMMON-LISP-USER package, ...
(KEY-PARAM:KEY-PARAM :KEY-PARAM 1) => 1
;;calling a function KEY-PARAM from :KEY-PARAM package with :KEY-PARAM parameter...
La pregunta es la misma: ¿cómo Common Lisp distingue el uso de la palabra clave :KEY-PARAM
aquí?
Si hay algún manual sobre palabras clave en Common Lisp con una explicación de su mecánica, le agradecería que publicara un enlace aquí, porque solo pude encontrar algunos artículos breves sobre el uso de las palabras clave.
Consulte Common Lisp Hyperspec para obtener detalles completos de los parámetros de palabras clave. Darse cuenta de
(defun fun (&key key-param) ...)
es realmente corto para:
(defun fun (&key ((:key-param key-param)) ) ...)
La sintaxis completa de un parámetro de palabra clave es:
((keyword-name var) default-value supplied-p-var)
default-value
y supplied-p-var
son opcionales. Si bien es convencional usar un símbolo de palabra clave como keyword-name
, no es obligatorio; si solo especifica una var
lugar de (keyword-name var)
, por defecto, keyword-name
es un símbolo en el paquete de palabras clave con el mismo nombre que var
.
Así, por ejemplo, podrías hacer:
(defun fun2 (&key ((myoption var))) (print var))
y luego llamalo como
(fun ''myoption 3)
La forma en que funciona internamente es cuando se llama a la función, recorre la lista de argumentos, recolectando pares de argumentos <label, value>
. Para cada label
, busca en la lista de parámetros un parámetro con ese keyword-name
y enlaza la var
correspondiente al value
.
La razón por la que normalmente usamos palabras clave es porque el prefijo :
destaca. Y estas variables se han autoevaluado para que no tengamos que citarlas también, es decir, puede escribir :key-param
lugar de '':key-param
(FYI, esta última notación era necesaria en los sistemas Lisp anteriores, pero los diseñadores de CL decidieron que era feo y redundante). Y normalmente no usamos la capacidad de especificar una palabra clave con un nombre diferente de la variable porque sería confuso. Se hizo de esta manera para generalidad completa. Además, permitir símbolos regulares en lugar de palabras clave es útil para instalaciones como CLOS, donde las listas de argumentos se fusionan y usted quiere evitar conflictos. Si está extendiendo una función genérica, puede agregar parámetros cuyas palabras clave están en su propio paquete y allí No habrá colisiones.
El uso de argumentos de palabras clave al definir paquetes y exportar variables es de nuevo solo una convención. DEFPACKAGE
, IN-PACKAGE
, EXPORT
, etc. solo se preocupan por los nombres que reciben, no por el paquete en el que están. Usted podría escribir
(defpackage key-param)
y por lo general funcionaría igual de bien. La razón por la que muchos programadores no hacen esto es porque incluye un símbolo en su propio paquete, y esto a veces puede causar conflictos en el paquete si resulta que tiene el mismo nombre que el símbolo que intentan importar de otro paquete. El uso de palabras clave separa estos parámetros del paquete de la aplicación, evitando problemas potenciales como este.
La conclusión es: cuando usa un símbolo y solo le importa su nombre, no su identidad, a menudo es más seguro usar una palabra clave.
Finalmente, se trata de distinguir las palabras clave cuando se usan de diferentes maneras. Una palabra clave es sólo un símbolo. Si se usa en un lugar donde la función o macro solo espera un parámetro ordinario, el valor de ese parámetro será el símbolo. Si está llamando a una función que tiene &key
argumentos &key
, esa es la única vez que se usan como etiquetas para asociar argumentos con parámetros.
El buen manual es el capítulo 21 de PCL .
Respondiendo a sus preguntas brevemente:
las palabras clave son símbolos exportados en el paquete de
keyword
, por lo que puede referirse a ellas no solo como:a
, sino también comokeyword:a
las palabras clave en las listas de argumentos de función (llamadas
lambda-list
s) se implementan, probablemente, de la siguiente manera. En presencia de&key
modifier, la formalambda
se expande en algo similar a esto:(let ((key-param (getf args :key-param))) body)
cuando usa una palabra clave para nombrar un paquete, en realidad se usa como un
string-designator
. Este es un concepto de Lisp que permite pasar a una determinada función relacionada con cadenas, que se utilizarán más adelante como símbolos (para diferentes nombres: de paquetes, clases, funciones, etc.) no solo cadenas, sino también palabras clave y símbolos . Entonces, la forma básica de definir / usar un paquete es en realidad esta:(defpackage "KEY-PARAM" ...)
Pero también puedes usar:
(defpackage :key-param ...)
y
(defpackage #:key-param ...)
(aquí
#:
es una macro de lector para crear símbolos no incrustados; y de esta manera es la preferida, porque no crea palabras clave innecesarias en el proceso).Las dos últimas formas se convertirán en cadenas en mayúsculas. Por lo tanto, una palabra clave sigue siendo una palabra clave y un paquete recibe su nombre como cadena, que se convierte de esa palabra clave.
En resumen, las palabras clave tienen el valor de sí mismas, así como cualquier otro símbolo. La diferencia es que las palabras clave no requieren una calificación explícita con el paquete de keyword
o su uso explícito. Y como otros símbolos pueden servir como nombres para objetos. Como, por ejemplo, puede asignar un nombre a una función con una palabra clave y estará "mágicamente" accesible en cada paquete :) Vea el blogpost de @Xach para más detalles.
No hay necesidad de que "el sistema" distinga diferentes usos de las palabras clave. Sólo se utilizan como nombres. Por ejemplo, imagina dos plistas:
(defparameter *language-scores* ''(:basic 0 :common-lisp 5 :python 3))
(defparameter *price* ''(:basic 100 :fancy 500))
Una función que da la puntuación de un lenguaje:
(defun language-score (language &optional (language-scores *language-scores*))
(getf language-scores language))
Las palabras clave, cuando se utilizan con language-score
, designan diferentes lenguajes de programación:
CL-USER> (language-score :common-lisp)
5
Ahora, ¿qué hace el sistema para distinguir las palabras clave en *language-scores*
de las de *price*
? Absolutamente nada. Las palabras clave son solo nombres, designando diferentes cosas en diferentes estructuras de datos. No son más distinguidos que los homófonos en lenguaje natural: su uso determina lo que significan en un contexto dado.
En el ejemplo anterior, nada nos impide usar la función con un contexto incorrecto:
(language-score :basic *prices*)
100
El lenguaje no hizo nada para evitar que hagamos esto, y las palabras clave para el lenguaje de programación no tan sofisticado y el producto no tan lujoso son exactamente iguales.
Hay muchas posibilidades para evitar esto: no permitir el argumento opcional para language-score
de language-score
en primer lugar, poner *prices*
en otro paquete sin tenerlo en cuenta, cerrando un enlace léxico en lugar de usar los *language-scores*
globalmente especiales *language-scores*
mientras solo exponer significa agregar y recuperar entradas. Quizás solo nuestra comprensión del código base o la convención sean suficientes para evitar que hagamos eso. El punto es: el sistema que distingue las palabras clave en sí mismo no es necesario para lograr lo que queremos implementar.
Los usos específicos de las palabras clave que usted pregunta no son diferentes: la implementación podría almacenar los enlaces de los argumentos de palabras clave en una lista, una lista, una tabla hash o lo que sea. En el caso de los nombres de paquetes, las palabras clave solo se usan como designadores de paquetes y el nombre del paquete como una cadena (en mayúsculas) podría usarse en su lugar. Ya sea que la implementación convierta cadenas en palabras clave, palabras clave en cadenas o algo totalmente diferente internamente, realmente no importa. Lo que importa es solo el nombre y en qué contexto se usa.