Apuntes sobre programación IA y Lisp

29
INTRODUCCIÓN A LA PROGRAMACIÓN CON LISP PARTICULARIDADES DE LOS PROBLEMAS EN INTELIGENCIA ARTIFICIAL -Problemas pobremente definidos y especificados inicialmente, aspecto sólo mejorable mediante refinamiento progresivo incremental -En dominios complejos -Problemas dependientes del contexto, con estrategias y heurísticas a desarrollar mediante paulatina prueba-error -Con enfoques simbólicos o sub-simbólicos, más que numéricos EJEMPLOS Procesamiento de lenguaje natural, traducción automática , visión por ordenador, control de procesos mal modelizables (como fabricación de cemento), procesos adaptativos o evolucionarios ,diagnosis médica, toma de decisiones económicas etc. CONSECUENCIAS PARA LA PROGRAMACIÓN -Diferencias con la Ingeniería del software habitual: no se comienza con una especificación precisa -El trabajo de implementación forma parte del proceso de especificación (progresivo) -Deseable librar al programador de ocuparse de limitaciones técnicas de construcción (bajo nivel en tipos de datos nuevos, gestión de depósito en memoria etc.) -Conveniencia de usar un estilo declarativo usando estructuras de datos ( como árboles o listas) y operaciones (como ajuste de patrones) built-in de alto nivel -Programación que soporte computación simbólica de alto nivel de abstracción -Dificultad de compilar eficientemente en máquinas estándar la programación con estilo imperativo, inicialmente, si bien, una

Transcript of Apuntes sobre programación IA y Lisp

Page 1: Apuntes sobre programación IA y Lisp

INTRODUCCIÓN A LA PROGRAMACIÓN CON LISP

PARTICULARIDADES DE LOS PROBLEMAS EN INTELIGENCIA ARTIFICIAL

-Problemas pobremente definidos y especificados inicialmente, aspecto sólo mejorable mediante refinamiento progresivo incremental

-En dominios complejos

-Problemas dependientes del contexto, con estrategias y heurísticas a desarrollar mediante paulatina prueba-error

-Con enfoques simbólicos o sub-simbólicos, más que numéricos

EJEMPLOS

Procesamiento de lenguaje natural, traducción automática , visión por ordenador, control de procesos mal modelizables (como fabricación de cemento), procesos adaptativos o evolucionarios ,diagnosis médica, toma de decisiones económicas etc.

CONSECUENCIAS PARA LA PROGRAMACIÓN

-Diferencias con la Ingeniería del software habitual: no se comienza con una especificación precisa

-El trabajo de implementación forma parte del proceso de especificación (progresivo)

-Deseable librar al programador de ocuparse de limitaciones técnicas de construcción (bajo nivel en tipos de datos nuevos, gestión de depósito en memoria etc.)

-Conveniencia de usar un estilo declarativo usando estructuras de datos ( como árboles o listas) y operaciones (como ajuste de patrones) built-in de alto nivel

-Programación que soporte computación simbólica de alto nivel de abstracción

-Dificultad de compilar eficientemente en máquinas estándar la programación con estilo imperativo, inicialmente, si bien, una vez comprendidos y especificados algunos problemas se pueden hacer, acaso parcialmente, reimplementaciones en imperativo.

PARADIGMAS ALTERNATIVOS SURGIDOS

-Programación Funcional, basada en la Teoría de Funciones Recursivas y el λ-cálculo: Lisp (McCarthy, finales de los cincuenta)

-Programación Lógica ,basada la Lógica Formal: Prolog (Colmerauer, Kowalski, Roussel, años setenta) Nota: Ésta será tratada en la asignatura Conocimiento y razonam. automatizado

.

Page 2: Apuntes sobre programación IA y Lisp

PROGRAMACIÓN FUNCIONAL (Frente a imperativa):

-Usa funciones con nombre y las anónimas del λ-cálculo ( Church 1936)

-La programación consiste en construir definiciones de funciones, acaso combinando otras previas, para cada problema específico

-La computación consiste en evaluar llamadas de funciones e imprimir valores resultantes, como una calculadora manual, pero a un nivel mucho más flexible y potente

-El orden en que se ejecute la evaluación no afecta a su resultado, un valor. No puede haber efectos colaterales debidos a la posición de variables en memoria, al no usarse variables

- Control de flujo: mediante recursión y condicionales, sin secuenciación ni iteración

-Alto nivel de abstracción: conseguido mediante el uso de la abstracción funcional y la aplicación funcional definidas en el λ-cálculo

-Beneficio: Alto nivel de modularidad, facilidad para descomponer problemas en subproblemas y para ensamblar los resultados parciales correspondientes

PROGRAMACIÓN FUNCIONAL EN LISP

-Lisp (de LISt-, Processing, McCarthy 1958): primer lenguaje funcional

-Diseñado para permitir computación simbólica, usando listas enlazadas como principal estructura de datos

-Principales dialectos en uso: Common Lisp y Scheme

-Sitios web sobre Common Lisp:

www.lispworks.com http://cliki.net/index

-Sitios web sobre Scheme:

