Introducción a Common Lisp Algunas Notas Sobre Computación Simbólica Esta sección trata acerca del lenguaje de programación Lisp, (que toma su nombre de procesamiento de listas, en inglés List Processing), presentando algunos conceptos básicos acerca del manejo de símbolos y programación básica en Lisp. A continuación se describe todo lo relacionado con el manejo de símbolos, resaltando su importancia y explicando por qué es apropiado aprender Lisp como lenguaje para manejo de símbolos. 1)
El manejo de símbolos es comparable a trabajar con palabras y oraciones
Dentro una computadora, todo es una cadena de dígitos binarios, ceros y unos, llamados bits. Desde este punto de vista, las sucesiones de bits se pueden interpretar como una representación para los dígitos decimales. Sin embargo, desde otra perspectiva, éstas se pueden considerar como un código para objetos semejantes a palabras y oraciones. •
En Lisp los elementos fundamentales formados a partir de bits son objetos semejantes a palabras y se denominan átomos. Los siguientes son átomos válidos para Lisp: arco dintel poste1 poste2
•
Los grupos de átomos forman objetos parecidos a las oraciones y se denominan listas. Las listas pueden agruparse para formar a su vez listas de nivel superior (listas de listas o listas anidadas). Los siguientes son ejemplos de listas: '(a b c d e f g)
; Una lista simple de siete elementos
'(a (b c) (d e) (f g))
; Una lista de cuatro elementos, ; de los cuales el primero es un átomo y ; los otros tres son listas
'(partes dintel columna1 columna2) •
; Una lista simple de cuatro elementos
En conjunto, los átomos y las listas se denominan expresiones simbólicas, o simplemente expresiones. Una expresión puede representar ya sea una operación, un listado de operaciones, un programa, un pedazo de conocimiento, etc. El manejo de símbolos mediante Lisp implica trabajar con expresiones simbólicas. Las siguientes son expresiones simbólicas que pueden tener sentido para un ingeniero civil: '(trabe1 (debe-estar-sostenido-por columna1) (debe-estar-sostenido-por columna2)) La expresión anterior trata de representar que se necesitan a la "columna1" y "columna2" para sostener a "trabe1". Una persona puede escribir programas que puedan llegar a conclusiones similares, a partir de expresiones como la anterior.
Las expresiones simbólicas también sirven para representar operaciones algebraicas. Los siguientes son : (+ 1 2 3 4 )
; Una expresión que se puede ; evaluar directamente y da ; como resultado 10
(sin pi)
; Una expresión que se puede ; evaluar directamente y representa ; el seno de pi (π) radianes. Esto da ; como resultado 1.2246063538223773E-16 ; (casi cero)
(sin (/ pi 8))
; Una expresión que representa el seno de pi ; radianes entre 8. Esto da como resultado ; 0.3826834323650898
Las expresiones simbólicas también sirven para representar listas de operaciones, tanto como si fuesen datos, así como expresiones que se pueden evaluar. Por ejemplo la siguiente expresión: '((+ 1 2 3 4 ) (sin pi) (sin (/ pi 8))) Es una lista compuesta por tres elementos, cada uno de ellos son a su vez operaciones algebraicas. Pero si se le suministra al compilador tal y como se muestra, Lisp la entenderá como un conjunto de datos y no devolverá cálculo alguno. La siguiente expresión sí lo hará: (eval (first '((+ 1 2 3 4 ) (sin pi) (sin (/ pi 8))))) La anterior es una lista anidada cuyo primer elemento es la función eval, la cual toma como argumento al siguiente elemento de la lista, que a su vez es otra lista, cuyo primer elemento es la función first, y a su vez toma como argumento a la lista de operaciones algebraicas definidas con anterioridad. El resultado de esta expresión es la evaluación del primer elemento de dicha lista, y da como resultado 10. Un programa que maneja símbolos se vale de expresiones simbólicas para trabajar con datos y procedimientos, igual que las personas emplean lápiz, papel y algún lenguaje para hacer lo mismo. Un programa que maneja símbolos, suele tener procedimientos para reconocer expresiones simbólicas particulares, separar elementos de ellas y formar nuevas expresiones. Por ejemplo, la siguiente expresión construye una lista que representa una operación algebráica: (list '+ '1 '2 '3 '4 ) La anterior genera como resultado (+ 1 2 3 4), que como se ha visto anteriormente, es una expresión que puede evaluarse en un momento dado. A continuación se muestran dos ejemplos de expresiones simbólicas más complejas que tratan de representar conocimiento. Los paréntesis indican el principio y el final de las listas. El primer ejemplo, es una descripción de una estructura formada por bloques de juguete, el segundo, la descripción de una universidad.
'(arco
(partes (dintel (dintel (dintel (poste1 (poste2 (poste1 (poste2
dintel poste1 poste2) debe-estar-sostenido-por poste1) debe-estar-sostenído-por poste2) es-un-tipo-de cuña) es-un-tipo-de ladrillo) es-un-tipo-de ladrillo) no-debe-tocar poste2) no-debe-tocar postel))
'(mit
(un-tipo-de universidad) (localidad (cambridge massachusetts)) (teléfono 253-1000) (facultades (arquitectura istración ingeniería humanidades ciencias)) (fundador (william barton rogers)))
Ciertamente, estos ejemplos no pueden evaluarse directamente, pero no deben amedrentar al lector; ambos describen algo de acuerdo con ciertas convenciones en cuanto a la manera de organizar los símbolos. He aquí otro ejemplo, pero esta vez expresando una regla para determinar si algún animal es carnívoro. (identifica6 ((? animal) tiene dientes puntiagudos) ((? animal) tiene garras) ((? animal) tiene ojos al-frente) ((?animal) es carnívoro)) Acabamos de expresar de otra forma la idea de que un animal con dientes puntiagudos, garras y ojos al frente probablemente es un carnívoro. Para emplear tal regla, un programa debe aislarla, determinar si los patrones que involucran dientes, garras y ojos son compatibles con la información que de algún animal específico se tenga en la base de datos y, en caso afirmativo, agregar a la base de datos la conclusión de que el animal es carnívoro. Todo esto se realiza mediante el manejo de símbolos. 2)
Lisp es uno de los lenguajes de programación utilizados en el desarrollo de sistemas que se denominan "inteligentes"
En la actualidad, hay un creciente desarrollo de programas que presentan lo que la mayoría considera comportamiento inteligente. Casi todos estos programas inteligentes, o que aparentan serlo, están escritos en Lisp, y muchos tienen gran importancia práctica. He aquí algunos ejemplos. •
Expertos en resolver problemas. Uno de los primeros programas en Lisp resolvía problemas de cálculo como los que resuelven los estudiantes universitarios de primer año. Otro de estos programas resolvía problemas de analogías geométricas como los que se incluyen en pruebas de inteligencia. Desde entonces se han creado nuevos programas para configurar computadoras, diagnosticar infecciones de la sangre, entender circuitos electrónicos, evaluar formaciones geológicas, planear diversiones, planificar fábricas, disertar cerraduras, inventar matemáticas interesantes y mucho más. Todos ellos escritos en Lisp.
•
Razonamiento con sentido común. Gran parte del pensamiento humano parece implicar una pequeña cantidad de razonamiento y una gran cantidad de conocimiento. La representación del conocimiento implica elegir un vocabulario de símbolos y establecer algunos acuerdos en cuanto a
cómo disponerlos. Las buenas representaciones hacen que las cosas correctas resulten explícitas. Lisp es un lenguaje en el cual se realiza la mayoría de las investigaciones acerca de la representación. •
Aprendizaje. Muchos han sido los trabajos que se han realizado acerca del aprendizaje de conceptos por computadora y ciertamente la mayor parte de lo realizado también se apoya en los avances logrados en las técnicas de representación del conocimiento. Una vez más, Lisp domina en esta área.
•
Interfaces en lenguaje natural. Existe una creciente demanda de programas que interactúen con las personas en inglés u otros lenguajes (a los cuales les denominaremos "naturales"). Se han construido sistemas prácticos para recuperar información de bases de datos, que de otra forma son difíciles de usar.
•
Educación, sistemas de apoyo (o ayudas) inteligentes y capacitación. Para interactuan de manera cómoda con las computadoras, es preciso que éstos sepan lo que la gente sabe y cómo decirle más. Nadie desearía tener una vasta explicación acerca de lo que conoce bien, como tampoco a nadie le agradaría recibir una explicación en términos telegráficos cuando se es principiante. Los programas basados en Lisp están empezando a hacer modelos de analizando lo que éste hace. Estos programas usan los modelos para ajustar o elaborar explicaciones.
•
Lenguaje y visión. Se ha probado que es sumamente difícil entender la manera como escuchamos y vemos. Parece que aún no logramos conocer lo suficiente acerca de cómo el mundo físico limita lo que percibimos por medio del tambor de nuestro oído o nuestra retina. Sin embargo, se ha progresado en este sentido y gran parte de este avance se ha realizado a través de Lisp, aunque se requiere una buena parte (le programación orientada a la aritmética. A decir verdad, Lisp no ofrece ventajas especiales para hacer programación orientada a la aritmética, pero tampoco plantea desventajas en ese aspecto.
En consecuencia, la gente que desea saber de la inteligencia utilizando las computadoras necesita de una manera u otra entender lenguajes para procesamientos simbólico, tales como los basados en Lisp. 3)
LISP está orientado a la promoción de la productividad y facilita la educación
Lisp es un lenguaje importante incluso para cuestiones no relacionadas con representación de la inteligencia. He aquí algunos ejemplos: •
Programación de aplicaciones. Los programadores talentosos se han dado cuenta que Lisp puede incrementar enormemente su productividad al permitirles escribir programas extensos mucho más rápido y a un costo mucho menor. Esto puede tener efectos notables en la forma en que los programas extensos se desarrollan. Anteriormente se empezaba por destinar varios años para la especificación, luego se asignaban varios años más para la programación, y esto acababa en sistemas que producían s decepcionados y malhumorados. Actualmente se construyen prototipos en pocos meses, con la evolución simultánea de la especificación y la programación, así como de los desarrollos logrados en ambientes de programación gráficos, y con la constante participación de los s en la producción del resultado final.
•
Programación de sistemas. Las máquinas LISP fueron estaciones de trabajo de gran potencia, programadas enteramente en Lisp. El sistema operativo, los programas para uso general, los editores, los compiladores y los intérpretes están todos escritos en Lisp, lo que demuestra el poder y la versatilidad de este lenguaje. Con la evolución de la tecnología de computadoras que actualmente se ha logrado, es posible utilizar todo el poder de aquellas estaciones de trabajo, en computadoras de escritorio.
•
Educación en ciencias de la computación. Lisp facilita la abstracción de procedimientos y de datos, dando énfasis con ello a (los ideas de suma importancia en la programación. Y en vista de que LISP es un lenguaje orientado a la implantación, resulta adecuado para construir intérpretes y compiladores para una amplia gama de lenguajes, incluyendo el propio Lisp.
4)
LISP es el lenguaje para manejo de símbolos más usado en América
Existen numerosos lenguajes de programación, de los cuales, sólo unos cuantos se destinan al manejo de símbolos. De éstos, Lisp es el más usado en el continente americano. Una vez que Lisp se entiende, la mayoría de los otros lenguajes para manejo de símbolos son relativamente fáciles de aprender. ¿Por qué Lisp ha llegado a ser el lenguaje más utilizado para el manejo de símbolos, y últimamente para muchas cosas más? A continuación se dan una serie de argumentos, cada uno con sus propios partidarios: •
Interacción. Lisp está diseñado para programar en una terminal con respuesta rápida. Todos los procedimientos y los datos pueden presentarse o modificarse a voluntad del .
•
Ambiente. Lisp fue utilizado por una comunidad que requirió y obtuvo lo mejor en herramientas para edición y depuración. Durante más de dos décadas, en los centros de inteligencia artificial más importantes del mundo, se han creado complejos ambientes de programación en torno a Lisp, creando una combinación inmejorable para escribir programas extensos y complicados.
•
Evolución. COMMON Lisp es el resultado de más de treinta años de continuos experimentos y refinamientos. En consecuencia, las versiones comerciales de Lisp tienen las características apropiadas para estar al nivel de paquetes de software tales como Visual FORTRAN, Visual Basic o Visual C++.
•
Uniformidad. Los procedimientos y datos en Lisp tienen la misma forma. Un programa en Lisp puede usar otro programa como dato, incluso puede crear otro programa y usarlo.
Lisp es un lenguaje fácil de aprender. No es necesario conocer otros lenguajes de programación. En realidad, el contar con cierta experiencia puede ser un obstáculo ya que puede existir el riesgo de desarrollar hábitos inadecuados. Esto obedece a que otros lenguajes hacen las cosas de manera diferente y la traducción procedimiento por procedimiento conduce a construcciones confusas y a prácticas deficientes de programación. Una razón por la que Lisp es fácil de aprender, es que su sintaxis es sencilla. Como dato curioso, la sintaxis actual de Lisp tiene raíces extrañas. El inventor de Lisp, John McCarthy, en un principio usó una especie de Lisp antiguo, que es casi tan difícil de leer como el inglés antiguo. Sin embargo, en determinado momento él quiso utilizar Lisp para hacer un trabajo de matemáticas donde se requería que tanto los procedimientos como los datos tuvieran la misma forma sintáctica. La forma resultante de Lisp se dedujo rápidamente. 5)
COMMON LISP es el Lisp que se debe aprender
El Lisp que se usa en este libro es COMMON LISP, dado que es moderno, estándar, y está ampliamente disponible. COMMON Lisp también es el estándar aceptado para uso comercial. El lector no deberá dejarse confundir por referencias a otros lenguajes extrañamente llamados Lisp. La mayoría de ellos son dialectos obsoletos o COMMON Lisp con extensiones de alguna compañía en especial. Recientemente, COMMON Lisp se ha extendido mediante la incorporación de características para programación orientada a objetos por medio de CLOS, el sistema de objetos de COMMON Lisp. 6)
Algunos de los principales mitos en contra de Lisp
No hay un lenguaje de programación perfecto y Lisp no es la excepción. Muchos de sus defectos originales se han corregido, aunque algunas personas continúan refiriéndolos erróneamente en la actualidad. Estos son algunos de los mitos más arraigados: Lisp es lento al operar con números. En efecto, esto fue cierto durante algún tiempo. Este problema se ha corregido mediante el desarrollo de programas adecuados que permiten traducir el material producido por los programadores en instrucciones, que pueden ser ejecutadas por una computadora de manera directa y sin más descomposiciones. Dicho de otra forma, el problema se ha corregido mediante el diseño de eficaces compiladores de Lisp. Lisp es lento. También esto fue cierto durante algún tiempo. Al principio, Lisp se usaba exclusivamente en investigaciones donde la interacción era el aspecto fundamental, en tanto que a la alta velocidad para depurar los programas orientados a aplicaciones se le confería una menor importancia. En la actualidad, se han desarrollado excelentes compiladores de Lisp para apoyar la creciente demanda comercial para programas en Lisp. Los programas en Lisp son grandes. En realidad este no es un mito. Algunos programas son grandes, pero esto se debe a que Lisp permite crear programas lo suficientemente grandes como para realizar una gran cantidad de tareas. Los programas en Lisp requieren computadoras caras. Hace unos cuantos años, para empezar con Lisp se necesitaba un millón de dólares. Hoy día, se pueden tener excelentes sistemas Lisp para computadoras personales que cuestan sólo unos cuantos cientos de dólares (que ya por si misma es una gran diferencia), y aun los sistemas complejos que operan en estaciones de trabajo orientadas a LISP tienen un costo inferior a los cien mil dólares. Lisp es difícil de leer y depurar por todos los paréntesis que requiere. En realidad, el problema de los paréntesis desaparece en cuanto se aprende a usar un editor para programas en Lisp que permite poner las cosas en la pantalla de edición, de manera apropiada. Nadie encontraría especialmente claro el siguiente ejemplo: (defun fibonacci (n) (if (or (= n 0) (= n l)) 1 (+ (fibonacci n l)) (fibonacci (- n 2))))) Con un poco de experiencia, se obtiene la versión equivalente, ya con formato: (defun fibonacci (n) (if (or (= n 0) (= n l)) 1 (+ (fibonacci (- n l)) (fibonacci (- n 2))))) Los editores orientados a Lisp facilitan la producción de versiones con formato, a medida que se escriben. Lisp es difícil de aprender.
Lisp adquirió esta mala reputación porque en sus primeros años se hizo acompañar de algunos libros difíciles de leer. Ahora existen muchas obras buenas, cada una con sus características distintivas. Aclaraciones Antes de seguir adelante con esta introducción, hay que hacer algunas aclaraciones. 1)
Operaciones Las operaciones se pueden expresar de dos maneras (o notaciones): Infija y Prefija.
Notación Infija "x operador y" Ventajas:
Las expresiones cotidianas (al menos como comúnmente las aprendemos en las escuelas) son de esta manera. Ej. 2 + 3, 4 + 4, 2 * 2 , 2 / 3, etc.
Desventajas:
No son muy adecuadas para la computadora, pues la máquina espera primero que se le proporcionen las acciones y luego los datos. Normalmente en los lenguajes como Fortran o Basic, esto se soluciona mediante un proceso de pre-compilación o "parsing."
Notación Prefija " operador x y" Ventajas:
Las expresiones computacionales están más de acuerdo con la manera en que la computadora las maneja. Se tiene la posibilidad de aplicar un operador a varios elementos. Ej. en la expresión "+ x y z a b c" el operador "+" se aplica a todos los elementos ella y solamente se declara una vez. En notación infija, la expresión anterior se tendría que escribir de la siguiente manera "x + y + z + a + b + c"
Desventajas:
No es la manera común en que las personas manejan las operaciones ni matemáticas ni simbólicas.
Lisp estuvo pensado desde un principio para ser programado con notación prefija. Esto no debe de ahuyentar al lector. Con un poco de práctica, es común que el estudiante del lenguaje se acostumbre a ella. Incluso después de ello, muchas personas prefieren este tipo de notación, por sobre otras encontradas en lenguajes tales como Pascal, Basic, o FORTRAN. 2)
El uso de paréntesis
Una de las características más obvias de Lisp son los paréntesis. Aunque de primera instancia los programas escritos en este lenguaje se asemejan a un conjunto de paréntesis sin mucho código de por medio, estos paréntesis son necesarios para delimitar el "ámbito de aplicabilidad" de una operación. Por ejemplo, en la expresión (+ n 1), el operador "+" sólo se aplica a "n" y a "1". Así mismo, en la expresión (or (= n 0) (= n l)), el operador "or" sólo aplica al resultado de "(= n 0)" y "(= n l)". Casi todos los lenguajes de programación tienen alguna u otra forma delimitación del ámbito de operaciones. En C/C++ lo son los corchetes "{...}", los paréntesis rectangulares "[...]", o el punto y coma ";". En Basic, Visual Basic y demás versiones de dicho lenguaje, lo son el retorno de carro (o línea nueva), los "If-Then-ElseIf-Else-Endif", los "Do-EndDo", etc. Lisp utiliza únicamente los paréntesis para delimitar. Esto puede verse como una ventaja, si se considera la economía en notación. 3)
Programación funcional
Lisp es un lenguaje "funcional". Esto quiere decir que todas las operaciones a realizar se consideran "funciones" que devuelven un resultado siempre. Por ejemplo, (+ 1 2 3)se considera como
una función que devuelve "6", así como (first '(a b c d e)) se considera como una función que devuelve "a". Para el compilador, el primer elemento de una lista siempre se considera una función, cuyos argumentos son el (los) siguiente(s) elemento(s). Si la función está definida ya sea por el o porque sea parte del lenguaje, Lisp la tratará de evaluar siempre. Si uno desea suministrar una lista o un átomo como si fuese solamente dato, la debe de suministrar antecedida por un apóstrofe sencillo ( ' ). Ejemplo: En la elaboración de un programa en un lenguaje funcional tal como Lisp, siempre se parte de expresiones sencillas, conocidas como "primitivas", pues están compuestas de operaciones propias del lenguaje. Por ejemplo, las siguientes expresiones tienen como objetivo el asignar la conectividad de cada uno de los nodos de la siguiente gráfica, con sus nodos vecinos: s
a
c
d
b
e
f (setf (get (get (get (get (get (get (get
's 'a 'b 'c 'd 'e 'f
'vecinos) 'vecinos) 'vecinos) 'vecinos) 'vecinos) 'vecinos) 'vecinos)
'(a d) '(s b d) '(a c e) '(b) '(s a e) '(b d f) '(e))
La siguiente expresión asigna la posición (en coordenadas x, y) de estos mismos nodos en un plano cartesiano: (setf (get (get (get (get (get (get (get
's 'a 'b 'c 'd 'e 'f
'coordenadas) 'coordenadas) 'coordenadas) 'coordenadas) 'coordenadas) 'coordenadas) 'coordenadas)
'(0 3) '(4 6) '(7 6) '(11 6) '(3 0) '(6 0) '(11 3))
Si se suministran las expresiones anteriores a la computadora, Lisp asignará lugares de memoria específicos para cada uno de los nodos que se han definido. Para encontrar cuales son los nodos vecinos de " 'a " por ejemplo, sólo basta con suministrar una instrucción como la siguiente: (get 'a 'vecinos)
y esto da como resultado (S B D)
Lo mismo se puede hacer para los demás nodos:
(get (get (get (get
's 'b 'c 'd
'vecinos) 'vecinos) 'vecinos) 'vecinos)
resultado resultado resultado resultado
(A D) (A C E) (B) (S A E)
...etc. Para obtener las coordenadas cartesianas de un nodo basta con escribir la directiva get, el nombre del nodo y el argumento coordenadas. Ejemplo: (get (get (get (get
's 'a 'b 'c
'coordenadas) resultado 'coordenadas) resultado 'coordenadas) resultado 'coordenadas) resultado
(0 3) (4 6) (7 6) (11 6)
...etc. Para obtener la distancia entre dos nodos, es necesario definir una función que utilice las coordenadas cartesianas de ambos nodos para calcularla. La distancia entre dos puntos está definida (para superficies planas) como:
[( X
− X 1 ) + (Y2 − Y1 ) 2
2
2
]
Para definir una función, se debe de observar de cerca los componentes que la conforman. Esto es necesario para cualquier lenguaje de programación. Para el caso particular de la función anterior, esta está compuesta por cuatro operaciones que se deben de realizar a los componentes de los pares cartesianos de asignados a cada nodo. Con anterioridad se definieron las coordenadas cartesianas de cada nodo como una lista compuesta por dos números, de los cuales se sobreentiende que el primero corresponde a X y el segundo a Y. En Lisp las funciones se definen con la directiva defun, el nombre que se le quiere asignar a ella y entre paréntesis los argumentos que utiliza. La función que nos proponemos construir entonces recibirá como argumentos a dos nodos, a los cuales llamaremos nodo-1 y nodo-2. (defun distancia-entre-nodos (nodo-1 nodo-2)
)
El paso siguiente será que la función busque los pares cartesianos que corresponden a los dos nodos que se le suministraron. Cabe hacer la aclaración que todos los resultados de las operaciones que se realizan en un ámbito, no permanecen en la memoria para ser utilizados en otros lugares de una función. Para nuestro caso en particular, se utiliza la directiva get de nuevo, pero se necesita que el resultado que se devuelva quede almacenado en un lugar de la memoria, por lo que definiremos dos variables propias de la función para este objetivo. A estas variables "temporales" las llamaremos "coordenadas-1 " y "coordenadas-2 ". La directiva let permite tanto definir como asignar valores a las variables dentro de una función. La sintaxis de la directiva let es de la siguiente manera: (let (
(variable-1 (variable-2 ... (variable-N
valor-1) valor-2) valor-N)
)
(Operaciones a realizar aquí...) )
... donde valor-N es ya sea un número, un símbolo, una lista, el resultado de una operación, etc. En nuestro caso, let quedaría de la siguiente manera: (let (
(coordenadas-1 (get nodo-1 'coordenadas)) (coordenadas-2 (get nodo-2 'coordenadas))
)
Tanto "coordenadas-1 " y "coordenadas-2 " recibirán sendas listas que representan cada una de las coordenadas de cada nodo. Hecho lo anterior, nuestra función quedaría de la siguiente manera: (defun distancia-entre-nodos (nodo-1 nodo-2) (let ((coordenadas-1 (get nodo-1 'coordenadas)) (coordenadas-2 (get nodo-2 'coordenadas))) ) Para obtener las coordenadas X, utilizaremos la directiva first, que devuelve al primer elemento de una lista. Para las coordenadas Y, utilizaremos la directiva second, que obtiene al segundo elemento de una lista. (first coordenadas-1) (first coordenadas-2) (second coordenadas-1) (second coordenadas-2) Respectivamente obtendremos las diferencias entre las coordenadas X e Y, para cada pareja de coordenadas: (- (first coordenadas-1) (first coordenadas-2)
)
(- (second coordenadas-1) (second coordenadas-2) ) Para exponenciación, Lisp posee la directiva expt, cuya sintaxis es la siguiente: (expt valor exponente) donde valor y exponente son números reales. Para nuestro caso: (expt (- (first coordenadas-1) (first coordenadas-2))
2 )
(expt (- (second coordenadas-1) (second coordenadas-2))
2 )
El paso siguiente será sumar el resultado de estas dos operaciones: (+ (expt (- (first coordenadas-1) (first coordenadas-2)) 2) (expt (- (second coordenadas-1) (second coordenadas-2)) 2)
)
Seguidamente, obtendremos la raíz cuadrada de la expresión anterior: (sqrt (+ (expt (- (first coordenadas-1) (first coordenadas-2)) 2) (expt (- (second coordenadas-1) (second coordenadas-2)) 2))
)
La función completa quedaría como sigue: (defun distancia-entre-nodos (nodo-1 nodo-2) (let ((coordenadas-1 (get nodo-1 'coordenadas)) (coordenadas-2 (get nodo-2 'coordenadas))) (sqrt (+ (expt (- (first coordenadas-1) (first coordenadas-2)) 2) (expt (- (second coordenadas-1) (second coordenadas-2)) 2))))) Una vez definidas, las funciones se pueden llamar desde cualquier punto de un programa, o inclusive dentro de otra función, pero en lugar de proporcionar los parámetros dentro de los paréntesis, tanto el nombre de la función como sus argumentos se proporcionan con una lista. Ejemplo: (distancia-entre-nodos 's 'a) da como resultado 5.0 (distancia-entre-nodos 'a 'd) da como resultado 6.082762530298219 ...etc.
Ejercicios Elabora un programa que haga lo mismo que el anterior, pero para ternas (X Y Z), con la siguiente información: nodos: vecinos (X Y Z) g: h: i: k: j: m: z:
h g h i g i m
j i j k m h m j z
(1 2 4) (3 2 1) (9 -1 10) (5 2 4) (-2 1 0) (0 0 -7) (-1 -1 -1)