Subido por Katty Guevara

xdoc.mx-leccion-6-entrada-salida-y-errores

Anuncio
LECCION 6.
ENTRADA/SALIDA Y ERRORES.
En esta lección se exponen y aplican las construcciones Lisp que permiten la comunicación
con el mundo exterior (E/S), así como algunas de las que permiten la señalización y el manejo de
errores de ejecución. Al finalizar la lección, el alumno debe ser capaz de
-escribir funciones que se comuniquen con el usuario a través del teclado y la pantalla (ej. 1, 2
y 3).
-manejar datos de tipo carácter y cadena (ej. 2)
-escribir funciones que se comuniquen con ficheros del sistema (ej. 4 y 5).
-modificar el lector del sistema Lisp mediante el uso de caracteres macro (ej. 6 y 7).
-escribir funciones que se comporten de forma robusta ante los errores de ejecución (ej. 8, 9 y
10)
"Hello world!"
(Programador C anónimo).
Inteligencia Artificial e I.C.. 2002/2003
6.1
Dpto. Leng. Ciencias Comp.
Inteligencia Artificial e I.C.. 2002/2003
6.2
Dpto. Leng. Ciencias Comp.
R.6.1. E/S de supervivencia.
a) Escribir una función sin argumentos que devuelva la suma de dos números leídos del teclado.
b) Escribir una función sin argumentos que devuelva NIL y como efecto lateral escriba en la pantalla: i)
dos mensajes solicitando cada uno que se teclee un número y ii) el resultado de su suma.
c) Escribir una función EVALQUOTE que modifique la interacción con LISP de la siguiente forma:
- el aviso (“prompt”) del intérprete debe ser EVALQUOTE>
-si el usuario teclea >EVAL, se vuelve al modo habitual
-en otro caso, el usuario debe teclear un nombre de función (en sentido estricto, es decir, no valen
formas especiales ni macros) y la lista de sus argumentos sin comillas. Por ejemplo
> EVALQUOTE
EVALQUOTE> CONS(A (B))
(A B)
EVALQUOTE> EVAL
>
**************************
SOLUCION:
a) READ es una función que tiene el siguiente significado funcional predefinido:
(READ)
-ningún argumento (de momento).
-el valor devuelto se toma de la corriente de entrada por defecto. Por ahora, podemos suponer que se
toma del teclado.
Según esto, la solucion será
(DEFUN KK1 ()
(+ (READ) (READ)))
b) PRIN1 es una función que tiene el siguiente significado funcional predefinido:
(PRIN1 expresión)
-un único argumento.
-el valor devuelto es del argumento.
-como efecto lateral, el valor es enviado a la corriente de salida por defecto. Por ahora podemos
suponer que el valor se escribe en la pantalla.
(TERPRI)
escribe una línea en blanco.
(PRINT expresión)
es como PRIN1, con la siguiente diferencia:
-como efecto lateral, el valor es enviado a la corriente de salida por defecto, precedido por un salto de
línea y seguido por un espacio.
Por el ap. anterior, sabemos que
(+ (READ) (READ))
calcula la suma de dos números leídos del teclado. Para escribirla tendremos que hacer
(PRINT (+ (READ) (READ)))
Sin embargo, de esta forma no consegimos escribir los mensajes que piden los datos. Para ello hay
que añadir para cada READ un PRINT que lo abrace:
(PRINT (+ (CADR (LIST (PRINT '(DAME UN NUMERO)) (READ)))
(CADR (LIST (PRINT '(DAME UN NUMERO)) (READ)))))
y finalmente
(DEFUN KK2 ()
(PRINT (+ (CADR (LIST (PRINT '(DAME UN NUMERO)) (READ)))
(CADR (LIST (PRINT '(DAME UN NUMERO)) (READ)))))
NIL)
NOTA:
El intérprete escribe siempre en la pantalla el valor de la expresión que se le teclea. Por tanto, si la
anterior expresión se teclea directamente al intérprete, el resultado aparecerá dos veces en la pantalla:
una, por el funcionamiento normal del intérprete; otra, como efecto lateral de su evaluación.
Inteligencia Artificial e I.C.. 2002/2003
6.3
Dpto. Leng. Ciencias Comp.
NOTA:
[Ch] afirman (p. 29): "LISP tiene varias funciones, como PRINT o MAPC, que existen solamente por sus efectos
laterales. No es en absoluto una buena idea depender de sus valores". Sin embargo, el valor de PRINT está
perfectamente especificado en la definición de Common Lisp, y el empleo de este valor no debe ocasionar
problemas.
c) Una definición recursiva por la cola:
(DEFUN EVALQUOTE ()
(PRINT 'EVALQUOTE>)
(LET ((F (READ)))
(COND ((EQ F '>EVAL) NIL)
( T (PRINT (APPLY F (READ)))
(EVALQUOTE)))))
;escribir nueva línea y el aviso
;leer la funcion f
;si es >EVAL, acabar, si no
;leer lista, aplicar f,
;escribir nueva línea y f(lista)
;y seguir en evalquote
NOTA. El funcionamiento normal del intérprete puede describirse como un bucle READ-EVALPRINT: se lee una expresión con READ, se evalúa con EVAL y se escribe el resultado con PRINT. El
ejercicio anterior muestra cuán fácilmente se puede simular o modificar este bucle.
Inteligencia Artificial e I.C.. 2002/2003
6.4
Dpto. Leng. Ciencias Comp.
R.6.2. Caracteres y cadenas.
a) Evaluar las siguientes expresiones:
#\A
#\Space
(CHARACTER 41)
(EQ #\A #\a)
(EQ #\A #\A)
(EQL #\A #\A)
(CHAR= #\A #\A)
(CHAR= #\SPACE (CHARACTER 32))
(CHAR-EQUAL #\A #\a)
b) Evaluar las siguientes expresiones:
"Hoy es jueves"
(EQUAL #\A "A")
(EQL "AB" "AB")
(EQUAL "AB" "AB")
(EQUAL (STRING 'A) (STRING #\A))
(EQ (INTERN "ABC") 'ABC)
(ELT (STRING 'PEPE) 1)
(LENGTH (STRING 'PEPE))
(CONCATENATE 'STRING "ABC" "XYZ")
(EQUAL "PEPE" "PEPE")
(STRING= "PEPE" "pepe")
(STRING-EQUAL "PEPE" "pepe")
c) Indicar qué se imprime al introducir sucesivamente las siguientes expresiones:
(PRINC #\k)
(PRINC "pEPE")
(PRINC "C:\\PEPE")
(PRINC "PEPE\"")
(PRIN1 "PEPE")
(DEFUN MI-PRINC (S) (TERPRI) (PRINC S))
(MI-PRINC "PEPE")
**************************
SOLUCION:
a) Además de números, símbolos, funciones y listas, CommonLisp tiene predefinidos otros tipos de
datos: por ejemplo, los caracteres (character). Los caracteres son átomos y se evalúan a sí mismos.
Para indicar que algo es un carácter, el usuario (y PRINT) lo escribe precedido de #\. La función
(CHARACTER n) devuelve el carácter designado por n (la forma concreta de designación depende de
la implementación; por ejemplo, n puede ser el código ASCII del carácter). Por tanto
#\A
=>#\A
#\Space
=>#\Space
(CHARACTER 41) => #\)
No es obligado que distintas apariciones de un mismo carácter sean EQ, pero sí que sean EQL. Las
funciones especializadas CHAR= y CHAR-EQUAL sirven para comparar únicamente caracteres. CHAREQUAL no distingue entre mayúsculas y minúsculas. Por supuesto, un símbolo cuyo nombre conste de
un solo carácter no es lo mismo que un carácter. Por tanto
(EQ #\A #\a)
=> NIL
(EQ #\A #\A)
=> ...depende de la implementación...
(EQL #\A #\A)
=> T
(CHAR= #\A #\A)
=> T
(CHAR= #\SPACE (CHARACTER 32))
=> T
(CHAR-EQUAL #\A #\a)
=> T
b) El tipo cadena (string) está también predefinido. Las cadenas son átomos y se evalúan a sí
mismas. Una cadena de un solo carácter no es lo mismo que un carácter. Para indicar que algo es una
cadena, el usuario (y PRINT) lo escribe entre comillas dobles. La función STRING transforma
caracteres y símbolos en cadenas. La función INTERN transforma cadenas en símbolos. Por tanto
Inteligencia Artificial e I.C.. 2002/2003
6.5
Dpto. Leng. Ciencias Comp.
"Hoy es jueves"
=> "Hoy es jueves"
(EQUAL #\A "A")
=> NIL
(EQL "AB" "AB")
=> NIL
(EQUAL "AB" "AB") => T
(EQUAL (STRING 'A) (STRING #\A))
=> T
(EQ (INTERN "ABC") 'ABC)
=> T
La función (ELT cadena n) extrae el n-ésimo carácter de cadena (el primero es el 0-ésimo).
(ELT (STRING 'PEPE) 1) => #\E
La función LENGTH da la longitud de una cadena:
(LENGTH (STRING 'PEPE)) => 4
Nótese que la misma función LENGTH sirve para listas y de cadenas. De la misma forma, ELT puede
emplearse también para extraer el enésimo elemento de una lista. La razón es que estas funciones, y
otras muchas, están definidas para el tipo sucesión (sequence), que comprende listas, cadenas y
vectores.
La función CONCATENATE, por ejemplo, sirve para APPENDar tato listas como cadenas. Su primer
argumento es un símbolo que indica el tipo del resultado. Por tanto
(CONCATENATE 'STRING "ABC" "XYZ") => "ABCXYZ"
y también tendríamos
(CONCATENATE 'LIST '(A B C) '(D C)) => (A B C D C)
(CONCATENATE 'LIST "ABC" "XYZ") => (#\A #\B #\C #\X #\Y #\Z)
No es obligado que distintas apariciones de una misma cadena sean EQL, pero sí que sean EQUAL.
Las funciones especializadas STRING= y STRING-EQUAL sirven para comparar únicamente cadenas,
análogamente a CHAR= y CHAR-EQUAL. Por tanto
(EQUAL "PEPE" "PEPE")
=> T
(STRING= "PEPE" "pepe") => NIL
(STRING-EQUAL "PEPE" "pepe") => T
c) La función PRINC es análoga a PRIN1, con la diferencia de que escribe sin delimitadores cadenas y
caracteres:
> (PRINC #\k)k
#\k
> (PRINC "pEPE")pEPE
"pEPE"
> (PRIN1 "PEPE")"PEPE"
"PEPE"
El carácter \ sirve como carácter de escape dentro de una cadena, para indicar que el siguiente
carácter debe tomarse literalmente. Su empleo es necesario si se desea que " o \ formen parte de la
cadena:
> (PRINC "C:\\PEPE")C:\PEPE
"C:\\PEPE"
> (PRINC "PEPE\"")PEPE"
"PEPE\""
> (DEFUN MI-PRINC (S) (TERPRI) (PRINC S))
MI-PRINC
> (MI-PRINC "PEPE")
PEPE
"PEPE"
.
NOTA: La diferencia entre PRIN1 y PRINC puede resumirse como sigue: PRIN1 escribe su argumento
respetando las convenciones exigidas por READ; PRINC escribe su argumento de forma que un ser
humano lo lea cómodamente.
Inteligencia Artificial e I.C.. 2002/2003
6.6
Dpto. Leng. Ciencias Comp.
R.6.3. FORMAT
Escribir una función KK1 que lleve a cabo la siguiente tarea: solicitar al usuario que introduzca una
expresión numérica y escribir en distintos renglones los valores de su cubo y su cuadrado, con los
correspondientes mensajes, en formato decimal y con dos decimales. En caso de introducir un dato no
numérico se dará al usuario un mensaje adecuado y se le solicitará de nuevo un valor. La función
devuelve siempre T. Por ejemplo:
Dime una expresión: PEPE
PEPE no es un número, lo siento.
Dime una expresión: 2E-1
El cuadrado es
0.04 y el cubo es
0.00.
Adiós.
T
**************************
SOLUCION:
Para conseguir salidas formateadas se emplea la función FORMAT:
(FORMAT destino cadena-control expresión*)
donde
destino es la corriente de salida. Si es T, se toma la corriente de salida por defecto (normalmente, la
pantalla); si es NIL, se crea una cadena que contiene la salida.
expresión* es una sucesión de expresiones, que se evalúan secuencialmente para obtener valor1,
..., valorn.
cadena-control es una cadena.
Si destino es NIL, FORMAT devuelve la cadena descrita más adelante.
En otro caso, FORMAT se evalúa a NIL y como efecto lateral escribe en destino los valores valor1, ...,
valorn, formateados de acuerdo a las instrucciones de la cadena de control. Concretamente:
-La cadena de control se escribe literalmente en la corriente de salida, a excepción de los caracteres
precedidos por ~, que se denominan directivas de formateo.
-La directiva ~S hace que en su lugar se escriba (como con PRIN1) uno de los valores valor1, ...,
valorn. La directiva ~A hace que en su lugar se escriba (como con PRINC) uno de los valores valor1,
..., valorn. El valor es determinado secuencialmente (el primer valor con el primer ~S o ~A, ...). Si hay
más directivas que valores, se produce un error. Si hay más valores que directivas, no se produce por
ello error: simplemente, los últimos valores no se escriben.
-La directiva ~% escribe un salto de línea.
-La directiva ~<nueva línea> ignora la <nueva línea> y los blancos que la sigan.
Por ejemplo
>(FORMAT T "~%Hoy es ~S de ~S de ~S." 12 "enero" 1492)
Hoy es 12 de "enero" de 1492.
NIL
>(FORMAT T "~%Hoy es ~S de ~A de ~S." 12 "enero" 1492)
Hoy es 12 de enero de 1492.
NIL
>(FORMAT NIL "~%Hoy es ~S de ~A de ~S." 12 "enero" 1492)
"
Hoy es 12 de enero de 1492."
-La directiva ~n,mF escribe un número en formato decimal, como mínimo con un total de n caracteres,
empleando m decimales.
Por ejemplo
>(FORMAT T "~% Un número:~10,3F.~% El mismo número:~5,2F.~% Y otra vez ~
el mismo número:~2,0F." 123.1 123.1 123.1)
Un número:
123.100.
El mismo número:123.10.
Y otra vez el mismo número:123..
NIL
Según esto
(DEFUN MENSAJE (CADENA)
Inteligencia Artificial e I.C.. 2002/2003
6.7
Dpto. Leng. Ciencias Comp.
(PRINC CADENA)
(READ))
(DEFUN KK1 ()
(LET ((S (MENSAJE "Dime una expresion: ")))
(COND((NUMBERP S)
(FORMAT T "~%El cuadrado es ~6,2F y el cubo es ~6,2F~
.~%Adios."
(* S S) (* S S S))
T)
(T
(FORMAT T "~%~S no es un numero, lo siento." S)
(KK1)))))
NOTA: (FORMAT T ...) devuelve NIL. Para respetar la definición especificada de KK1, es necesario
por tanto introducir explícitamente T como valor del COND. Compárese con PRIN1 o PRINT, que
devuelven el valor de su argumento.
NOTA. La definición completa de FORMAT ocupa 29 páginas de CLtL2 (581-609), y otras tantas del
estándar ANSI. El alumno interesado puede consultar allí todas las posibilidades de formateo
(numérico, alfanumérico, condicional, mediante funciones, ...) que FORMAT proporciona.
Inteligencia Artificial e I.C.. 2002/2003
6.8
Dpto. Leng. Ciencias Comp.
R.6.4. Secuenciación.
Evaluar las siguientes expresiones:
(PROGN (+ 1 2 3) (CAR '(A B)))
(PROG1 (+ 1 2 3) (CAR '(A B)))
(LOOP (+ 1 2 3) (CAR '(A B)))
(LOOP (PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN))
(PRINT 'OIDO))
(LOOP (PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN T))
(PRINT 'OIDO))
(BLOCK PEPE (+ 1 2 3) (CAR '(A B)))
(BLOCK PEPE
(PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN-FROM PEPE))
ESTO ES BASURA)
(BLOCK PEPE
(BLOCK JUAN
(PRINT 'DIGA?)
(IF (EQ (READ) 'PEPE)
(RETURN-FROM PEPE 'ADIOS)
(RETURN-FROM JUAN 'JA)))
ESTO ES BASURA)
**************************
SOLUCION:
La secuenciación es la forma básica en que fluye la ejecución de los programas imperativos. Sin
embargo, en los programas funcionales la secuenciación tiene una importancia más reducida. De
hecho, hasta ahora no la hemos mencionado explícitamente.
Sin embargo, al programar mediante "efectos laterales" (entrada/salida, asignación), es inevitable el
uso de la secuenciación. Los operadores más sencillos son PROGN y PROG1, que evalúan
secuencialmente las expresiones de su cuerpo y devuelven el último valor o el primero,
respectivamente:
(PROGN (+ 1 2 3) (CAR '(A B)))
=> A
(PROG1 (+ 1 2 3) (CAR '(A B)))
=> 6
NOTA. Muchas de las construcciones ya estudiadas (DEFUN, COND, ...) ejecutan siempre
secuencialmente las expresiones de su cuerpo. Pero nótese que en IF y en las inicializaciones y
actualizaciones de LET y DO es necesario para ello un PROGN explícito.
El operador LOOP evalúa secuencialmente las expresiones de su cuerpo, una y otra vez; en principio,
no devuelve ningún valor:
(LOOP (+ 1 2 3) (CAR '(A B)))
=> ...computación infinita...
Sin embargo, si dentro del cuerpo se evalúa una forma (RETURN), finaliza el cálculo y LOOP devuelve
NIL. El ámbito desde el cual se puede RETURNar se determina léxicamente.
(LOOP (PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN))
(PRINT 'OIDO))
=>
DIGA? HOLA
OIDO
DIGA? FIN
Inteligencia Artificial e I.C.. 2002/2003
6.9
Dpto. Leng. Ciencias Comp.
NIL
Si se evalúa (RETURN e), se devuelve el valor de e:
(LOOP (PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN T))
(PRINT 'OIDO))
=>
DIGA? HOLA
OIDO
DIGA? FIN
T
El operador más general que establece la posibilidad de RETURNar es BLOCK, que se emplea en
formas como
(BLOCK nombre expresión*)
nombre es un símbolo que no se evalúa. Las restantes expresiones se evalúan secuencialmente y
BLOCK devuelve el valor de la última. Pero si alguna de ellas es de una forma
(RETURN-FROM nombre resultado)
el valor de resultado pasa a ser el de la forma BLOCK y las restantes expresiones quedan sin
evaluar.
(BLOCK PEPE (+ 1 2 3) (CAR '(A B)))
=> A
(BLOCK PEPE
(PRINT 'DIGA?)
(WHEN (EQ (READ) 'FIN)
(RETURN-FROM PEPE))
ESTO ES BASURA)
=>
DIGA? FIN
NIL
Los bloques pueden anidarse. En los anidamientos se siguen las reglas de ámbito léxico.
(BLOCK PEPE
(BLOCK JUAN
(PRINT 'DIGA?)
(IF (EQ (READ) 'PEPE)
(RETURN-FROM PEPE 'ADIOS)
(RETURN-FROM JUAN 'JA)))
ESTO ES BASURA)
=>
DIGA? PEPE
ADIOS
(BLOCK PEPE
(BLOCK JUAN
(PRINT 'DIGA?)
(IF (EQ (READ) 'PEPE)
(RETURN-FROM PEPE 'ADIOS)
(RETURN-FROM JUAN 'JA)))
ESTO ES BASURA)
=>
DIGA? NO-PEPE
Error: ESTO sin ligar.
NOTA. DEFUN establece implícitamente un bloque con el nombre de la función. LOOP, DO y sus
variantes establecen implícitamente un bloque con el nombre NIL. En lugar de RETURN-FROM NIL
se puede escribir simplemente RETURN
Inteligencia Artificial e I.C.. 2002/2003
6.10
Dpto. Leng. Ciencias Comp.
R.6.5. Manejo elemental de archivos.
Sea un sistema bajo MS-DOS. En un fichero llamado KK.DAT, situado en el directorio C:\DBVARIOS,
se almacenan las lecturas de un sensor, tomadas cada hora. El primer dato del archivo es la fecha
(formato dd-mm-aa), el segundo la hora de la primera lectura y el tercero los minutos de la primera
lectura.
a) Evaluar las siguientes expesiones, indicando los efectos laterales:
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT) 1 2 3 F1)
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(READ F1)(READ F1))
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\QQ.DAT" :DIRECTION :OUTPUT)
(PRIN1 'PEPE F1) (PRIN1 2 F1))
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (READ F1)))
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (READ F1 NIL)))
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (UNLESS (READ F1 NIL) (RETURN NIL))))
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
EE)
b) Escribir una función LISP LEE1 que lea el archivo y escriba en pantalla una tabla como la siguiente:
Datos del día 17-2-96
HORA
LECTURA
====
=======
00:20
123.14
01:20
0.00
02:20
23.01
...
c) Escribir una función LISP LEE2 que lea el archivo y escriba una tabla como la anterior en un fichero
KK.TXT.
**************************
SOLUCION:
En CommonLisp, una fuente o sumidero de datos se denomina corriente (stream). Una corriente es
un objeto Lisp, y por tanto puede ser el valor de la ligadura de un símbolo. Por otra parte, una corriente
debe corresponder a un objeto del mundo exterior, de donde se toman los datos o a donde se envían.
Las funciones de E/S ya vistas (READ, PRINT, ...) pueden operar sobre cualquier corriente. Para ello la
corriente se introduce como argumento adicional:
(READ expresión-c)
(PRIN1 expresión expresión-c)
(PRINC expresión expresión-c)
(PRINT expresión expresión-c)
En cuanto a FORMAT, ya hemos dicho que su primer argumento puede ser, además de T o NIL,
cualquier corriente:
(FORMAT expresión-c cadena-control expresión*)
En todos estos casos, expresión-c debe evaluarse a una corriente.
Una corriente puede ser binaria o de caracteres; puede usarse para sólo lectura, sólo escritura o
ambas operaciones a la vez; y puede estar abierta (accesible como fuente o sumidero de datos) o
cerrada (inaccesibe).
La manera más sencilla y recomendable de crear corrientes para comunicarse con ficheros, y además
ligarlas a símbolos, es emplear la forma WITH-OPEN-FILE:
(WITH-OPEN-FILE (símbolo expr-fichero opción*)expr*)
donde expr-fichero es una expresión que debe evaluarse a un nombre de fichero valido según las
convenciones del sistema operativo subyacente. La evaluación de WITH-OPEN-FILE crea
primeramente una corriente ligada a este fichero, que queda ligada a símbolo en todo el ámbito de la
evaluación. Esta corriente se abre de la forma señalada por opción*. De esta forma se evalúan las
expresiones siguientes. Finalmente se cierra la corriente. El valor devuelto por WITH-OPEN-FILE es
el de la última expresión. El archivo siempre queda cerrado, aun cuando la evaluación de WITH-OPENFILE haya terminado prematuramente.
Inteligencia Artificial e I.C.. 2002/2003
6.11
Dpto. Leng. Ciencias Comp.
La corriente se describe mediante los siguientes parámetros clave:
:DIRECTION. Puede ser :INPUT, :OUTPUT, :IO. Por defecto es :INPUT
:ELEMENT-TYPE. Puede ser CHARACTER, BIT, SIGNED-BYTE, UNSIGNED-BYTE... Por defecto
es CHARACTER (en realidad, un subtipo de CHARACTER).
Por tanto
a)
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT) 1 2 3 F1)
=> #<closed-file-stream C:\DBVARIOS\KK.DAT #xE2F5D0>
(Se ha creado una corriente para lectura de caracteres asociada al archivo C:\DBVARIOS\KK.DAT,
se ha abierto y se ha cerrado).
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(READ F1)(READ F1))
=> 0
(Se ha creado una corriente para lectura de caracteres asociada al archivo C:\DBVARIOS\KK.DAT y
se ha abierto. El primer READ ha leído el primer dato del archivo -la fecha- y el segundo, el segundo
dato -la hora de la primera lectura, que es 0- Finalmente, el archivo se ha cerrado).
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\QQ.DAT" :DIRECTION :OUTPUT)
(PRIN1 'PEPE F1) (PRIN1 2 F1))
=>2
(Se ha creado una corriente para escritura de caracteres asociada al archivo C:\DBVARIOS\QQ.DAT y
se ha abierto. El primer PRIN1 ha escrito PEPE y el segundo, 2. Finalmente, el archivo se ha cerrado.
Nótese que PRIN1 no añade blancos ni saltos de línea, por lo que el contenido del archivo es ahora
PEPE2).
Los siguiente ejemplos hacen uso del operador LOOP. En el caso más sencillo, (LOOP cuerpo)
evalúa una y otra vez las expresiones que forman cuerpo. Por tanto
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (READ F1)))
=> Error: intento de leer más allá del fin del fichero.
(Se ha creado una corriente para lectura de caracteres asociada al archivo C:\DBVARIOS\KK.DAT y
se ha abierto. Se han ejecutado los READ hasta llegar al fin del fichero, donde se ha producido un
error).
El error anterior se puede evitar, añadiendo más argumentos a READ:
(READ expresión-c error-eof-p [valor-eof])
Si error-eof-p es falso, no se produce el error de fin de fichero, sino que READ devuelve valoreof (valor por defecto, NIL) cuando intenta ir más allá. Por tanto:
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (READ F1 NIL)))
...
(bucle infinito: no se produce error y READ se evalúa a NIL una vez alcanzado el fin del fichero)
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LOOP (UNLESS (READ F1 NIL) (RETURN NIL))))
=> NIL
(DEFUN F (N) (+ N EE)) => F
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(F 1))
=> Error: EE sin ligar.
Tanto en este caso como en alguno de los anteriores, se ha producido un error de ejecución mientras
se estaba leyendo o escribiendo un fichero. En general, esto puede tener consecuencias fatales para
la información contenida en él; sin embargo, la forma WITH-OPEN-FILE se asegura de que el fichero
no se perjudique. Sea cual sea el error que haga abortar la evaluación de una forma WITH-OPENFILE, el sistema garantiza que el fichero queda cerrado. Aún más: el fichero queda cerrado
independientemente de que el error se haya producido en un entorno –como el del último ejemplodonde léxicamente no estaba vigente la ligadura que lo señala.
Inteligencia Artificial e I.C.. 2002/2003
6.12
Dpto. Leng. Ciencias Comp.
NOTA. Este efecto puede programarse explícitamente con una forma UNWIND-PROTECT.
b) Hay que abrir para lectura de caracteres el fichero C:\DBVARIOS\KK.DAT. Primero se lee la fecha,
hora y minutos iniciales. A continuación se leen los datos del sensor. Como no sabemos cuántos hay,
leemos con (READ NIL 'EOF) para evitar el error de fin de fichero:
(DEFUN LEE1 ()
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(LET ((FECHA (READ F1))
(HORA (READ F1))
(M (READ F1)))
(FORMAT T "~%Datos del día ~A" FECHA)
(FORMAT T "~%HORA
LECTURA")
(FORMAT T "~%====
=======")
(DO ((L (READ F1 NIL 'EOF)(READ F1 NIL 'EOF))
(H HORA (1+ H)))
((EQ L 'EOF) NIL)
(FORMAT T "~%~2,D:~2,D
~6,2F" H M L)))))
c) Al programa anterior hay que añadirle la creación de una corriente de salida. Nótese que pueden
crearse cuantas corrientes sea necesario, así como tenerlas simultáneamente abiertas. Nótese
también que FORMAT escribe en el fichero exactamente lo que escribiría en la pantalla si el primer
argumento fuera T.
(DEFUN LEE2 ()
(WITH-OPEN-FILE (F1 "C:\\DBVARIOS\\KK.DAT" :DIRECTION :INPUT)
(WITH-OPEN-FILE (FS "C:\\DBVARIOS\\KK.TXT" :DIRECTION :OUTPUT)
(LET* ((FECHA (READ F1))
(HORA (READ F1))
(M (READ F1)))
(FORMAT FS "~%Datos del día ~A" FECHA)
(FORMAT FS "~%HORA
LECTURA")
(FORMAT FS "~%====
=======")
(DO ((L (READ F1 NIL 'EOF)(READ F1 NIL 'EOF))
(H HORA (1+ H)))
((EQ L 'EOF) NIL)
(FORMAT FS "~%~2,D:~2,D
~6,2F" H M L))))))
Inteligencia Artificial e I.C.. 2002/2003
6.13
Dpto. Leng. Ciencias Comp.
R.6.6. Lectura de líneas y caracteres.
a) Escribir una función CODIFICAR, que tenga como argumentos dos nombres de fichero ENTRADA y
SALIDA y produzca como efecto lateral la escritura en SALIDA de los caracteres contenidos en
ENTRADA codificados según la siguiente regla: si el código ASCII de c es n, su codificación es el
carácter de código (n +23) mod 128.
b) Escribir en LISP una función TFD que corresponda a un traductor finito determinista para convertir
una cadena de caracteres en una lista de tokens. Se suponen los tokens "variable" y "constante" y las
reglas de Prolog para definir ambos. Para separar los diversos identificadores en la entrada se admiten
blancos, comas, tabuladores y saltos de línea. La función debe devolver para cada par (estado,
carácter de entrada) el nuevo estado y el token emitido correspondiente (NIL si no se emite ninguno).
El fin de la cadena de entrada se supone señalado por el carácter "$".
c) Escribir en LISP una función TRADUCIR que tenga como argumentos un TFD y una cadena de
caracteres, y devuelva la traduccion de la cadena hasta donde haya sido posible hacerla. Además,
como valor secundario, se devolverá T si la traducción se ha podido efectuar por completo, NIL si ha
acabado en fracaso.
d) Escribir en LISP una función TRADUCIR-FICHERO-TEXTO que tenga como argumentos el cierre
léxico de un TFD y dos nombres de fichero ENTRADA y SALIDA. La función escribirá como efecto
lateral la traduccion de ENTRADA en SALIDA hasta donde haya sido posible hacerla. El valor de la
función será T si la traducción se ha podido efectuar por completo, NIL si ha acabado en fracaso.
**************************
SOLUCION:
Hemos dicho que READ lee una a una las expresiones LISP. Pero en realidad no sólo lee, sino que
lleva a cabo un análisis lexicográfico y sintáctico de lo leído, para determinar, por ejemplo, dónde
acaba cada expresión. Existen otras funciones de lectura más elementales, como por ejemplo READCHAR y READ-LINE, que sí se limitan a leer de una corriente.
(READ-CHAR expresión-c error-eof-p [valor-eof])
lee un carácter. La función devuelve precisamente ese carácter.
(READ-LINE expresión-c error-eof-p [valor-eof])
lee una línea completa, es decir, lee todos los caracteres hasta encontrar un salto de línea. La función
devuelve la cadena formada por todos los caracteres de la línea (salvo el de salto). Es por ello muy útil
para leer del teclado. READ-LINE devuelve también un segundo valor: "verdadero", si la línea acaba
con el carácter de fin de fichero, "falso" en otro caso.
Por tanto
a) La función que realiza la codificación de un carácter es
(LAMBDA (C)
(CHARACTER (MOD (+ (CHAR-CODE C) 23) 128)))
Leeremos de ENTRADA carácter a carácter, realizaremos la transformación y escribiremos en SALIDA
carácter a carácter:
(DEFUN CODIFICAR (&KEY ENTRADA SALIDA)
(FLET ((DESPLAZAR (C)
(CHARACTER (MOD (+ (CHAR-CODE C) 23) 128))))
(WITH-OPEN-FILE (CE ENTRADA :DIRECTION :INPUT)
(WITH-OPEN-FILE (CS SALIDA :DIRECTION :OUTPUT)
(LOOP
(LET ((C (READ-CHAR CE NIL :EOF)))
(WHEN (EQ C :EOF) (RETURN))
(PRINC (DESPLAZAR C) CS)))))))
b) Un TFD queda definido por los siguientes elementos:
-la tabla de transiciones. Cada transición es una tripla o cuádrupla de la forma (estado carácter-leído
nuevo-estado [símbolo-emitido]). Cuando el TFD está en estado y lee carácter-leído pasa a nuevoestado y emite símbolo-emitido, caso de que exista. Si en la tabla no hay ninguna transición que
empiece por esta combinación (estado carácter-leído ...), la traducción acaba en fracaso. Nunca hay
más de una transición para una misma combinación (estado carácter-leído ...).
-un conjunto de estados finales. Si al acabar de leer la cadena de entrada el TFD se halla en un estado
final, la traducción ha acabado con éxito, caso contrario, en fracaso.
Inteligencia Artificial e I.C.. 2002/2003
6.14
Dpto. Leng. Ciencias Comp.
-un estado inicial, en el que se halla el TFD antes de leer ningún carácter.
Representaremos los elementos anteriores por los siguientes datos LISP:
- El segundo elemento de cada transición será un predicado, en lugar de un carácter. De esta forma
reducimos considerablemente el tamaño de la tabla. La transición se considera aplicable a todos los
caracteres que satisfacen el predicado.
Según la gramática de Prolog, son variables las cadenas alfanuméricas que comienzan por una letra
mayúscula o por "_". Son constantes las cadenas alfanuméricas que empiezan por una letra
minúscula.
(DEFUN SEPARADORP (C)
(MEMBER C (LIST #\Newline #\Space #\Tab #\,)))
(DEFUN TERMINADORP (C)
(EQL C #\$))
(DEFUN TERMINAR (CAD)
(CONCATENATE 'STRING CAD "$"))
(DEFUN VACIAP (CADENA)
(= 0 (LENGTH CADENA)))
El TFD será (todos los estados finales)
letra, dígito, _
E1
separador
mayúscula, _
E0
minúscula
letra, dígito, _
E2
Para considerar la terminación de la cadena, añadimos un estado T al que se llega al leer "$" en un
estado final:
letra, dígito, _
E1
separador
$
mayúscula, _
E0
T
minúscula
$
letra, dígito, _
E2
En Lisp
Inteligencia Artificial e I.C.. 2002/2003
6.15
Dpto. Leng. Ciencias Comp.
(DEFUN TFD1 (ESTADO C-LEIDO)
(CASE ESTADO
(E0 (COND ((TERMINADORP C-LEIDO) (VALUES T NIL))
((FUNCALL #'UPPER-CASE-P C-LEIDO) (VALUES 'E1 NIL))
((FUNCALL #'LOWER-CASE-P C-LEIDO) (VALUES 'E2 NIL))
((FUNCALL #'(LAMBDA (C) (EQL C #\_)) C-LEIDO)
(VALUES 'E1 NIL))
((FUNCALL #'SEPARADORP C-LEIDO) (VALUES 'E0 NIL))))
(E1 (COND ((TERMINADORP C-LEIDO) (VALUES T 'VAR))
((FUNCALL #'ALPHA-CHAR-P C-LEIDO) (VALUES 'E1 NIL))
((FUNCALL #'(LAMBDA (C) (EQL C #\_)) C-LEIDO)
(VALUES 'E1 NIL))
((FUNCALL #'DIGIT-CHAR-P C-LEIDO) (VALUES 'E1 NIL))
((FUNCALL #'SEPARADORP C-LEIDO) (VALUES 'E0 'VAR))))
(E2 (COND ((TERMINADORP C-LEIDO) (VALUES T 'CTE))
((FUNCALL #'ALPHA-CHAR-P C-LEIDO) (VALUES 'E2 NIL))
((FUNCALL #'(LAMBDA (C) (EQL C #\_)) C-LEIDO)
(VALUES 'E2 NIL))
((FUNCALL #'DIGIT-CHAR-P C-LEIDO) (VALUES 'E2 NIL))
((FUNCALL #'SEPARADORP C-LEIDO) (VALUES 'E0 'CTE))))))
c) La función empleará por acumuladores, uno para la salida y otro para el estado en que se encuentra
el traductor. Por otra parte, la entrada se completa en un primer paso con el carácter terminador:
(DEFUN TRADUCIR (TFD ENTRADA &OPTIONAL SALIDA (ESTADO 'E0))
(LABELS ((TTRAD (TFD ENTRADA SALIDA ESTADO)
(COND ((VACIAP ENTRADA)
(VALUES (REVERSE SALIDA) ESTADO))
(T
(MULTIPLE-VALUE-BIND (NUEVO-E NUEVA-SAL)
(FUNCALL TFD ESTADO (ELT ENTRADA 0))
(COND ((NOT NUEVO-E)
(VALUES (REVERSE SALIDA) NIL))
((NOT NUEVA-SAL)
(TTRAD TFD (SUBSEQ ENTRADA 1)
SALIDA NUEVO-E))
(T
(TTRAD TFD (SUBSEQ ENTRADA 1)
(CONS NUEVA-SAL SALIDA)
NUEVO-E))))))))
(LET ((ENTRADA (TERMINAR ENTRADA)))
(TTRAD TFD ENTRADA SALIDA ESTADO))))
d) No hay más que llamar sucesivamente a TRADUCIR con las diversas líneas de ENTRADA,
escribiendo al tiempo las traducciones en SALIDA.
(DEFUN TRADUCIR-FICHERO-TEXTO (&KEY TRADUCTOR ENTRADA SALIDA)
(WITH-OPEN-FILE (CORR1 ENTRADA :DIRECTION :INPUT)
(WITH-OPEN-FILE (CORR2 SALIDA :DIRECTION :OUTPUT)
(LOOP
(LET ((LINEA (READ-LINE CORR1 NIL :EOF)))
(WHEN (EQ LINEA :EOF) (RETURN T))
(MULTIPLE-VALUE-BIND (TRADUCC EXITO)
(TRADUCIR TRADUCTOR LINEA)
(WHEN (NOT EXITO) (RETURN))
(PRINT TRADUCC CORR2)))))))
Inteligencia Artificial e I.C.. 2002/2003
6.16
Dpto. Leng. Ciencias Comp.
R.6.7. Caracteres Macro.
a) Evaluar en el orden dado las siguientes expresiones:
(SET-MACRO-CHARACTER #\?
#'(LAMBDA (CORRIENTE CARACTER)
(LIST 'A 'B 'C))))
'(? (?))
(SET-MACRO-CHARACTER #\?
#'(LAMBDA ()
(LIST 'A 'B 'C))))
'(? (?))
b) Queremos representar en un programa fórmulas de la lógica de primer orden. Para ello las variables
lógicas serán listas de la forma (VAR X), cuyo CAR es el símbolo VAR y cuyo CADR es el nombre X
de la variable. Modificar el significado de READ de manera que ?expresión se lea como (VAR
expresión).
**************************
SOLUCION:
Un carácter macro es un carácter tal que READ lo trata de manera especial cuando lo encuentra,
expandiéndolo en un conjunto de caracteres. Ya conocemos un carácter macro predefinido, el
apóstrofo: cuando READ (bien en el ciclo normal del intérprete, bien en una llamada explícita)
encuentra 'expresión, lo transforma inmediatamente en (QUOTE expresión).
Es posible definir nuevos caracteres macro mediante la función SET-MACRO-CHARACTER:
(SET-MACRO-CHARACTER carácter función)
donde carácter es el caracter macro que se define y función es la función de expansión, que que
determina lo que debe sustituir al carácter macro. Esta función debe tener dos argumentos: una
corriente y un carácter. SET-MACRO-CHARACTER devuelve T y como efecto lateral modifica el
significado de READ. A partir de este momento, cuando READ encuente el carácter macro lo sustituirá
por el resultado de aplicar la función. Por tanto
(SET-MACRO-CHARACTER #\?
#'(LAMBDA (CORRIENTE CARACTER)
(LIST 'A 'B 'C))))
=> T
'(? (?))
=>((A B C) ((A B C)))
En este sencillo caso la función de expansión no emplea sus argumentos. Sin embargo, es necesario
que figuren en su lista lambda:
(SET-MACRO-CHARACTER #\?
#'(LAMBDA ()
(LIST 'A 'B 'C))))
=>T
'(? (?))
=>Error: número equivocado de argumentos.
De hecho, lo más habitual será que la función de expansión contenga una llamada a (READ
corriente ...). En este caso hay que añadir un cuarto argumento a READ con valor T (¿por qué?
vd. nota).
b) La función siguiente realiza la modificación especificada:
(DEFUN HAZ-MI-READ ()
(SET-MACRO-CHARACTER #\?
#'(LAMBDA (CORRIENTE CARACTER)
(LIST 'VAR (READ CORRIENTE T NIL T)))))
Inteligencia Artificial e I.C.. 2002/2003
6.17
Dpto. Leng. Ciencias Comp.
> (HAZ-MI-READ)
T
> '?X
(VAR X)
> '??X
(VAR (VAR X))
NOTA: Es bastante peligroso definir como caracteres macro caracteres alfabéticos o de uso común: a
partir de ese momento, cualquier aparición de ellos se verá sustituida por su expansión. Por ejemplo,
tras ejecutar la función HAZ-MI-READ de este ejercicio, no es posible emplear en el programa el signo
de interrogación (salvo usando caracteres de escape).
NOTA: Los caracteres macro se pueden agrupar en una tabla de lectura (readtable). Pueden existir
varias tablas de lectura alternativas; según la que se cargue, READ leerá de una manera o de otra.
NOTA: la definición completa de READ es
(READ expresión-c error-eof-p valor-eof llamada-recursiva-p)
donde todos los argumentos son opcionales. El cuarto, llamada-recursiva-p, (valor por defecto,
NIL) indica si el READ se ejecuta en el nivel superior o bien proviene de la ejecución de otro READ.
Puede ser importante distinguir entre ambos casos (vd. CLtL, pg. 568-569)
Inteligencia Artificial e I.C.. 2002/2003
6.18
Dpto. Leng. Ciencias Comp.
R.6.8. Caracteres macro de envío.
a) Evaluar en el orden dado las siguientes expresiones:
(MAKE-DISPATCH-MACRO-CHARACTER #\z)
(SET-DISPATCH-MACRO-CHARACTER #\z #\z
#'(LAMBDA (CORRIENTE SUBCAR NUM)
(LIST 'A 'B 'C)))
'zz
'za
'ZZ
b) Ahora queremos escribir el cuantificador universal como $E, abreviatura de la lista (EXISTE
variable) y el universal como $U, abreviatura de la lista (PARA-TODO variable). Modificar
adecuadamente el significado de READ.
**************************
SOLUCION:
a) Un carácter macro de envío (dispatching macro character) también es un carácter tal que READ lo
trata de manera especial cuando lo encuentra, expandiéndolo en un conjunto de caracteres. Pero en
este caso la expansión realizada depende, no sólo del carácter macro, sino también del siguiente
carácter leído (subcaracter). Ya conocemos un carácter macro de envío predefinido, el sostenido:
cuando READ (bien en el ciclo normal del intérprete, bien en una llamada explícita) encuentra
#'expresión, lo transforma inmediatamente en (FUNCTION expresión).
Es posible definir nuevos caracteres macro de envío mediante las funciones MAKE-DISPATCHMACRO-CHARACTER y SET-DISPATCH-MACRO-CHARACTER:
(MAKE-DISPATCH-MACRO-CHARACTER carácter)
Esta función devuelve T y hace que carácter sea un carácter macro de envío. Pero aún no
establece su significado. Para ello es necesario emplear además
(SET-DISPATCH-MACRO-CHARACTER carácter subcarácter función)
donde carácter es el caracter macro de envío, subcarácter es el subcaracter macro de envío,
que se define y función es la función de expansión, que determina por lo que se debe sustituir esta
cadena de dos caracteres. Esta función debe tener tres argumentos: una corriente y un carácter, y un
número (vd. nota; si así lo desea, prescinda el alumno de conocer el significado de este número, pero
es un parámetro requerido). SET-DISPATCH-MACRO-CHARACTER devuelve T y como efecto lateral
modifica el significado de READ. A partir de este momento, cuando READ encuente la combinación
carácter-subcarácter la sustituirá por el resultado de aplicar la función. Por tanto
(MAKE-DISPATCH-MACRO-CHARACTER #\z)
=> T
(SET-DISPATCH-MACRO-CHARACTER #\z #\z
#'(LAMBDA (CORRIENTE SUBCAR NUM)
(LIST 'A 'B 'C)))
=> T
'zz
=> (A B C)
'za
=> Error: combinación ilegal z a
Nótese que READ señala un error cuando encuentra una combinación carácter-subcarácter no definida.
'ZZ
=> ZZ
'zZ
=> (A B C)
Nótese que en el subcarácter no se distingue entre mayúsculas y minúsculas, pero sí en el carácter.
b) Agrupemos en una sola función HAZ-MI-READ-2 todas las modificaciones pedidas en este ejercicio
y el anterior:
(DEFUN HAZ-MI-READ-2 ()
(SET-MACRO-CHARACTER #\?
#'(LAMBDA (CORRIENTE CARACTER)
Inteligencia Artificial e I.C.. 2002/2003
6.19
Dpto. Leng. Ciencias Comp.
(LIST 'VAR (READ CORRIENTE T NIL T))))
(MAKE-DISPATCH-MACRO-CHARACTER #\$)
(SET-DISPATCH-MACRO-CHARACTER #\$ #\E
#'(LAMBDA (CORRIENTE SUBCAR NUM)
(LIST 'EXISTE (READ CORRIENTE T NIL T))))
(SET-DISPATCH-MACRO-CHARACTER #\$ #\U
#'(LAMBDA (CORRIENTE SUBCAR NUM)
(LIST 'PARA-TODO (READ CORRIENTE T NIL T)))))
y ahora
>(HAZ-MI-READ)
T
>'$E?X
(EXISTE (VAR X))
>'$U ?X
(PARA-TODO (VAR X))
NOTA: la función de expansión de un carácter macro de envío debe tener 3 parámetros: la corriente, el
subcarácter y un número. Este número puede aparecer, como un natural en notación decimal, entre el
carácter y el subcarácter. De esta forma los caracteres macros de envío tienen mayor flexibilidad. Si no
hay ningún número, el tercer parámetro de la función de expansión se liga a NIL.
Inteligencia Artificial e I.C.. 2002/2003
6.20
Dpto. Leng. Ciencias Comp.
R.6.9. Manejo de errores (I).
Implementar versiones de la función factorial tales que
a) (FACTORIAL N) devuelva NIL cuando N no sea un número natural.
b) (FACTORIAL N) señale un error y aborte la computación cuando N no sea un número natural.
c) (FACTORIAL N) señale un error y aborte la computación cuando N no sea un número natural.
Opcionalmente, si el argumento proporcionado es numérico, se calculará el factorial de un número
“parecido” al dado: por ejemplo, el factorial de |n| si n es negativo, o de int(n) si n es fraccionario.
d) (FACTORIAL N) señale un error y aborte la computación cuando N no sea un número natural.
Opcionalmente, se solicitará al usuario un nuevo valor y se calculará el factorial de éste.
**************************
SOLUCION:
a) Partamos, por ejemplo, de la implementación recursiva ingenua:
(DEFUN FACTORIAL (N)
(COND ((ZEROP N) 1)
(T (* N (FACTORIAL (1- N))))))
¿Qué ocurre cuando FACTORIAL recibe un argumento inadecuado, por ejemplo, -9.9? Nunca se
alcanzará el caso base y la recursión proseguirá hasta que se agota la memoria disponible. La forma
más burda de evitar esto es la especificada en este apartado:
(DEFUN FACT-EXTENDIDO (S)
(COND ((NOT (NUMBERP S)) (PRINT 'ERROR) NIL)
((NOT (INTEGERP S)) (PRINT 'ERROR) NIL)
((MINUSP S) (PRINT 'ERROR) NIL)
(T (FACTORIAL S))))
Y ahora será
(FACT-EXTENDIDO 9.9)=> NIL
escribéndose además el mensaje ERROR
El lector debe ser consciente de que FACT-EXTENDIDO no implementa la función matemática factorial
, sino una versión extendida de ella:
factorial(X), si X es un número natural
fact-extendido(X) =
NIL, en otro caso.
Ello puede llevar a errores algo difíciles de comprender. Por ejemplo,
(* 2 (FACT-EXTENDIDO 9.9))
=> Error: NIL no es un número.
b) Por lo dicho en a), es mejor señalar explícitamente un error cuando el argumento no es adecuado.
Para ello, CommonLisp proporciona la función ERROR:
(ERROR cadena-control expresión*)
Al intentar evaluar ERROR, se genera un mensaje de error –formateado según las reglas de FORMATque se envía a la corriente establecida para los mismos; se entra en el entorno de depuración. Todas
las evaluaciones pendientes quedan abortadas y es imposible continuarlas.
Según esto, la implementación pedida en este apartado será
(DEFUN FACT-PROTEGIDO (S)
(COND ((NOT (NUMBERP S)) (ERROR "~S no es un numero." S))
((NOT (INTEGERP S)) (ERROR "~D no es entero." S))
((MINUSP S) (ERROR "~D es negativo." S))
(T (FACTORIAL S))))
Y ahora tendremos:
(FACT-PROTEGIDO 9.9)
=> Error: 9.9 no es entero.
(* 2 (FACT-PROTEGIDO 9.9))
=> Error: 9.9 no es entero.
Inteligencia Artificial e I.C.. 2002/2003
6.21
Dpto. Leng. Ciencias Comp.
NOTA. Es costumbre que los mensajes de error sean frases completas, acabadas con un punto. No es
necesario mencionar en ellos la función en la que se produce el error, ya que se supone que el entorno
del intérprete o compilador será capaz de añadir automáticamente esta información.
c) Además de los errores irrecuperables empleados en b), el programador puede definir errores
“continuables” o “recuperables”. Para ello, CommonLisp proporciona la función CERROR:
(CERROR cadena-control1 cadena-control2 expresión*)
Al intentar evaluar CERROR, se genera un mensaje de error –formateado según las reglas de FORMAT
aplicadas a cadena-control2 y a expresión*- que se envía a la corriente establecida para los
mismos. Además, se genera otro mensaje error –formateado según las reglas de FORMAT aplicadas a
cadena-control1 y a expresión*- que informa al usuario de lo que ocurrirá si la computación no
es abortada. Se entra en el entorno de depuración y el usuario puede elegir entre abortar las
evaluaciones pendientes o continuar. Si elige continuar, CERROR devuelve NIL y se continúa
normalmente con las evaluaciones pendientes.
Según esto, la implementación pedida puede ser
(DEFUN FACT-CERROR (S)
(COND ((NOT (NUMBERP S)) (ERROR "~S no es un numero." S))
((NOT (INTEGERP S))
(CERROR "Se calculara el factorial de INT(~D)."
"~D no es entero." S)
(FACT-CERROR (FLOOR S)))
((MINUSP S)
(CERROR "Se calculara el factorial de |~D|."
"~D es negativo." S)
(FACTORIAL (- S)))
(T (FACTORIAL S))))
Y ahora tendremos el siguiente diálogo (usuario en cursiva):
> (FACT-CERROR –9)
Error: -9 es negativo.
Para continuar, teclee CONTINUE
Se calculará el factorial de |-9|.
CONTINUE
362880
Pero
> (FACT-CERROR –9)
Error: -9 es negativo.
Para continuar, teclee CONTINUE
Se calculará el factorial de INT(-9).
ABORT
>
d) Lo que se pide podría implementarse mediante CERROR. Sin embargo, existe una manera más
cómoda de hacerlo, empleando una forma ASSERT.
(ASSERT expresión-test [(símbolo*) [cadena-control expresión*]])
Al intentar evaluar una forma ASSERT, se realiza el siguiente proceso:
1. Se evalúa expresión-test. Si es verdadera, ASSERT devuelve NIL y se continúa normalmente.
2. Si expresión-test es falsa, ASSERT señala un error y envía un mensaje de error indicando este
hecho.
3. Si figura la lista (símbolo*), se proporciona al usuario la posibilidad de modificar el entorno
vigente, asignando nuevos valores a cada uno de los símbolos de la lista. Si el usuario elige esta
opción, se volverá al paso 1.
4. Si el usuario así lo elige, se abortarán las computaciones pendientes.
Los argumentos cadena-control expresión* sirven para formar –según las reglas de FORMAT-el
mensaje que se envía al usuario.
Inteligencia Artificial e I.C.. 2002/2003
6.22
Dpto. Leng. Ciencias Comp.
Por ejemplo, tras definir
(DEFUN FACT-ASSERT1 (S)
(ASSERT (AND (NUMBERP S) (INTEGERP S)
(OR (ZEROP S) (PLUSP S))))
(FACTORIAL S))
se produce el siguiente diálogo:
>(FACT-ASSERT1 -9)
Error:
la aserción (AND (NUMBERP S) (INTEGERP S) (OR (ZEROP S) (PLUSP S))) ha
fracasado.
Tras definir
(DEFUN FACT-ASSERT2 (S)
(ASSERT (AND (NUMBERP S) (INTEGERP S)
(OR (ZEROP S) (PLUSP S))) (S))
(FACTORIAL S))
se produce el siguiente diálogo:
>(FACT-ASSERT2 -9)
Error:
la aserción (AND (NUMBERP S) (INTEGERP S) (OR (ZEROP S) (PLUSP S))) ha
fracasado.
Teclee otra expresión para S: 9
362880
o bien
>(FACT-ASSERT2 -9)
Teclee otra expresión para S: ABORT
Y tras definir
(DEFUN FACT-ASSERT3 (S)
(ASSERT (AND (NUMBERP S) (INTEGERP S)
(OR (ZEROP S) (PLUSP S)))
(S)
"Lo siento, ~S no es un argumento valido para factorial." S)
(FACTORIAL S))
El diálogo es
>(FACT-ASSERT3 -9)
Lo siento, -9 no es un argumento valido para factorial.
Teclee otra expresión para S: 9
362880
NOTA. Es obvio que la forma concreta del diálogo entre sistema y usuario depende de la
implementación del intérprete o compilador.
Inteligencia Artificial e I.C.. 2002/2003
6.23
Dpto. Leng. Ciencias Comp.
R.6.10. Manejo de errores (II).
a) Implementar una versión de EVALQUOTE (vd. R.6.1.c) que nunca señale error.
b) Implementar una versión de EVALQUOTE que nunca señale error, y además nunca salga del ciclo
EVALQUOTE a causa de un error.
c) Implementar una versión de EVALQUOTE que nunca señale error, y además salga del ciclo
EVALQUOTE solamente a causa de los errores aritméticos.
**************************
SOLUCION.
En R.6.9 hemos aprendido a definir errores. En este ejercicio aprenderemos a programar el manejo de
errores, tanto predefinidos como definidos por el programa.
a) La siguiente forma CommonLisp nos permite realizar esto de una manera sencilla:
(IGNORE-ERRORS expresión*)
IGNORE-ERRORS evalúa sus expresiones secuencialmente y si no se producen errores devuelve el
valor de la última. Por el contrario, si durante la evaluación se señala un error, IGNORE-ERRORS
devuelve de manera inmediata NIL (y además el objeto error) y vuelve al nivel superior de interacción,
sin entrar en el entorno de depuración. Nótese que el ámbito de IGNORE-ERRORS es dinámico, no
léxico, es decir, los errores despreciados son aquellos que se producen durante la ejecución de
expresión* o de cualquier otra expresión que se haya llamado desde ellas.
Según esto, la función pedida será
(DEFUN EEVALQUOTE0 ()
(IGNORE-ERRORS
(PRINT 'EVALQUOTE>)
(LET ((F (READ)))
(COND ((EQ F 'EVAL>) NIL)
(T (PRIN1 (APPLY F (READ)))
(EEVALQUOTE0))))))
y ahora tendremos el siguiente diálogo:
> (EEVALQUOTE0)
EVALQUOTE> / (2 0)
NIL
#<SIMPLE-ERROR @ #xE204F4>
>
b) CommonLisp permite manejar los errores de forma flexible. Para ello proporciona los siguientes
recursos:
-los errores son objetos Lisp (más concretamente, objetos CLOS de tipo condición).
-existe una jerarquía predefinida de errores.
-se puede programar explícitamente el manejador de cualquier clase de error, es decir, se pueden
indicar las formas que se evaluarán cuando se señale un error de esa clase.
Para esto último se emplea la forma HANDLER-CASE, que en su formato más sencillo es
(HANDLER-CASE expresión {(tipo-error (símbolo) expresión*)}*)
Se evalúa expresión (nótese que es exactamente una). Si durante su evaluación se señala un error
de un tipo mencionado en una de las cláusulas (tipo-error expresión*), entonces se transfiere
el control a las expresión* y símbolo se liga al objeto error señalado. Si el error satisface varios
tipos, se transfiere el control a la primera de las cláusulas cuyo tipo lo satisfaga. tipo-error no se
evalúa. . Nótese que el ámbito de HANDLER-CASE es dinámico, no léxico
Según esto, podemos conseguir lo especificado con la siguiente definición:
(DEFUN EEVALQUOTE1 ()
(HANDLER-CASE
(PROGN
(PRINT 'EVALQUOTE>)
(LET ((F (READ)))
(COND ((EQ F 'EVAL>) NIL)
Inteligencia Artificial e I.C.. 2002/2003
6.24
Dpto. Leng. Ciencias Comp.
(T (PRIN1 (APPLY F (READ)))
(EEVALQUOTE1)))))
(ERROR (CONDICION)
(FORMAT T "Error ~S. ~%Se desprecia y se sigue en EVALQUOTE."
CONDICION)
(EEVALQUOTE1))))
y ahora el diálogo puede ser
> (EEVALQUOTE1)
EVALQUOTE> / (2 0)
Error #<SIMPLE-ERROR @ #xE21AAC>.
Se desprecia y se sigue en EVALQUOTE.
EVALQUOTE>
C) Nótese que no es necesario emplear un único manejador de errores, como se ha hecho en el
apartado anterior. Para resolver el nuevo problema emplearemos dos (el segundo se empleará sólo si
el primero no es aplicable al error señalado):
(DEFUN EEVALQUOTE2 ()
(HANDLER-CASE
(PROGN
(PRINT 'EVALQUOTE>)
(LET ((F (READ)))
(COND ((EQ F 'EVAL>) NIL)
(T (PRIN1 (APPLY F (READ)))
(EEVALQUOTE2)))))
(ARITHMETIC-ERROR (CONDICION)
(FORMAT T "Error ~S. ~%Se desprecia y se sale de EVALQUOTE."
CONDICION)
0)
(ERROR (CONDICION)
(FORMAT T "Error ~S. ~%Se desprecia y se sigue en EVALQUOTE."
CONDICION)
(EEVALQUOTE2))))
y ahora el diálogo puede ser
> (EEVALQUOTE2)
EVALQUOTE> CAR (2)
Error #<SIMPLE-ERROR @ #xE21ADA>.
Se desprecia y se sigue en EVALQUOTE.
EVALQUOTE> / (2 0)
Error #<SIMPLE-ERROR @ #xE21AAC>.
Se desprecia y se sale de EVALQUOTE.
0
>
Inteligencia Artificial e I.C.. 2002/2003
6.25
Dpto. Leng. Ciencias Comp.
EJERCICIOS PROPUESTOS.
P.6.1. Evaluar las siguientes expresiones, indicando los efectos laterales que se produzcan durante la
evaluación:
a) (READ X Y Z)
b) (PRIN1 (LIST (READ) (READ) (READ)))
c) (APPEND (QUOTE (READ)) (READ))
d) (PRINT (LIST 'TU 'NOMBRE 'ES (CADR (PRINT '(COMO TE LLAMAS?)) (READ))))
e) (PRINT (PRIN1 (PRINT (PRIN1 (+ 3 7)))))
f) (PRINT (+ 3 (PRIN1 (PRIN1 (PRIN1 7)))))
g) (+ 3 (PRINT (PRINT (PRINT (PRINT 7)))))
h) (FORMAT T “¡Vaya lío! ~S” (+ 3 (PRINT (PRINT (PRINT (PRINT 7))))))
i) (FORMAT NIL “¡Vaya lío! ~S” (+ 3 (PRINT (PRINT (PRINT (PRINT 7))))))
P.6.2. Implementar la función VARIACIONES-FORMATO-1, que tiene un argumento numérico y lo
escribe en pantalla en formato decimal sin parte decimal, decimal con 3 decimales, exponencial y
hexadecimal.
P.6.3. Implementar las siguientes funciones:
a) FUGA-DE-VOCALES, que tiene como argumento una cadena c y devuelve la cadena obtenida
sustituyendo en c cada vocal por un *:
(FUGA-DE-VOCALES “AeifghoU”) => “***fgh**”
b) RESTAR-CADENAS, que tiene dos cadenas c1, c2 como argumentos, y devuelve la cadena obtenida
quitando de c2 todas las apariciones como subcadena de la cadena c2:
(RESTAR-CADENAS “ABCXYBAZABC” “AB”) => (CXYBAZC)
P.6.4. Implementar la función COPIAR(fich-origen fich-destino), cuyos argumentos son cadenas, que
copia el fichero cuyo nombre es fich-origen en el fichero cuyo nombre es fich-destino.
P.6.5. Supóngase que queremos evitar que cierta cadena de caracteres c aparezca en un fichero de
texto. Escribir la correspondiente función CENSURA(nombre-archivo cadena). Nótese que no basta
eliminar las apariciones actuales de cadena, ya que al quitarlas pueden crearse otras nuevas: por
ejemplo, al eliminar "AR" de "AARR" queda "AR", donde de nuevo aparece la cadena censurada.
P.6.7. a) Definir ? como un carácter macro, de manera que ?expresión1 expresión2 se lea como
expresión2:
>?PEPE '(A B C)
(A B C)
b) Ciertas aplicaciones MS-DOS escriben uno o varios ^Z al final del archivo. Sin embargo, READ no
acepta este carácter como nombre de símbolo, ni lo reconoce como marca de final de archivo. Definir
^Z como carácter macro de forma que se evite este problema.
P.6.8. a) Definir ! como un carácter macro de envío y R como un subcarácter, de manera que
!Rcadena se lea como el número cuya notación romana es cadena:
>!RXVIII
18
b) Definir ! como un carácter macro de envío e I como un subcarácter, de manera que !Icadena se
lea como el contenido del archivo cuyo nombre es cadena (se supone que el contenido del archivo es
una sola expresión Lisp).
P.6.9. a) Implementar versiones robustas de las funciones para el manejo de polígonos de R.2.8, de
manera que señalen los errores adecuados cuando reciban argumentos que no sean de tipo correcto.
Inteligencia Artificial e I.C.. 2002/2003
6.26
Dpto. Leng. Ciencias Comp.
b) Implementar versiones de las mismas funciones, de forma que los errores sean recuperables,
proporcionando al usuario la posibilidad de cambiar los argumentos proporcionados.
P.6.10. a) Implementar una versión de EVALQUOTE que procese adecuadamente las formas
especiales.
b) Implementar una versión de EVALQUOTE que desprecie los errores aritméticos y los de
entrada/salida.
Inteligencia Artificial e I.C.. 2002/2003
6.27
Dpto. Leng. Ciencias Comp.
NOTAS ERUDITAS Y CURIOSAS.
La función EVALQUOTE de R.6.1. tiene una larga historia en LISP. Hubo un tiempo en que muchos
intérpretes interaccionaban de esa manera con el usuario; pero hace ya bastantes años que, debido a
la falta de homogeneidad que introducen en el lenguaje, han ido cayendo en desuso. Como ha
comprobado el alumno, no es demasiado difícil pasar del intérprete 'normal' (al a que se solía
denomInar 'intérprete EVAL') al intérprete EVALQUOTE, y viceversa.
En los viejos tiempos, la interacción con ficheros se llevaba a cabo mediante las típicas operaciones
OPEN y CLOSE (que aún están disponibles en CommonLisp). Sin embargo, la manera más
recomendable de llevarla a cabo es emplear WITH-OPEN-FILE, entre otras razones por el implícito
UNWIND-PROTECT que lleva consigo.
El manejo de errores es una de las cosas importantes que no se suelen contar en los cursos
académicos de programación. El programador del mundo real, sin embargo, hará bien si emplea
siempre versiones “seguras” o “protegidas” de sus procedimintos y funciones. Ello supone unas
cuantas líneas de código y unos microsegundos adicionales de tiempo de ejecución, pero los
beneficios obtenidos suelen compensar más que sobradamente estas molestias.
CommonLisp es especialmente potente en cuanto al manejo de errores. La clase “error” se define
como subclase de la más general “condición” y existe un amplio repertorio de funciones de bajo nivel
para señalar y manejar condiciones. La contrapartida es que la especificación del lenguaje es
complicada y a veces confusa para el lector inexperto. Como afirma Steele al hablar de la definición
de condiciones [CLtL2, p.898], “...el lector debe tomarse esta sección con cierta precaución y dos
aspirinas, y llamar a un hacker”.
Inteligencia Artificial e I.C.. 2002/2003
6.28
Dpto. Leng. Ciencias Comp.
Descargar