http://www.gnu.org/software/mit-scheme

Page 3: Apuntes sobre programación IA y Lisp

BREVE INTRODUCCIÓN A COMMON LISP

FÓRMULAS

Lisp es un lenguaje interactivo. Cualquier sistema de Lisp incluye un interfaz interactivo. En él se escriben expresiones Lisp y el sistema devuelve los valores de las mismas.

Lisp por lo general muestra una señal para indicar que está esperandoue se escriba algo. Muchas implementaciones de Lisp usan como señal de espera el signo >.

Uno de los tipos de expresión Lisp más simple es un número entero. Si se haceentrar un 1 después de > el sistema dará 1 como resultado (la evaluación de la expresión de un número es él mismo) y presentará > otra vez> 11>

Por ejemplo, si queremos sumar dos números, habría que escribir algo como:> (+ 2 3)5

En la expresión (2 + 3), el signo + se llama el operador, y los números 2 y 3 se denominan los argumentos. Usualmente esta expresión la escribimos 2 + 3 (notación infija), pero Lisp usa notación polaca prefija, poniéndose el operador + en primer lugar, seguido por los argumentos, con toda la expresión encerrada en un par de paréntesis: (+ 2 3).Puede parecer a primera vista de una manera extraña de escribir expresiones, pero de hecho esta notación presenta ventajas para Lisp. Por ejemplo, al sumar tres números en la forma ordinaria de la notación hay que usar + dos veces, 2 + 3 + 4 mientras que en Lisp basta añadir el tercer argumento: (+ 2 3 4)

La forma en que normalmente puede usar +, debe tener exactamente dos argumentos:una a la izquierda y la derecha. La flexibilidad de la notación prefija posibilita que + puede tener cualquier número de argumentos, o incluso ninguno:

> (+)0> (+ 2)2> (+ 2 3)5> (+ 2 3 4)9> (+ 2 3 4 5)

Page 4: Apuntes sobre programación IA y Lisp

Cuando los operadores pueden tener un número cualquiera de argumentos, es necesario usar paréntesis para mostrar cuando empieza y termina cada expresión.

Las expresiones se pueden encajar, es decir, los argumentos en una expresión puede ser también otras expresiones complejas:

> (/ (- 7 1) (- 4 2))3Otra ventaja de la notación Lisp es su economía : lo dicho es todo lo que hay. Toda expresión Lisp es un átomo, como 1, o una lista, que a su vez consistecero o más expresiones entre paréntesis. Expresiones Lisp válidas son:

2 (+ 2 3) (+ 2 3 4) (/ (- 7 1) (- 4 2))

Todo código Lisp toma esta forma. Un lenguaje como C tiene una sintaxis más compleja: uso de expresiones aritméticas con notación infija, las llamadas a funciones usan una especie de notación de prefija, con los argumentos delimitados por comas, las expresiones están delimitados por punto y coma, y los bloques de código se delimitan mediante llaves.En Lisp, se utiliza una notación única para expresar todas estas ideas.

EVALUACIONESVeamos más de cerca cómo se evalúan las expresiones.En Lisp, + es una función, y una expresión como (+ 2 3) es una llamada o invocación a la función. Cuando Lisp evalúa una llamada a la función, lo hace en dos pasos:

1. En primer lugar los argumentos se evalúan, de izquierda a derecha (En este ejemplo, cada argumento se evalúa a sí mismo, lo que produce los valores de los argumentos 2 y 3, respectivamente.

2. Los valores de los argumentos se pasan a la función llamada por el operador. En este caso es la función +, que devuelve 5.

Si alguno de los argumentos es en sí mismo una llamada de función, este esevaluado de modo análogo. Así, cuando (/ (- 7 1) (- 4 2)) se evalúa, esto es lo que sucede:

1. Lisp evalúa (- 1 7): 7 evalúa a 7 y 1 se evalúa a 1. Estos valores se pasa a la función -, que devuelve 6.

2. Lisp evalúa (- 4 2): 4 evalúa a 4 y 2 se evalúa como 2. Estos valores se pasa a la función -, que devuelve 2.

3. Los valores de 6 y 2 son enviados a la función /, que devuelve 3. Como se ve, la recursividad es natural aquí.

Page 5: Apuntes sobre programación IA y Lisp

No todos los operadores de Common Lisp son funciones, pero la mayoría lo son. Las llamadas de función siempre se evalúan de esta manera. Los argumentos se van evaluando de izquierda a derecha, y los valores obtenidos se pasan a la función, que devuelve el valor de la expresión como un todo. Esto constituye la regla de evaluación en Common Lisp.

Un operador que no sigue esta regla de Lisp Común de evaluación es la cita ( quote o, abreviando, ‘ ). Este operador es especial, tiene una regla de evaluación propia: no hacer nada. El operador comilla toma un único argumento, y lo devuelve al pie de la letra:

> (quote (+ 3 5))(+ 3 5)

Para mayor comodidad, al usar abreviatura de la comilla para cita, no es necesario usar paréntesis> ‘(+ 3 5)(+ 3 5)

En Lisp la cita constituye una forma de proteger las expresiones del proceso de evaluación, lo que se verá que puede ser útil en algunos casos.

Si se escribe algo que Lisp no puede procesar, se mostrará un mensaje de error y se producirá una “rotura de bucle”. La rotura proporciona a los programadores la ocasión de analizar el error y de intentar averiguar causa de un error, pero lo primero que tendrá que hacerse ante de una rotura de bucle es salir de ella. Lo que tenga que escribirse para volver a el nivel superior depende de su aplicación de Common Lisp usada. En muchos casos es lo siguiente:

> (/ 1 0)Error: Division by zero. Opcions : : Abort, :backtrace >>: Abort>

DATOSLisp ofrece todos los tipos de datos presentes en la mayoría de los otros lenguajes, junto con varios otros que no lo son en estos. Un tipo de datos ya utilizado es de número entero, que está escrita como una serie de dígitos: 256 . Otro tipo de datos Lisp tiene en común con la mayoría de los otros lenguajes es la palabra o cadena, que se representa como una serie de caracteres rodeado por comillas dobles: "Pauca sed matura". Enteros y cadenas se evalúan produciendo a sí mismos.

Dos tipos de datos de Lisp que no encuentran comúnmente en otros lenguajes son símbolos y listas. Los símbolos son palabras. Ordinariamente son convertidas en mayúsculas, independientemente de cómo se escriban:

Page 6: Apuntes sobre programación IA y Lisp

> 'AutomovilAUTOMOVIL

Los símbolos por lo general no se evalúan a sí mismos, por lo que si desea referirse a uno se debe citar, como en el ejemplo anterior.

Las listas se representan como cero o más elementos encerrados entre paréntesis.Los elementos pueden ser de cualquier tipo, incluso listas. Éstas han de ir citadas o Lisp los consideraría como llamadas a funciones, produciendo errores:> ‘(Sus 3 ‘ hijos )(SUS 3 "Hijos")

> ‘(La lista (a b c ) tiene 3 elementos)(LA LISTA (A B C) TIENE 3 ELEMENTOS)

Una cita protege a una expresión entera, incluyendo las subexpresiones que contenga.

Se puede crear listas llamando a la función list . Como en toda función, y list lo es sus argumentos se evaluan. Aquí vemos una llamada a + dentro de una llamadaa list:

> (list ‘los (+ 2 1) " Hijos ")(LOS 3 "Hijos")

Aparte de la flexibilidad y la elegancia de su notación, una de las características más importantes de de Lisp es que los programas en Lisp se expresan como listas. . Esto significa que los programas Lisp pueden generar código Lisp. Los programadores de Lisp pueden (y con frecuencia lo hacen) escribir programas para escribir programas para ellos, como podrá ver quien ahonde en el estudio de este lenguaje. Pero es importante aquí comprender la relación entre expresiones y listas, aunque sólo sea para evitar confusiones. Es aquí donde interviene quote. Si una lista es citada, la evaluación devuelve la lista misma, y si no se cita, la lista se trata como código, yla evaluación devuelve su valor, si existe:

> (list '(+ 2 1) (2 + 1))((+ 2 1) 3)

Aquí, el primer argumento aparece citado, y así se obtiene una lista. El segundoargumento no se cita, y por ello es tratado como una llamada a la función, produciendoun número.

En Common Lisp, hay dos formas de representar la lista vacía. Se puede representar como un par de paréntesis sin nada entre ellos, o puede utilizar el símbolo de nil. Sin importar como se

Page 7: Apuntes sobre programación IA y Lisp

haya hecho, Lisp mostrará como resultado NIL:> ( )NIL

> nilNIL

Aunque pueda hacerse, no es preciso citar a nil, ya que nil se evalúa a sí mismo.

OPERACIONES CON LISTASLa función cons crea listas. Si el segundo argumento es una lista,devuelve una nueva lista con el primer argumento añadido delante:

> (cons 'a ' (b c d) ) (A B C D)

Se puede crear listas por usando cons para poner nuevos elementos en una lista vacía, aunque la funcione list antes vista es una manera más conveniente de colocar varias cosas en nil:

> ( cons 'a (cons ' b nil) )( A B )> ( list 'a ' b)( A B )

Las funciones primitivas para extraer elementos de las listas son car y cdr. El car de una lista es el primer elemento, y el cdr es todo lo que sigue al primer elemento:

> (car '( a b c) )A> (cdr '(a b c) )(B C)

Se puede utilizar combinaciones de car y cdr para captar cualquier elemento de unalista. Por ejemplo, si se desea conseguir el tercer elemento de la lista que aparece en la siguiente expresión se podría decir:

> (car (cdr (cdr '( a b c d ) ) ) )C

Sin embargo, puede hacer lo mismo con mayor facilidad invocando la función third:

> (third '( a b c d ) )C

Page 8: Apuntes sobre programación IA y Lisp

VALORES DE VERDAD En Common Lisp, el símbolo t es la representación por defecto de verdadero . Al igual que nil, t se evalúa a sí mismo. La función listp ( “se predica que es lista lo que sigue”) devuelve T si su argumento es una lista:

> (listp '(a, b c))T

Una función cuyo valor de retorno haya de ser a ser “verdadero” o “falso” es lo que se conoce como predicado. Los predicados en Common Lisp a menudo tienen nombres que terminan con p.La falsedad en Common Lisp está representado por nil, la lista vacía. Si damos a list un argumento que no sea una lista, devuelve nil:

> (Listp 27)NIL

Como nil desempeña dos papeles en Common Lisp, el de la función null, que devuelve T en caso de aplicarse a la lista vacía,> (null nil )T

y la función (lógica) not, que devuelve T si su argumento es falso,

> (not nil )Thacen exactamente lo mismo.

CONDICIONALESLa forma más simple de condicional en Common Lisp es if. Por lo general toma tres argumentos: una expresión (el condicional o antecedente), una expresión (el consecuente de la condición) y por ultimo otra expresión (el consecuente alternativo). El proceso es el siguiente: la expresión antecedente se evalúa. Si resulta verdadera,entonces la expresión consecuente se evalúa a su vez, presentándose su valor; si por el contrario la evaluación devuelve falso, la tercera expresión se evalúa y su valor se presenta:

> (if ( listp '(a b c) ) (+ 1 2) (+ 5 6))3> (if ( listp 27) (+ 1 2) (+ 5 6))11

Page 9: Apuntes sobre programación IA y Lisp

Como quote, el operador if es especial. No podría ser implementado como una función, porque en una llamada a función todos los argumentos se evalúan siempre , mientras que aquí se evalúe sólo uno de los dos últimos.En este operador el último argumento es opcional. Si lo omite, el valor predeterminadoserá nil :

> (if (listp 27) (+ 2 3) )NIL

aunque t es la representación por omisión de verdadero, todo excepto nil también cuenta como verdadero en un contexto lógico:

> (if 27 1 2)1

Los operadores lógicos and y or actúan en cierto modo como condicionales. Ambos toman cualquier número de argumentos, pero sólo evalúan tantos como necesitanpara decidir qué devolver. Si todos sus argumentos resultan ser verdaderos(es decir, no son nil ), devuelve el valor del último de ellos

> ( and t ( + 1 2 ) )3

Pero si uno de los argumentos resulta falso, ninguno de los argumentos posteriores se evalúa . De manera similar pasa con or, cuyo proceso se detiene tan pronto como aparezca un argumento verdadero.. Estos dos operadores son macros. Al igual que los operadores especiales, las macros pueden eludir la regla de cálculo de lo habitual.

FUNCIONESSe pueden definir nuevas funciones con defun. Por lo general toma tres argumentos o más: un nombre, una lista de parámetros, y una o más expresiones que forman el cuerpo de la función. Por ejemplo, podríamos definir la función “tercera-entrada” mediante

> ( defun tercera -entrada (x) ( car ( cdr ( cdr x) ) ) ) TERCERA-ENTRADA

El primer argumento dice que el nombre de esta función será “ tercera- parte”. El segundo argumento, la lista (x), dice que la función tendrá exactamente un argumento, x, símbolo que se utiliza como contenedor (variable). Cuando la variable represente un argumento de una

Page 10: Apuntes sobre programación IA y Lisp

función, como x hace aquí, también se llamará “parámetro”.

El resto de la definición, (car ( cdr ( cdr x) ) ), referido como “el cuerpo de la función” le indica a Lisp lo que tiene que hacer para calcular el valor de retorno de la función. Por lo tanto una llamada a tercera-entrada devuelve ( car ( cdr ( cdr x ) ) ), para cualquier x que le demos damos como argumento:

> ( tercera-entrada '( a b c d ) )C

Tras considerar las variables, es más fácil entender qué son los símbolos . Son nombres de variables, existiendo como objetos en sí. Y por eso los símbolos, como las listas, tienen que citados. Una a lista tiene que ser citada, porque de lo contrario, sería tratada como código; un símbolo tiene que ser citado porque de lo contrario será tratado como una variable.

Puede considerarse una definición de función como una versión generalizada de una expresión Lisp. La siguiente expresión sirve para verificar si la suma de 1 y 4 es mayor que 3:

> ( > ( + 1 4 ) 3 )T

Al sustituir estos números particulares por variables, podemos escribir una función que comprueba si la suma de dos números es mayor que un tercero:

> ( defun suma –mayor-que (x, y z) ( > ( + x y ) z ) )SUMA-MAYOR-QUE> ( suma-mayor –que 1 4 3 )T

Lisp no distingue entre programas, procedimientos o funciones. Las funciones sirven para todo (y de hecho, constituyen la mayor parte del lenguaje).

RECURSIVIDAD

Las funciones definidas antes invocan a otras funciones para hacer aparte del trabajo para ellas. Por ejemplo, suma –mayor-que opera llamando a + y a > . Una función puede llamar a cualquier función, incluyéndo a sí misma.

Una función que se invoque a sí es recursiva. La función Lisp member sirve para examinar si algo es un elemento de una lista dada. Aquí, como ejemplo, se presenta una versión simplificada definida mediante recursividad:

> ( defun miembro-de ( obj lst ) ( if ( null lst )

Page 11: Apuntes sobre programación IA y Lisp

nil ( if ( eql ( car lst ) obj ) lst ( miembro-de obj ( cdr lst ) ) ) ) )

El predicado eql comprueba si sus dos argumentos son idénticos; aparte de eso, todo lo demás que figura en esta definición se ha visto antes. La función definida hace:

> ( miembro -de 'b' ( a b c) ) ( B C)> ( miembro- de 'z ' ( a b c) ) NIL

La definición de miembro corresponde a la siguiente descripción. Para probar si un objeto obj es un miembro de una lista lst hay que:

1. Comprobar si lst está vacía. Si lo está, entonces obj claramente no es miembro de ella, y se ha terminado.

2. Si no lo está y si obj es el primer elemento de lst, entonces es miembro de la misma.

3. En otro caso obj es un miembro de lst si es un miembro de el resto de lst.

Cuando se quiera entender cómo funciona una función recursiva, puede ayudar el que se traduzca a una descripción de este tipo.

ENTRADA Y SALIDAHasta ahora se han hecho las operaciones i / o implícitamente, aprovechando el interfaz . Para programas interactivos reales esto no bastará. Destaquemos algunas funciones de entrada salida.

La función de salida más general de Common Lisp es el format. Usa dos o más argumentos: el primero indica que la salida se va a imprimir, el segundo es una plantilla de cadena, y el resto de argumentos generalmente consiste en objetos cuya representación impresa va a ser insertade en la plantilla. Un ejemplo :

> (format t " ~ A mas A igual-a ~A. ~%" 2 3 (+ 2 3) )2 mas 3 igual-a 5.NIL

Aquí aparecen dos cosas. La primera línea es producida por format. La segunda línea es el valor devuelto por el sistemas tras la llamada a evaluar dada al pasar línea. Por lo general una función como formato no se llama a ejecutar directamente por el usuario,

Page 12: Apuntes sobre programación IA y Lisp

sino que se utiliza dentro de los programas, por lo que el valor del retorno no se ve.

El primer argumento de formato, t, indica que la salida ha de ser enviada al lugar por defecto, lo que suele ser presentarla. El segundo argumento es una cadena que sirve como plantilla para la salida. Dentro de esta cadena, cada ~ A indica una posición a ser llenada, y ~% indica un salto de línea. Las posiciones se llenan con los valores de los argumentos restantes, en orden.

La función estándar para la entrada es read. Cuando no se especifica ningún argumento, se lee desde el lugar por defecto, que suele la pantalla. Por ejemplo, una función que pide al usuario la entrada, y devuelve lo que se entre:

(defun pregunta (cadena) (format t "~ A" cadena) (read))

Se comporta como sigue:

> ( pregunta " edad ?") edad? 29 29

Tengase en cuenta que read ocasionará una espera indefinida, hasta que escribaalgo y (generalmente) se pulse enter. Así que no es conveniente llamar a read sin hacer que se imprima un mensaje explícito, o el programa puede dar la impresión de que se ha quedado atascado, cuando en realidad está a la espera de la entrada.

Lo segundo a señalar sobre read es que es potente: constituye un analizador de Lisp completo. No se limita a leer caracteres y devolverlos como una cadena. Analiza lo que lee, y devuelve el objeto Lisp que resulte. En el caso anterior, se devuelve un número.

Aunque sencilla, la definición de pregunta del ejemplo anterior muestra algo inédito hasta aquí en una función: su cuerpo contiene contiene más de una expresión. De hecho, el cuerpo de una función puede contener cualquier número de ellas. Cuando se invoque la función, serán evaluados por su orden y la función devolverá el valor de la última.

En todo lo anterior , se ha mantenido en puridad una de las características iniciales del Lisp: que no haya efectos colaterales. Un efecto secundario es cierto cambio dell estado del mundo que ocurre como secuela de la evaluación de una expresión. Cuando se evalúa una expresión en Lisp “puro”, como (+ 1 2 ), no se producen efectos secundarios, sino que sólo devuelve un valor. Pero cuando llamamos formato, además de devolver un valor, se imprimirá algo. Esa es una especie de efecto secundario.

Cuando se esté escribiendo código sin efectos colaterales, no tiene sentido considerar la posible definición de funciones con cuerpos de más de una expresión:

Page 13: Apuntes sobre programación IA y Lisp

El valor de la última expresión sería lo único aprovechado, como vlor de la función, pero los valores de las expresiones anteriores se desecharían. Si estas expresiones no tienen efectos secundarios (que puedan interesar por algún motivo), carece de sentido incluirlas ni evaluarlas.

VARIABLESUno de los operadores más utilizados en Common Lisp es let , que permite introducir nuevas variables locales:

> ( let ( x 1 ) ( y 2 ) ) (+ x y) )3

Una expresión let tiene dos partes. Primero viene una lista de instruccionespara la creación de variables, cada una de la forma ( variable expresión ).Cada variable se inicia con el valor de la correspondiente expresión. Así que en el ejemplo anterior, se crean dos variables nuevas, x é y, que inicialmente se ponen con valores 1 y 2 respectivamente. Estas variables son válidas dentro del cuerpo let.

Tras la lista de variables y valores va un conjunto de expresiones, que se evalúan en orden. En este ejemplo sólo hay una, la llamada a +. El valor de la última expresión se devuelve como el valor de let. Veamos otro ejemplo, el de una versión más selectivade pregunta expresad mediante let :

(defun pregunta-un-numero ( ) ( format t "Por favor, introduzca un numero.") ( let ( (val ( lectura ) ) ) ( if (numberp val ) val ( pregunta-un-numero ) ) ) )

Esta función crea una variable para contener valores del objeto leído. Debido a que puede “coger” este objeto, la función puede “examinar” lo que se ha introducido antes de decidir si presentarlo o no. Aquí, numberp es un predicado que verifica si lo introducido es un número o no.

Si el valor introducido por el usuario no fuera un número, pregunta-un-numero se llamaría misma. Así se tiene una función que insiste en conseguir un número:

> ( preguntar-un-numero)Por favor, introduzca un número. aPor favor, introduzca un número. ( luego lo hare )Por favor, introduzca un número. 5252

Page 14: Apuntes sobre programación IA y Lisp

Las variables hasta ahora consideradas se denominan variables locales.Sólo son válidas dentro de un contexto determinado. Hay otro tipo de variable, llamado variable global, que pueden ser trascender a todas partes.

Se puede crear una variable global dando un símbolo y un valor de defparameter por ejemplo:

> ( defparameter * glob * 99)* GLOB *

esta variable será accesible desde todas partes, excepto desde expresiones que establezcan variables locales con el mismo nombre. Para evitar que esto ocurra por despiste, es usual dar nombres de las variables globales que empiecen y terminen en asteriscos. El nombre de la variable que acabamos de crear sería se pronuncia "asterisco- glob-asterisco".

También se puede definir constantes globales, por medio de defconstant:

( defconstant limit (+ * glob * 1))

No hay necesidad de dar a las constantes nombres distintivos, porque se producirá un error si se utiliza el mismo nombre para una variable. Si desea comprobar si un símbolo es el nombre de una variable o constante globales, puede usarse boundp:

> ( boundp ‘*glob*)T

ASIGNACIONESEl operador de asignación más general de Common Lisp es setf. Se puede utilizar para hacer asignaciones a cualquier tipo de variable:

> ( setf *glob* 98)98 > (let ( ( n 10 ) ) (setf n 2) n)2

Cuando figure como primer argumento de setf sea un símbolo que no sea el nombrede ninguna una variable local, queda considerado como una variable global:

> (setf x ( list 'a 'b 'c) )(A B C)

Es decir, se pueden crear variables globales de forma implícita, con sólo asignarles valores. En

Page 15: Apuntes sobre programación IA y Lisp

los archivos fuente, al menos, es de mejor estilo que se utilice defparameters explícitamente.

Se puede hacer algo que va más allá de asignar valores a las variables. El primerargumento de setf puede ser una expresión, tanto como un nombre de variable.En tale casos, el valor del segundo argumento se inserta en el lugar a que se refiere a la primera:

> ( setf ( car x) 'n)N> X(N B, C)

El primer argumento de setf puede ser casi cualquier expresión que se refiereen un lugar determinado.

Se puede dar cualquier número (par) de argumentos a setf. Una expresiónde la forma

( setf a b c d e f )

es equivalente a tres llamadas sucesivas a setf :

(setf a b )(setf c d )(setf e f )

PROGRAMACIÓN FUNCIONAL

Al hacer programación funcional se escriben programas que trabajan devolviendo valores, no modificando cosas. Así se estila en Lisp. La mayor parte de las funciones que incorpora se caracterizan por ser llamadas para devolver valores, no por realizar efectos colaterales .

La función de remove, por ejemplo, toma un objeto y una lista y devuelve otra (nueva) lista que contiene todo lo anterior de la lista, salvo el objeto :

> ( setf lst '(c a r a t) )(C A R A T)> ( remove ‘a lst)(C R T)

Obsérvese que remove no quita un objeto de una lista. La lista original no queda modificada:> Lst

Page 16: Apuntes sobre programación IA y Lisp

(C A R A T)

Para quitar realmente algo de una lista En Lisp por lo general, se hacen tales cosas pasando la lista como argumento por alguna función y usando setf con el resultado. Para quitartodas las “a” de una lista se puede hacer lo siguiente:

(setf x (remove ‘a x) )

La “funcionalidad” en la programación funcional, consiste esencialmente en intentar evitar el uso de setf y de manejos semejantes. A primera vista, puede ser difícil afirmar si ello es es posible y si es deseable. ¿Cómo se puede construir programas que operen sólo mediante la devolución de valores?

Sería inconveniente prescindir de los efectos secundarios por completo, pero en realidad no es demasiado preciso usarlos

Una de las ventajas más importantes de la programación funcional es que permite realizar comprobaciones interactivas. En código funcional puro se puede probar cada función a medida que se escribe. Si devuelve los valores esperados, va confirmando la corrección en conjunto, disponiéndose así de confirmaciones inmediatas cuando se realizan cambios en cualquier parte de un programa, sobre la marcha.

ITERACIÓNEn ocasiones, cuando se quiere hacer algo repetidas veces, resulta más natural utilizar la iteración que la recursividad. Un caso típico se da al generar algún tipo de tabla. Por ejemplo, la función

(defun ver-los-cuadrados ( inicio fin) ( do ( ( i inicial (+ i 1 ) ) ( ( > i fin) 'hecho) (format t "~ A ~ A ~%" i (* i i ) ) ) )

imprime los cuadrados de los enteros desde el principio hasta el final:

> ( ver- los- cuadrados 2 5 )2 43 94 165 25HECHO

La macro do es el operador de repetición fundamental en Common Lisp. Como pasaba con let puede crear variables, su primer argumento es una lista de especificación de variables. Cada elemento de esta lista puede ser de la forma

Page 17: Apuntes sobre programación IA y Lisp

(variable inicial actualización)

donde “variable” es un símbolo, e “ inicial” y “actualización” son expresiones.Al empezar, cada variable se establece con el valor de la correspondiente inicial; cada iteración que haga la reestablecerá, con el valor de actualizado correspondiente. El do que figura en ver-los-cuadrados origina sólo una variable i. En la primera iteración i se establecerá en el valor de partida , y en las sucesivas iteraciones va incrementando su valor con una unidad.

El segundo argumento de un do ha de ser una lista que contenga una o más expresiones. La primera de ellas se utiliza para comprobar si la iteración debe detenerse. En el ejemplo anterior, la expresión de dicha comprobación es (> i fin). El resto de expresiones de la lista serán evaluadas en orden cuando la iteración pare, y el valor de la última será devueltocomo el valor de el do , así que ver- los- cuadrados siempre devolverá hecho.

El resto de argumentos de do será, el llamado cuerpo del bucle, será evaluado en su orden durante cada iteración. En dicha iteración se actualizan las variables, se aplica el test de finalización y, si este falla, se procede a evaluar el cuerpo.

Una versión recursiva del programa de la misma función sería:

(defun ver-los- cuadrados (i final) ( if (> i final) 'Hecho (progn (format t "~ A ~ A ~%" i (* i i) ) (ver-los-cuadrados (+ i 1) final) ) ) )

La única novedad en esta función es progn, que toma cualquier número de expresiones, las evalúa en orden, y devuelve el valor de la última, aprovechando el efecto colateral para ir imprimiendo sucesivamente los números y sus cuadrados .

Common Lisp tiene operadores de iteración más sencilloss para casos especiales. Para iterar a través de los elementos de una lista, por ejemplo, que sería mejor a usar dolist. Por ejemplo, una función que devuelve la longitud de una lista es:

(defun longitud-de ( lst ) ( let ( ( longn 0 ) ) (setf long (+ long 1 ) ) ) long ) )

Aquí dolist toma un argumento de la forma (variable expresión), seguida por un cuerpo de expresiones. El cuerpo será evaluado con la variable ligada a los elementos sucesivos de la lista devuelta por expresión. De modo que el ciclo anterior dice que para cada obj en lst, el incremento es long.

Page 18: Apuntes sobre programación IA y Lisp

La versión recursiva obvia de esta función sería:

(defun longitud-de ( lst ) ( if ( null lst ) 0 (+ ( longitud-de ( cdr lst ) ) 1 ) ) )

O bien, si la lista está vacía, su longitud es igual a cero, de lo contrario es lalongitud del cdr de la misma mas uno.

LAS FUNCIONES COMO OBJETOS

En Lisp, las funciones son objetos normales, como los símbolos cadenas o listas. Si le damos el nombre de una función a function, lo hará devolver el objeto asociado. Como cita, function es un caso especial de operador, por lo que no se tiene que citar el argumento:

> ( function + )# <Compiled-Function + 17BA4E>

Este valor de retorno de aspecto extraño es la forma en que una función suele sermostrade en una implementación típica de Common Lisp.

Hasta ahora sólo se ha tratado sobre objetos que se ven lo mismo cuandoLisp los muestra en la pantalla que cuando se teclean para meterlos Esta convención no se aplica a las funciones. Internamente, una función integrada como por ejemplo + será un segmento de código máquina. Cada implementación concreta de Common Lisp su representación externa particular.

Igual que se puede utilizar ‘ como abreviatura para la cita, se puede usar \ # 'como una abreviatura para function:

> # '+# <Compiled-Function + 17BA4E>

Como con cualquier otro tipo de objeto, se puede pasar funciones como argumentos.Una función que toma auna función como argumento es apply . Éstatoma una función y una lista sus argumentos y devuelve el resultado de la aplicación de la función de los mismos:

> (apply # ‘+ ‘(1 2 3) )6> (+ 1 2 3)6

Page 19: Apuntes sobre programación IA y Lisp

Se puede usarcon cualquier número de argumentos, siempre y cuando el último sea una lista:

> (apply # '+ 1 2 ‘(3 4 5))15

La función fucall hace lo mismo, pero no necesita que los argumentos vayan empaquetados en una lista:

> ( funcall # '+ 1 2 3 )6Como se ha visto, la macro defun crea una función y le da un nombre. Pero las funciones no tienen por qué tener nombres y defun no es imprescindible paradefinirlas. Como con muchos otros tipos de objetos de Lisp, podemos referirnosa las funciones literalmente. Para referirse literalmente a un número entero, se utiliza una serie de dígitos. Para hacerlo con una función, se utiliza lo que se llama una expresión lambda.

Una expresión lambda es una lista que contenga el símbolo lambda, seguido por una lista de parámetros, seguido por un cuerpo de cero o más expresiones. Por ejemplo, una expresión lambda que representa una función que toma dos los números y devuelve su suma sería:( lambda (x y) (+ x y ) )La lista (x y) es la lista de parámetros, y después va lo que se refiere al cuerpode la función.

Una expresión lambda puede ser usada en vez del nombre de una función anónima.Al igual que el nombre de una función ordinaria, una expresión lambda puede ser elprimer elemento de una llamada a función,> ( (lambda (x) (+ x 100) ) 1)101y colocando # ' a una expresión lambda, se obtiene la función correspondiente:> ( (funcall # ' lambda (x) (+ x 100) 1)101Nota: La lambda en una expresión lambda no es un operador. Es sólo un símbolo. En los dialectos iniciales de Lisp que tenía un propósito:las funciones se representan internamente como listas, y la única manerade distinguir a una función de una lista cordinaria era la de comprobar si el primer elemento era el símbolo lambda.

En Common Lisp, puede expresar las funciones como listas, pero no sonrepresentan internamente como objetos distintos. Así lambda ya no es realmente necesario. No habría ninguna incoherencia en requerir que las funciones se denotaran como ( (x) (+ x 100 ) ) en lugar de (lambda (x) (+ x 100) ) , pero como los

Page 20: Apuntes sobre programación IA y Lisp

programadores están acostumbrados a usar elsímbolo lambda, Common Lisp lo mantiene.

TIPOSLisp tiene un enfoque muy flexible para los tipos. En muchos lenguajes,son las variables las que tienen tipo, y no se puede utilizar una variable sinespecificarselo. En Common Lisp, los valores tienen los tipos, no las variables. Se podría imaginar que cada objeto tenga una etiqueta adherida a él, la identificación de su tipo. No hay que declarar los tipos de variables, ya que toda variable puede contener objetos de cualquier tipo.

A pesar de las declaraciones de tipo no son necesarios, es posible que desee hacerellos por razones de eficacia. 2.16 Mirando hacia el futuro

En este capítulo apenas hemos arañado la superficie de Lisp. Ysin embargo, un retrato de un lenguaje muy inusual está comenzando a emerger.

RESUMEN

1. Lisp es un lenguaje interactivo. Si se escribe una expresión, Lisp mostrará su valor.

2. Los programas Lisp consisten en expresiones. Una expresión puede ser un átomo, o una lista de un operador seguido de cero o más argumentos. La sintaxis prefija permite que los operadores puedan tomar cualquier número de argumentos.

3. La regla para evaluar las llamadas a funciones comunes de Lisp es: evaluar los argumentos de izquierda a derecha, y pasarlos a la función denotadoa por el operador. El operador cita tiene su propia regla: devolver el argumento sin cambios, literalmente.

4. Junto a los tipos de datos habituales, Lisp tiene símbolos y listas. Dado que los programas Lisp se expresan en las listas, es fácil escribir programas que escriben programas.

5. Las tres funciones básicas de la lista son cons que genera una lista; car , que devuelve el primer elemento y cdr, que devuelve todo lo que sigue al primer elemento.

6. En Common Lisp, t representa verdadero y falso se representa mediante nul. En un contexto lógico todo, excepto nil cuenta como verdadero. La condicional básica es if. El and y or se asemejan a los operadores condicionales.

Page 21: Apuntes sobre programación IA y Lisp

7. Lisp se compone principalmente de funciones. Puede definirse nuevas funciones medianta defun.

8. Una función que se llame a sí misma recursiva. Una función recursiva debe ser considerado como un proceso.

9. Las funciones I / O básicas son read , que incluye un parser Lisp completo , y format, que genera salidas sobre la base de plantillas.

10. Pueden crear senuevas variables locales con let, y variables globales con defparameter.

11. El operador de asignación es setf. Su primer argumento puede ser una expresión.

12. La programación funcional, que evita efectos secundarios, es el paradigma dominante en Lisp.

13. El operador de iteración básica es do.

14. Las funciones son objetos regulares de Lisp. Se pueden pasar como argumentos y se pueden denotar mediante las expresiones lambda.

15. En Lisp, los valores tienen tipos, las variables no.

BIBLIOGRAFÍA

Graham, ANSI Common Lisp, Prentice Hall 1995, caps. 1 y 2

Winston-Horn , LISP, Addison-Wesley Iberoamericana 1991