Professional Documents
Culture Documents
Prlogo
La gran mayora de los lenguajes de programacin que existen actualmente se han diseados basados en
dos paradigmas de programacin: paradigma imperativo y paradigma declarativo. El estilo de programacin
imperativa, programacin donde las acciones modifican el estado de la computadora, ha dominado el
panorama de la programacin desde sus inicios. Los lenguajes ms utilizados, tales como C, C++, Pascal,
Basic, Fortran, etc., pertenecen a este paradigma. Una razn fundamental para este dominio reside en que los
lenguajes imperativos estn ms cercanos a la operacin real de la mquina, es decir, son modelos de la
mquina subyacente. Por otro lado, bajo el enfoque del paradigma declarativo, se encuentran los lenguajes de
programacin declarativos: lgicos y funcionales. Y, aunque los programas escritos con este lenguaje son
compilados de forma eficiente, su esquema de razonamiento y transformaciones son complicados. Por otro
lado, los programas escritos en lenguajes de programacin lgica y funcional describen qu hacer pero no
cmo hacerlo. Estos lenguajes se basan en modelos formales y crean programas bajo un esquema de
razonamiento preciso y con transformaciones correctas. En la dcada de los 80 hubo un gran desarrollo de
lenguajes funcionales, de los cuales dos son sus principales exponentes: ML (meta-Lenguaje) desarrollado en
la Universidad de Edimburgo, y Haskell (posteriormente Curry) creado por varios grupos de investigacin de
Europa y Estados Unidos.
Aunque los lenguajes declarativos son casi tan antiguos como los imperativos, LISP (primer lenguaje
funcional) fue desarrollado en la misma poca en que se desarroll FORTRAN, su uso ha estado restringido
principalmente al mbito acadmico o bien dentro de reas de aplicacin especficas, tales como la
inteligencia artificial y la computacin simblica. Sin embargo, y a pesar de la hegemona de la programacin
imperativa, la programacin funcional adquiere cada vez mayor aceptacin gracias a su capacidad expresiva
que permite la escritura de programas ms compactos, a una forma eficiente de evaluacin de expresiones, y
a su transparencia referencial, que posibilita una verificacin matemtica sencilla de propiedades de los
programas.
A su vez, la programacin lgica, PROLOG fue el primer lenguaje de este tipo, se basa en sentencias de
lgica de primer orden, en concreto las clusulas de Horn (restriccin del Clculo de Predicados de Primer
Orden) y su forma de ejecucin es el principio de resolucin de Robinsn. Un programa lgico consiste en un
conjunto de relaciones, y su ejecucin vendr a demostrar que una nueva relacin se sigue se las que
constitua el programa. Las relaciones sern especificadas con reglas y hechos. La ejecucin de programas
lgicos consiste en la demostracin de hechos sobre las relaciones por medio de preguntas. Aunque este
lenguaje de programacin
ha tenido avances al buscar la integracin con los programas funcionales o el
paradigma imperativo, su uso se ha visto restringido al mbito acadmico y de la inteligencia artificial.
Este texto se estructura en cuatro captulos. En el primer captulo, denominado conceptos fundamentales se
describen algunas caractersticas propias del estilo de programacin imperativo, tales como los tipos de datos
que manejan, variables, mtodos y expresiones. El captulo dos se enfoca al estilo de programacin
declarativo, y describe el estudio y realizacin de programas (secuencias de instrucciones o scripts) basado
en un lenguaje de programacin representativo de este paradigma: Haskell y/o Gofer. Una manera eficiente
para evaluar expresiones, conocida como la tcnica de Evaluacin Perezosa se describe en el captulo tres.
Asimismo, en el captulo cuatro se explica y se muestran las principales caractersticas de la programacin
lgica, utilizando para clarificar la explicacin ejemplos basados en el lenguaje Prolog SWI.
El autor agradece al Instituto Tecnolgico de Acapulco las facilidades otorgadas para el desarrollo de este
libro de texto, el cual integra el contenido de la materia: Programacin Lgica Funcional, que forma parte de la
retcula del programa Ingeniera en Sistemas Computacionales.
Tabla de contenido
10
14
19
20
1.6 Ejercicios
25
29
2.2 Funciones
34
2.3 Operadores
48
52
2.5 rboles
61
2.6 Ejercicios
67
69
84
3.3 Ejercicios
100
102
113
124
127
130
132
137
140
144
156
160
4.12 Ejercicios
166
Referencias
168
ANEXO A
Archivo STANDARD.PRELUDE
171
ANEXO B
177
ANEXO C
183
Captulo 1
CONCEPTOS FUNDAMENTALES
Introduccin
Los programas de computadora pueden ser escritos de maneras muy diferentes y sin embargo llegar a los
mismos resultados. Programas funcionalmente equivalentes pueden tener importantes diferencias en su
estilo. Un buen estilo de programacin se ve influenciado por el rea de aplicacin, tcnica y lenguaje
utilizado. Los programas escritos con un buen estilo de programacin son ms fciles de leer y entender,
asimismo, son ms pequeos y eficientes de aquellos mal escritos. Aunque las tcnicas de programacin
estructurada apoyan un buen estilo de programacin, por s solas no aseguran que el cdigo generado ser
bueno, algunos programas estructurados pueden ser tan malos como sus contrapartes no estructurados.
Existen algunos principios bsicos que se deben tener en cuenta para escribir programas con buen estilo,
tales como: a) simplicidad, escribir el cdigo de la forma ms simple y autoexplicativa, b) reusar componentes,
las funciones y mtodos deben hacer slo una tarea y no mltiples, c) uso mnimo de lneas, en un programa
hace que sea fcilmente comprendido y, en su caso, modificado, d) indentacin y espacios, en los programas
facilitan su lectura y comprensin, sobretodo cuando se trata de ciclos anidados.
En este captulo se mostrarn los aspectos que definen los estilos de programacin, sobretodo, aquellos que
son importantes para realizar buenos programas.
[1974], un buen estilo de programacin debe considerar, adems de los puntos anteriores, los siguientes
elementos concernientes a los lenguajes de programacin: Expresin, Estructura, Robustez, Eficiencia e
Instrumentacin, y Documentacin. Con respecto a estos elementos se indica lo siguiente:
Expresin. Son los elementos bsicos de codificacin, los cuales pueden aparecer de manera individual o en
grupo. Una expresin debe ser escrita de forma clara, de tal manera que sea fcil de leer, debe considerarse
una analoga con los lenguajes naturales (espaol, ingls,..) si alguien no escribe una sentencia coherente
cmo se puede hacer un prrafo comprensible con un conjunto de estas sentencias?. En este sentido, en un
lenguaje de programacin, si no se declara una expresin coherente, no podemos esperar una
subrutina/funcin/clase comprensible formada por estas expresiones.
Estructura. La mayor estructura codificada debe ser fcilmente leda tal como si fuese un artculo o libro en
idioma nativo. Esta codificacin debe contener flujos de control primitivas tales como if-then-else, ciclos dowhile, for, sentencias de grupo (begin-end, funciones). El uso de estas estructuras identifican a los lenguajes
estructurados. Codificar en base a estos lenguajes debe hacer al programa ms legible y comprensible, y ms
fcil de modificar y depurar.
Robustez. Un programa debe trabajar de manera correcta. Un programa bien escrito debe ser capaz de
manejar datos errneos y an bajo estas condiciones ofrecer resultados confiables. basura entra, basura
sale, no es realmente una ley de la naturaleza, ms bien indica que es responsabilidad del programador
vigilar a los datos de entrada con el fin de ofrecer salidas coherentes, por ejemplo, cmo se debe comportar
un programa de ordenamiento cuando solo hay un dato de entrada?, o, una rutina de bsqueda en tablas,
cuando la tabla est vaca.
Eficiencia e instrumentacin. En este momento debemos considerar que el estilo de programar tiene que
ver con la eficiencia. No es que no nos importe lo rpido que se ejecuta un programa o la cantidad de
memoria que necesita, pero hablar de eficiencia resulta absurdo si no tenemos un total conocimiento de lo
que hace cada lnea de cdigo y en que lneas es donde el programa consume ms tiempo. Cuando se tiene
todo el programa escrito con claridad, se debe entonces verificar si es demasiado grande o si se ejecuta muy
lento. Es en este punto cuando se debe pensar en un mejor algoritmo, pues resulta ms fcil migrar de un
algoritmo a otro si el programa se encuentra bien escrito. Si consideramos que el algoritmo es el adecuado,
evaluarlo y mejorarlo no debe ser tan debe ser tan difcil.
Documentacin. Si un programa se escribe con claridad, si se eligen las estructuras apropiadas y se escoge
una buena representacin para los datos, este programa se considera autodocumentado, es decir, puede ser
ledo sin ningn problema. En este sentido, se evitarn las discrepancias entre diagrama de flujo, los
comentarios y el cdigo.
Por su parte Barnette y McQuain [2000], proporcionan unos estndares en relacin a lo que consideran un
buen estilo de programacin. Con respecto a su trabajo, muchos programadores expertos consideran que
estos deben ser los requerimientos mnimos que debe cumplir todo software comercial de calidad. Aunque el
trabajo realizado por Barnette y McQuain lo basan en un lenguaje estructurado, la mayora de estos puntos
son aplicables a cualquier lenguaje de programacin. Los siguientes prrafos se relacionan con este trabajo.
La esencia de un buen estilo de programacin es lo que se pretende comunicar. Aprender un buen estilo en la
programacin es similar a aprender un buen estilo en algn lenguaje natural. En ambos casos, el documento
(libro, programa) realizado, no tiene ningn valor si no representa algn significado para el lector. Cualquier
programa que es creado para ser utilizado, debe ser mantenido por alguien, y ese alguien, debe ser capaz de
entender el cdigo aunque l no lo haya hecho. Asimismo, cualquier programa que requiere algn cambio o
necesita depurarse, debe ser fcil de hacer estas tareas, si su creador lo document de manera cuidadosa.
En el texto del programa, los programadores disponen de tres elementos para comunicar sus intenciones: 1)
comentarios, explicacin para el programa, 2) coherencia en la asignacin de nombres a
variables,
expresiones constantes y funciones, las palabras asignadas deben ser auto explicativas, 3) uso adecuado de
sangras y espacios. Los elementos anteriores aplicados de forma apropiada ayudan a establecer una buena
comunicacin entre el creador de un programa y el que lo lee. Cuando un programador se encuentra
depurando un programa se convierte de escritor en lector, es en este punto, donde un buen estilo de
programacin permite ahorrar muchas penas.
Las reglas para determinar un buen estilo de programacin mostradas a continuacin fueron desarrolladas por
programadores quienes aprendieron de la forma difcil que el uso de un buen estilo en la programacin es la
mejor defensa para minimizar problemas.
Reglas generales. Los aspectos ms tiles que se deben conocer acerca de la documentacin de un
programa son el Qu y Cuando. En general, se deben incluir comentarios acerca de lo que hace cada
funcin, subrutina, variable, segmento o seccin de cdigo. Esto ayuda a comprender lo que hace el programa
y facilita lo que se tiene que hacer en caso de realizar alguna modificacin. Si se presenta alguna dificultad
para explicar lo que hace alguna subrutina, es un indicador de que el programa est mal estructurado. En
muchas ocasiones resulta conveniente forzar la precedencia de operaciones aplicando los parntesis que
sean necesarios para no dejar lugar a dudas acerca de la operacin. En este aspecto la regla ms importante
es: ser consistente!, es decir, hay que mantener la consistencia a travs de todo el programa.
Encabezado del programa. Todos los programas deben incluir al inicio del mismo unas lneas de
comentarios indicando al menos la siguiente informacin (ver Figura 1.1): datos del programador, fecha de
creacin y/o modificacin, nombre del programa, propsito, sistema operativo en que se cre, datos del
equipo, instruccin de compilacin, ejecucin y edicin.
/*
* nombre del programa: grados.c
*
* Uso: Realiza la conversin de grados farenheit a centgrados.
*
* Datos del equipo: PC MacBook Pro, SO Mac OS X version 10.6.8, 2.66 GHz Intel
Core i7
*
* Created by Antonio Montero on 08/02/14.
* Copyright 2014 ita. All rights reserved.
*
* compilacin en terminal: >gcc grados.c enter, archivo compilado: a.out
*
* ejecucin: ./a.out enter
*
* editor: Xcode.
*/
#include <stdio.h>
#define LOWER 0
#define UPPER 300
#define STEP 20
/* imprime una tabla de conversion de grados
int convierte(int);
main()
{
int fahr, celsius;
farenheit a centgrados */
fahr=LOWER;
while (fahr<=UPPER) {
printf("%d\t%d\n\a", fahr, convierte (fahr));
fahr=fahr+STEP;
}
return 0;
}
int convierte(int faren)
{
int celsius;
celsius=5*(faren-32)/9;
return celsius;
}
Figura 1.1. Ilustracin del listado del programa grados.c.
La informacin mostrada en el encabezado es una gua que otros programadores pueden utilizar para tener
una informacin general acerca del programa. Asimismo, se puede incluir informacin acerca del algoritmo y
estructuras de datos utilizados en el programa. Si se tienen varios mdulos en el mismo, indicar por lo menos
los nombres de cada mdulo y su funcin, independientemente de que en cada uno de ellos exista un
encabezado con informacin similar a la indicada en el programa principal.
Documentacin de funciones/subrutinas/clases. Los mdulos de un programa que realizan una tarea
especfica mejor conocidos como subrutinas, funciones o clases son los que definen la estructura del mismo.
Cada subrutina debe tener una tarea bien definida, no deben ser muy extensas (mximo una pgina de
cdigo), de tal forma que un programador la pueda comprender fcilmente. Al igual que el programa principal
una subrutina debe tener un encabezado donde especifique su nombre, propsito, parmetros que utiliza y
llamadas a otras subrutinas (si es el caso). La Figura 1.2 ilustra un ejemplo de subrutina cuya tarea es
convertir grados farenheit a grados centgrados.
/*
* Funcin convierte
*
* Tarea: Realiza la conversin de grados farenheit a centgrados.
*
* Parmetros:
* faren: cantidad de grados farenheit que deben ser convertidos a grados
centgrados
*
* retorna celsius: grados centgrados
*
* Descripcin: rutina que recibe un parmetro de tipo entero y retorna un
resultado de tipo entero.
*/
int convierte(int faren)
{
int celsius; /* variable que almacena los grados centgrados */
celsius=5*(faren-32)/9; /* expresin matemtica que transforma grados far a
cent */
return celsius; /* retorno de la conversin */
}
Figura 1.2. Funcin que convierte grados Farenheit a grados Centgrados.
Hay que tener presente que cuando se modifica una subrutina se tiene que hacer lo mismo con la seccin de
comentarios. Con el objetivo de tener un buen mantenimiento se debe limitar el nmero de parmetros en una
funcin a un nmero no mayor a siete, ya que si una funcin tiene un nmero mayor de parmetros es un
indicador de un programa mal estructurado. Una sugerencia para disminuir el nmero de parmetros en el
encabezado de una funcin, puede ser agruparlos en un registro y usar a ste
como parmetro.
Generalmente existen dos clases de subrutinas: funciones y procedimientos. Las funciones slo afectan un
valor, el valor de retorno, si se requiere retornar ms de un valor, entonces se debe utilizar un procedimiento.
Indentacin y espacios. El uso de indentacin y de espacios tienen la finalidad de hacer a un programa ms
comprensible. Por ejemplo, en la Figura 1.3, se muestra lo que la indentacin y espacios en blanco hacen en
un programa. Para un compilador, la lnea mostrada en la Figura 1.3 (a) y las lneas mostradas en la Figura
1.3 (b) hacen exactamente lo mismo. Sin embargo, para un lector humano las lneas mostradas en la Figura
1.3 (b) son mucho ms comprensibles.
if (x<0)x=0;else{while(x>0){P(x);x=x-1;}y=y+1;}
(a)
if (x < 0)
x = 0;
else
{
while (x > 0)
{
P(x);
x = x - 1;
}
y = y + 1;
}
(b)
Generalmente, los editores utilizados por los lenguajes de programacin realizan de forma automtica la
indentacin. Sin embargo, los espacios en blanco generalmente los debe hacer el programador. Se
recomienda escribir solo una sentencia o instruccin por lnea, tambin no usar ms de 80 caracteres por
lnea, si es necesario (como cuando se realizan operaciones matriciales), se deben particionar en mltiples
lneas.
Identificadores. Los identificadores son los nombres asignados a las variables, registros, funciones, etc., el
nombre seleccionado debe ser tal que comunique el propsito del mismo al lector, por ejemplo, en la Figura
1.2 el nombre de la variable celsius da idea al lector que se refiere a una variable utilizada para medir la
temperatura. Se debe evitar el uso de identificadores con un solo carcter, esto es una excepcin en
expresiones matemticas, donde es comn el uso de x y y para indicar coordenadas, y el uso de i, j y k para
especificar variables de ciclo o ndices en arreglos. Existen varias reglas para el buen uso de las variables,
por ejemplo, una fcil de seguir es: nunca cambiar el valor de una variable de ciclo dentro del ciclo. Es decir,
nunca hacer lo siguiente:
for (int i = 1; i <= 10; i++)
{
i = i + 1;
}
Lo anterior es una instancia de un principio general para el uso de variables: no sobrecargar el significado
lgico de una variable. Al usar i como una variable de ciclo, el programador le indica esto al lector con relacin
a i. Mediante una asignacin a la variable i (tercer rengln) un segundo significado es introducido. El programa
es ms fcil de entender si una segunda variable es utilizada en la asignacin.
Utilizar las variables con el ms pequeo nivel de alcance, esto es, minimizar el uso de variables globales,
evitar el uso de variables globales como una forma de comunicacin entre subrutinas. Hay que ser consistente
con el uso de las variables locales con el mismo nombre en todo el programa, por ejemplo, si la variable i se
utiliza como variable de ciclo en una subrutina, hay que evitar utilizarla en otra subrutina.
Dependiendo del lenguaje de programacin, el compilador puede o no, hacer distinciones entre letras
maysculas y minsculas. Sin embargo, un humano puede distinguir perfectamente estas diferencias, de tal
forma que el uso de sus combinaciones pueden dar algunas pistas en relacin a los nombres. Se pueden
utilizar algunos estndares de uso en la programacin, por ejemplo: i) se pueden declarar las constantes en
maysculas, ii) los tipos creados (typedef, record, structure) con la primera letra en mayscula, y iii) declarar
las variables en minsculas.
Cada variable en el programa debe tener una lnea describiendo su uso. Generalmente, este comentario
aparece al final de la lnea que contiene la declaracin de la variable (ver Figura 1.2).
Constantes y tipos enumerados. Cuando se escribe una expresin matemtica como celsius=5*(faren32)/9, los nmeros 5, 32 y 9, son realmente eso: nmeros. Sin embargo, la mayora de los programadores
minimizan el uso de nmeros reales, en su lugar, tratan con nmeros que son cantidades lgicas, por ejemplo
10
1024 es 1KB, lunes es el da 1 de la semana. Para este fin, algunos lenguajes como C, C++, y Pascal,
integran el manejo de constantes y la enumeracin de tipos. El uso de constantes ofrecen al programador
asignarle un nombre a estos datos, de esta forma, el significado de un nmero puede ser capturado bajo un
identificador, proporcionndole mayor informacin al programador. Otra ventaja de este uso consiste en la
facilidad para cambiar su valor dentro del programa. Por su parte, los tipos enumerados permiten al
programador la declaracin de variables con un rango limitado de valores. Estos valores pueden ser
numricos o cadenas de caracteres. Este tipo de variables son tiles en el manejo de das de la semana,
meses del ao, colores, etc., ya que en vez de utilizar su valor numrico (1 para Lunes, 1 para Enero,) se
utiliza su descripcin, la cual resulta ms explicativa.
Registros, Estructuras, Tipos y Definicin de Tipos (Typedefs). En muchas ocasiones la parte ms difcil
al estar escribiendo un programa radica en la definicin de varios tipos de registros (estructuras y uniones en
C). Esto es porque las definiciones para registros definen las estructuras de datos en el programa. Se debe
tener cuidado al nombrar tanto al registro como a sus campos, an ms que asignarles nombres a las
variables. La Figura 1.4 muestra un segmento de programa donde se emplean registros y datos enumerados.
Se puede observar que an sin agregar comentarios a las lneas, la explicacin de cada una resulta intuitiva
por los nombres asignados.
11
}
Los comentarios en un programa deben ser breves, comprensibles y de carcter informativo, por ejemplo, la
lnea siguiente no es informativa (ver tambin la Figura 1.5):
contador = contador + 1; /* incrementa el contador en 1 */
Figura 1.5. El Programador Real, por Oliver Widder, Webcomics Geek and Poke, 2011-02-14.
Resumiendo lo descrito los principales aspectos relacionados con la industria del software que se ven
beneficiados por un buen estilo de programacin, son los siguientes:
De igual forma, algunos aspectos relevantes a considerar al momento de programar con estilos son:
12
El estilo del cdigo no es un resultado final, sino algo que se debe preservar a lo largo de toda su
escritura. Es cierto que las primeras veces escribir con estilo requiere un esfuerzo consciente, una
vez que uno se acostumbra al estilo, seguirlo deja de ser un esfuerzo adicional. Usualmente es ms
trabajo "arreglar un cdigo despus, que escribirlo bien desde el principio".
El estilo debe ser uniforme en un mismo proyecto. Al leer un cdigo, uno se acostumbra al estilo
usado en una forma que permite entenderlo con un vistazo general. Si hay cdigo con estilos
mezclados, leer algo con un estilo despus de haberse acostumbrado a leer algo con otro puede ser
confuso. Por ejemplo, si dos tramos de cdigo usan distintas abreviaturas para lo mismo, o distintas
formas de indentar (que pueden hacer que dos estructuras de control iguales se vean diferentes).
El estilo de cdigo debe promover programas que pueden ser comprendidos de forma inmediata
(suponiendo el conocimiento del problema que ste resuelve). Los problemas de computacin ya son
complejos y no hay motivo para aumentar su complejidad con cdigo rebuscado. Los programas
deberan ser soluciones, no problemas.
El estilo no debe promover fragmentos de cdigo que le dan demasiada importancia a detalles
irrelevantes. Escribir demasiado, le da relevancia a aspectos no fundamentales, y ocupa la atencin
en aspectos secundarios del programa. Un buen programa debera enfocar la atencin en lo
importante, y permitir abstraerse de los detalles.
El estilo de indentacin debe permitir ver la estructura del cdigo sin mirar el cdigo en s (es decir,
con solo ver la distribucin de espacio en blanco y espacio escrito). Cuando se busca un tramo de
programa o se lee rpido, uno puede visualizar la distribucin de espacio blanco/no blanco pero no
tiene tiempo para ver estructuras, o concordancia de parntesis/llaves/corchetes.
El estilo de indentacin debe poder usarse de la misma forma en distintos lenguajes y verse similar
(para construcciones lingsticas similares). Muchas veces es necesario cambiar de lenguaje (entre
un proyecto o entre varios), y preservar un estilo uniforme permite no tener que estar fabricando
reglas nuevas cada vez.
El estilo de cdigo debe permitir fcilmente realizar cambios bsicos en el cdigo: agregar una lnea
a un bloque, borrar una lnea de un bloque, mover lneas en un bloque. Este tipo de cambios es muy
usual, y si el estilo dificulta realizarlos interrumpe en la forma de trabajo.
El nombre de un objeto cualquiera del programa (funcin, variable, tipo), debe permitir identificar el
objeto de forma no ambigua rpidamente dentro del rea de visibilidad del objeto. Un estilo de esta
forma permite leer el cdigo sin tener que detenerse en cada identificador a recordar (o buscar)
donde estaba definido y que era.
Indentacin. Se utiliza para mejorar la legibilidad del cdigo fuente por parte de los programadores,
teniendo en cuenta que los compiladores o intrpretes raramente consideran los espacios en blanco
entre las sentencias de un programa.
13
Con respecto a los estilos de programacin se puede decir lo siguiente: No hay un "estilo correcto", sino que
hay muchos. Definitivamente hay distintos criterios sobre cul de ellos es el mejor, y discusiones bizantinas al
respecto. De todos modos, si hay un acuerdo bastante generalizado sobre varias cosas que se consideran
"mal estilo".
]
]
[
[
]
]
El mismo clculo expresado en las lneas anteriores, pero con el uso de expresiones se observara como
sigue:
Se observa que el uso de expresiones en los programas los hace ms comprensibles para el que los lee.
Carlos Urea (2011), describe el concepto de expresin como sigue:
Una expresin es un trozo del texto de un programa que denota un proceso de clculo que produce
como resultado un valor.
El clculo ser llevado a cabo durante la ejecucin del programa.
El proceso de llevar a cabo este clculo se denomina evaluar la expresin.
Una expresin se puede evaluar un nmero arbitrario de veces durante la ejecucin de un programa.
Cada vez puede producir un valor distinto como resultado.
Las expresiones son una parte fundamental de la programacin ya que sirven para realizar una o varias
operaciones sobre un dato o un conjunto de datos, obtenindose otro dato como resultado. Los operadores
definen algunas de las operaciones que pueden realizarse dentro de una expresin [Beltrn y otros, 2002].
Una expresin es una combinacin de operadores y operandos. Los datos u operandos pueden ser
constantes, variables y llamadas a funciones. Adems, dentro de una expresin pueden encontrarse
subexpresiones encerradas entre parntesis (ver Figura 1.6). Cuando se ejecuta una sentencia de cdigo que
14
contiene una expresin, sta se evala. Al evaluarse la expresin toma un valor que depende del valor
asignado previamente a las variables, las constantes y los operadores y funciones utilizadas y la secuencia de
la ejecucin de las operaciones correspondientes. Este valor resultante de la evaluacin de la expresin ser
de un determinado tipo de dato. Por ejemplo, de un tipo numrico entero (int, long..), de un tipo real (float) o
de un tipo lgico o booleano. A travs de las expresiones
un programador
operaciones que la computadora debe realizar. A su vez, las expresiones estn compuestas de operadores,
operandos, parntesis y llamadas a funciones. Los operadores pueden ser de los siguientes tipos:
Unarios (ver Tabla 1.1). Cuando
y posteriormente el
, y el resultado se
multiplica por c.
La jerarqua entre operadores
porque viene de la jerarqua de operadores usada en la matemtica. Por su parte, las reglas de asociatividad
definen el orden en que deben evaluarse los operandos de igual precedencia que aparezcan de manera
consecutiva en una expresin, por ejemplo:
asociatividad por la izquierda, evalan los operandos de izquierda a derecha, y 2) asociatividad por la
derecha, en este caso los operandos se evalan de derecha a izquierda. La mayora de los lenguajes de
programacin implementan una asociatividad de izquierda a derecha en la mayora de sus operadores. Al
operador de exponenciacin (**) se le suele aplicar asociatividad por la derecha [Kernighan y Ritchie, 2015].
Los parntesis alteran las reglas anteriores (de precedencia y asociatividad). Se evala en primer lugar la
subexpresin que aparece entre parntesis. Por ejemplo, en la expresin:
15
#include <stdio.h>
main()
{
float a, b, c, x, y, z;
a = 9;
b = 12;
c = 3;
x = a - b / 3 + c * 2 - 1;
y = a - b / (3 + c) * (2 - 1);
z = a - (b / (3 + c) * 2) - 1;
printf ("x = %fn", x);
printf ("x = %fn", y);
printf ("x = %fn", z);
}
En lenguaje C, una expresin puede contener varios operadores con la misma prioridad. Cuando varios
operadores de este tipo aparecen en el mismo nivel en una expresin, la evaluacin contina segn la
asociatividad del operador, de derecha a izquierda y de izquierda a derecha. La direccin de evaluacin no
afecta a los resultados de las expresiones que incluyen ms de un operador de multiplicacin (*), adicin (+) o
binario bit a bit (& | ^) en el mismo nivel. El orden de las operaciones no lo define el lenguaje. El compilador es
libre de evaluar estas expresiones en cualquier orden, si puede garantizar un resultado coherente. Solo los
operadores de evaluacin secuencial (,), Y lgico (&&), O lgico (||), expresin condicional (? :) y llamada a
funcin constituyen puntos de secuencia y, en consecuencia, garantizan un orden de evaluacin determinado
para los operandos. El operador de llamada a funcin es el conjunto de parntesis que siguen al identificador
de funcin. Est garantizado que el operador de evaluacin secuencial (,) evala sus operandos de izquierda
a derecha. (Observe que el operador de coma en una llamada a funcin no es igual que el operador de
evaluacin secuencial y no proporciona esta garanta).
Los operadores lgicos tambin garantizan la evaluacin de sus operandos de izquierda a derecha. Sin
embargo, evalan el nmero ms pequeo de operandos necesarios para determinar el resultado de la
expresin. Esto se denomina evaluacin de "cortocircuito". Por tanto, es posible que algunos operandos de la
expresin no se evalen. Por ejemplo, en la expresin
x && y++
16
se evala el segundo operando, y++, solo si x es true (distinto de cero). As, y no se incrementa si x es false
(0).
Tabla 1.1. Prioridad y asociatividad de los operadores de C. La prioridad de ejecucin se muestra de arriba
hacia abajo, los parntesis indican la ms alta prioridad.
Smbolo
[ ] ( ) . > postfix ++ y postfix
prefix ++ y prefix sizeof & * + ~ !
typecasts
* / %
+,
<< >>
<, >, <=, >=
==, !=
&
^
|
&&
||
?:
= *, = /, = %, = +, = , = <<, = >>, =&, = ^, = |
,
Tipo de operacin
Expresin
Unario
Unario
Multiplicacin, Divisin
Suma, Resta
Desplazamiento bit a bit
Relacional
Igualdad
AND bit a bit
OR exclusivo bit a bit
OR inclusivo bit a bit
AND lgico
OR lgico
Expresin condicional
Asignacin simple y compuesta
Evaluacin secuencial
Orden de evaluacin
De izquierda a derecha
De derecha a izquierda
De derecha a izquierda
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De izquierda a derecha
De derecha a izquierda
De derecha a izquierda
De izquierda a derecha
En la Tabla 1.2 se muestra cmo el compilador enlaza varias expresiones automticamente de acuerdo a las
reglas indicadas previamente en la Tabla 1.1.
Tabla 1.2. Enlace automtico que realiza el compilador de C con respecto a la prioridad de sus operadores.
Expresin
Enlace automtico
En la primera expresin, el operador AND bit a bit (&) tiene prioridad sobre el operador OR lgico (||), por lo
que
lgico (||) tiene una mayor prioridad que el operador de asignacin simple (=), por lo que b || c se agrupa como
el operando derecho de la asignacin. Observe que el valor asignado a
es 0 o 1.
La tercera expresin muestra una expresin correctamente formada que puede generar un resultado
inesperado. El operador AND lgico (&&) tiene una mayor prioridad que el operador OR lgico (||), por lo que
q && r se agrupa como operando. Puesto que los operadores lgicos garantizan la evaluacin de los
operandos de izquierda a derecha, q && r se evala antes de s--. Sin embargo, si q && r se evala como un
valor distinto de cero, s no se evala y s no se reduce. Si la no reduccin de s puede provocar un problema
17
en el programa, s debe aparecer como el primer operando de la expresin, o bien s se debera reducir en
una operacin independiente.
Generalmente todos los lenguajes de programacin obedecen las reglas de
prioridad de operadores
Con funciones apropiadamente diseadas, es posible ignorar como se realiza una tarea, sabiendo
qu es lo que hacen.
Se puede acceder (llamar) a una determinada funcin desde cualquier parte de un programa. Cuando se
llama a una funcin se ejecutan las instrucciones que la definen. Una vez que se ejecutan las instrucciones de
Hay que tener presente que no se trata de un curso para aprender el dominio de un lenguaje de
programacin, sino, de conocer algunos aspectos generales relacionados con un buen estilo de
programacin.
18
la funcin, se devuelve el control del programa a la siguiente instruccin (si existe) inmediatamente despus
de la que provoc la llamada a la funcin. Cuando se accede a una funcin desde un determinado punto del
programa, se le puede pasar informacin mediante unos identificadores especiales conocidos como
argumentos (tambin denominados parmetros). Una vez que la funcin procesa esta informacin, devuelve
un valor mediante la instruccin return. La estructura general de una funcin en C se muestra en la Figura 1.7:
Donde:
tipo : especifica el tipo de valor que devuelve la funcin. Si no se especifica algn tipo, el compilador asume
que es entero (int). Si se emplea void no se devuelve ningn tipo.
nombre_funcion: es el nombre asignado a la funcin.
lista de parmetros : es la lista de nombres de variables separados por comas con sus tipos asociados que
reciben los valores de los argumentos actuales cuando la funcin es llamada.
cuerpo de la funcin: aqu se indica el conjunto de sentencias que determinan la tarea que realiza la funcin.
return: esta sentencia fuerza la salida de la funcin, tambin se utiliza para regresar un valor.
La Figura 1.8 ilustra el uso de funciones en C, en este programa se obtiene el precio de un producto,
basndose en el precio unitario del mismo y el impuesto aplicable.
#include <stdio.h>
float precio (float base, float impuesto); /* declaracin de la funcin */
main()
{
float importe = 2.5;
/* se inicializan los datos que utiliza */
float tasa = 0.07;
printf ("El precio a pagar es: %.2f\n", precio (importe, tasa));
return 0; /* el valor 0 indica una terminacin normal */
}
/* definicin de la funcin */
float precio(float base, float impuesto)
{
float calculo;
calculo = base + (base * impuesto); /* cuerpo de la funcin */
return calculo;
/* valor que regresa la funcin */
}
Figura 1.8 Programa escrito en C que muestra el uso de funciones.
19
Como se puede observar en el programa anterior, se define la funcin precio con dos argumentos (base,
impuesto) los cuales son datos numricos de tipo real. Posteriormente, en el cuerpo del programa se llama
esta funcin y se le pasan los parmetros importe y tasa, los cuales a su vez van a reemplazar a los
argumentos originalmente declarados. La tarea a realizar por la funcin precio es definida despus del
programa principal y en el cuerpo de la misma se indican las instrucciones a realizar, en este caso el clculo
del precio ms impuesto (calculo = base + (base * impuesto)) de un producto, el que es retornado
a travs de la sentencia return.
En la Figura 1.9 se muestra otro ejemplo de programa escrito en el lenguaje de programacin C, que utiliza
una funcin para calcular el factorial de un nmero. Ya que el programa utiliza declaraciones comprensibles,
se deja al lector que documente este programa.
#include <stdio.h>
long factorial (int);
void main()
{
int m;
printf ("\n\nIntroduzca nmero a calcular su factorial.\n");
printf ("Digite -1 o cualquier nmero negativo para terminar.\n");
printf (" nmero: ");
scanf ("%d", &m);
while (m >= 0 )
{
printf ("\t\tfactorial: %ld\n", factorial(m));
printf (" nmero: ");
scanf ("%d", &m);
}
}
long factorial (int p)
{
if (p == 0 || p == 1)
return 1;
else
return p * factorial(p - 1);
}
Figura 1.9. Programa C que utiliza funciones.
Funciones diseadas para realizar operaciones a partir de sus argumentos y devolver un valor
basado en sus clculos (como el ejemplo mostrado en las Figuras 1.8 y 1.9).
Funciones que no tienen argumentos ni valor de retorno explcito, operan sobre el entorno de
variables globales o atributos del sistema operativo (funcin main())
Asimismo y con respecto a las variables, estas son de dos tipos, dependiendo de su alcance: locales y
globales. La variables locales son aquellas que se declaran dentro de la funcin y slo estn disponibles
durante su ejecucin, se crean cuando una funcin se ejecuta y se destruyen cuando termina (ver Figura
20
1.10). Las variables globales se declaran fuera de las funciones y pueden ser utilizadas por todas las
funciones, existen durante toda la vida del programa (ver Figura 1.11). Con respecto a los parmetros o
argumentos de una funcin, stos pueden ser pasados por valor y por referencia. En el caso de pasar un
parmetro por valor se copia el valor de un argumento de la llamada en el parmetro formal de la funcin, por
lo tanto, los cambios en los parmetros de la funcin no afectan a las variables que se usan en la llamada. En
el caso de pasar un parmetro por referencia se copia la direccin del argumento en el parmetro, los
cambios hechos a los parmetros afectan a las variables usadas en la llamada a la funcin. En los programas
1.8 y 1.9, indicar el alcance de las variables utilizadas y la forma en que se pasan los parmetros a las
funciones.
Funciones estndar en C. El lenguaje C ofrece un conjunto de funciones estndar que dan soporte a las
operaciones que se utilizan con ms frecuencia. Estas funciones estn agrupadas en bibliotecas. Para utilizar
cualquiera de las funciones que forman parte de las bibliotecas estndar de C, slo hace falta realizar una
llamada a dicha funcin. Las funciones que forman parte de la biblioteca estndar de C, funciones estndar o
predefinidas, estn divididas en grupos. Todas las funciones que pertenecen a un mismo grupo se definen en
el mismo archivo de cabecera (stdio, stdlib, assert, string, math, time, limits, etc.). Cuando se desea utilizar
cualquiera de estas funciones, primero se debe utilizar la directiva de precompilacin #include para incluir los
archivos de cabecera en el programa. Asimismo, antes de utilizar dichas funciones hay que conocer las
caractersticas de las mismas, es decir, el nmero y tipo de datos de sus argumentos y el tipo de valor que
devuelve. Esta informacin es proporcionada por los prototipos de la funcin.
Los grupos de funciones estndar ms comnes son los siguientes:
Entrada/salida estndar. Las funciones ms usuales en este grupo son: a) printf, funcin de salida utilizada
para escribir datos en el dispositivo de salida estndar (impresora), de esta forma, la sentencia printf
(Hola,); enva la cadena encerrada entre comillas a la impresora, b) scanf es la funcin de entrada a travs
del dispositivo estndar, teclado, la sentencia scanf (introduce opcin: %i, &opcin) a travs de un mensaje
espera la lectura de un dato, tipo entero, del teclado.
21
Figura 1.11. Programa escrito en C que ilustra el uso de las variables globales, asimismo se muestran los resultados al
ejecutar este programa.
22
Matemticas. En el grupo de las funciones matemticas existen varios subgrupos, tales como: a) funciones
trigonomtricas:
, c) exponenciales
, d) raz cuadrada
, e) valor absoluto
, entre otros.
Las anteriores son algunas de las funciones estndar que se encuentran descritas en la biblioteca de fuciones
del lenguaje de programacin C. Asimismo, se encuentran funciones para el manejo del flujo del programa,
archivos, cadenas de caracteres, bsqueda, manejo de memoria, entre otras.
Pascal. Cercano a tener disciplina de tipos pero no realiza comprobacin de tipos en los registros
variantes (incluso puede omitirse la etiqueta discriminatoria en dichos registros)
Ada. Resuelve el problema de los registros variantes realizando comprobacin dinmica de tipos
(slo en este caso), asimismo, cuenta con una funcin de biblioteca que permite extraer un valor de
una variable de cualquier tipo (como una cadena de bits) y usarlo como un tipo diferente (no es una
conversin de tipos), se trata de una suspensin temporal de la comprobacin de tipos
C. Con respecto a este lenguaje, se dice que no tiene disciplina de tipos debido a que no realiza la
comprobacin de tipos sobre las uniones, adems permite funciones con parmetros sobre los que
no se realiza comprobacin de tipos. Estos aspectos estn considerados en las versiones posteriores
(C++, C#, etc.).
Java. Aunque este lenguaje se considera una extensin del lenguaje C, no maneja las uniones,
asimismo se basa en clases, por lo que se dice que Java tiene disciplina de tipos.
ML y Haskell, A estos lenguajes se les caracteriza por ser fuertemente tipados, es decir, tienen las
siguientes caractersticas: i) poseen disciplina de tipos, b) los tipos de los parmetros de las
funciones (y de estas mismas) se conocen en tiempo de compilacin (ya sea por declaracin del
23
usuario o por inferencia de tipos). En este sentido, Haskell y otros lenguajes funcionales utilizan el
sistema de tipos de Milner, que tiene dos caractersticas fundamentales: 1) Disciplina esttica de
tipos: Los programas bien tipados se pueden conocer en tiempo de compilacin. Un programa bien
tipado se puede utilizar sin efectuar comprobaciones de tipo en tiempo de ejecucin, estando
garantizado que no se producirn errores de tipo durante el computo, y 2) Polimorfismo: Permite que
una misma funcin se pueda aplicar a parmetros de diferentes tipos, dependiendo del contexto en el
que la funcin se utilice.
La disciplina de tipos es un aspecto importante, ya que en algunos trabajos [Honda y otros, 1997] se
considera elemental para el buen diseo y operacin de la programacin concurrente basada en la
comunicacin (redes de computadoras, sistemas cliente servidor, aplicaciones distribuidas en la web,
interaccin con robots mviles, etc.).
De acuerdo con Honda y otros (1997), contar con una disciplina de tipos con primitivas de comunicacin es un
elemento indispensable para los mtodos de estructuracin de los lenguajes de programacin. En este
sentido, mencionan que el tipado en un programa asegura que dos procesos comunicndose entre s
mantendrn en todo momento patrones de comunicacin compatibles. Por ejemplo, una llamada a
procedimiento tiene un patrn de entrada-salida desde la perspectiva del procedimiento que llama; a
continuacin el destinatario tambin debera tener el mismo patrn de comunicacin. Sin embargo, esto no
siempre se cumple. Por lo tanto, la incompatibilidad de los patrones de interaccin entre los procesos
sera
una de las principales razones de errores en la programacin basada en la comunicacin, se cree que el
tipado como disciplina tiene una gran importancia pragmtica. La disciplina de tipos, asimismo, ofrece un alto
nivel de abstraccin en el comportamiento interactivo de un programa.
24
Los tipos de datos pueden ser predefinidos o abstractos. Un tipo de dato predefinido es intrnsecamente
comprendido por el compilador. En contraste, un tipo de datos definido por el usuario es aquel que el
programador crea, estos son generalmente conocidos como tipos de datos abstractos. En la Tabla 1.3 se
muestran los tipos de datos predefinidos ms comunes en el lenguaje de programacin C [Kernighan y
Ritchie, 2016].
Espacio en memoria2
8 bits
8 bits
8 bits
8 its
16 bits
32 bits
32 bits
32 bits
16 bits
32 bits
32 bits
64 bits
80 bits
16, 32, 64 bits
0
Conversin de tipos. Es comn que un programa contenga operandos con diferentes tipos de datos, cuando
este es el caso, C realiza una conversin explcita al tipo ms comn. Las reglas siguientes indican la forma
en que se realizan las convesriones entre tipos de datos distintos:
1. Cualquier tipo entero pequeo como char o short ser convertido a int o unsigned int.
2. Si algn operando es de tipo long double, el otro se convertir a long double.
3. Si algn operando es de tipo double, el otro se convertir a double.
4. Si algn operando es de tipo float, el otro se convertir a float.
5. Si algn operando es de tipo unsigned long, el otro se convertir a unsigned long.
6. Si algn operando es de tipo long, el otro se convertir a long.
7. Si algn operando es de tipo unsigned long, el otro se convertir a unsigned long.
8. Si algn operando es de tipo unsigned int, el otro se convertir a unsigned int.
Un dato tipo enum crea una asociacin entre nombres constantes y sus valores. Usando este mtodo (como
una alternativa a #define), se pueden generar valores constantes, o se pueden asignar estos valores a alguna
variable. Se coloca un identificador por lnea entre corchetes alineados, agregando indentacin para mejorar
25
la comprensin. La asignacin de valores en el dato tipo enum inicia con 0 de forma predeterminada. En las
lneas que siguen se generan los siguientes valores: 0 se asigna a BAJO, 1 a MEDIO, y 2 a ALTO.
enum posicion
{
BAJO,
MEDIO,
ALTO
};
Los nmeros de punto flotante deben tener al menos un nmero a cada lado del punto decimal, tales como:
0.5, 5.0, y
. El lenguaje C tambin maneja datos en formato hexadecimal, estos datos deben iniciar con
Modificadores. En la Tabla 1.3 se muestran los tipos bsicos de datos (char, int, float, puntero, void, double)
incluyendo sus modificadores (signed, unsigned, short, long). Los modificadores short y long, se aplican por
defecto a los datos tipo entero. En tanto, los modificadores de signo (signed, unsigned) se aplican a datos tipo
char, short, int y long. De esta manera, el lenguaje C dispone de seis tipos bsicos para representar nmeros
enteros, tres con signo: signed (char | short | long), y tres con signo: unsigned (char | short | long). El tipo de
dato int es equivalente al tipo de dato short en compiladores de 16 bits, y al tipo de datos long en
compiladores de 32 o 64 bits. Por su parte, el tipo de datos unsigned int es equivalente al tipo de datos
unsigned short en compiladores de 16 bits, y al tipo de datos unsigned long en compiladores de 32 y 64 bits.
Con respecto a los literales3 enteros, estos pueden expresarse en los siguientes formatos:
a) Decimal, 175, 234, 255.
2
b) Octal (base 8), con el prefijo 0: 0355, equivalente decimal (3x8 + 5x8 + 5x8 = 237).
c) Hexadecimal
Los literales enteros son por defecto tipo int. Un literal entero es de tipo long si va acompaado del sufijo l o L,
por ejemplo: 123876L. Un literal entero unsigned debe ir acompaado del sufijo u o U, por ejemplo: 123876u.
Con respecto a literales reales, estas se pueden representar de las siguientes formas:
a)
b)
26
Por defecto los literales reales representan a datos tipo double. Para representar valors tipo float se debe usar
el sufijo f o F, por ejemplo: 123.34F, 0.022f, .008F. Asimismo, el sufijo l o L indican literales de tipo long
double, por ejemplo: 321.21L, 0.23L.
De acuerdo con el estndar IEEE 754, las operaciones aritmticas que involucran datos en punto flotante
puedan dar como resultado algunos valores especiales (ver Tabla 1.4).
Cuando el resultado de una operacin est fuera de rango, se obtiene +Inf o Inf (Infinito). Cuando el
resultado de una operacin est indeterminado, se obtiene NaN (Not a Number).
Tabla 1.4. Resultados de algunas operaciones establecidas por el estndar IEEE 754.
Operacin
1.0 / 0.0
-1.0 / 0.0
0.0 / 0.0
Resultado
+Inf
-Inf
NaN
Sin embargo, la mayora de los compiladores muestran un mensaje en tiempo de ejecucin avisando acerca
de estas situaciones, tales como:
Floating point error: Domain
Abnormal program termination
O bien:
Floating point error: divide by 0.
Abnormal program termination.
Punteros. Este tipo de datos se utiliza para declarar de forma explcita apuntadores a diferentes entidades
(variables, valores de retorno, y constantes). En este caso se utiliza como modificador el asterisco (*) con el
nombre de la variable, ms que con el tipo, por ejemplo: int *p. Generalmente, los programas no manejan de
forma rutinaria la conversin de tipos en punteros a excepcin de los siguientes casos [Dolland, 1994]:
o
Funciones de colocacin, por ejemplo malloc garantizan asignaciones vlidas. En este caso, siempre
se debe utilizar el operador sizeof para especificar el espacio de almacenamiento a ser asignado.
Size. Los punteros a un objeto de un tamao dado pueden ser convertidos a un puntero de un objeto
de tamao ms pequeo y retornar sin cambio. Por ejemplo, un puntero a un dato tipo long puede
ser asignado como puntero de variable tipo char, posteriormente puede regresar como puntero al
dato tipo long.
27
Estructuras. Las estructuras no son un tipo de dato bsico, sino que puede mantener datos de diferentes
tipos. El uso de este elemento ofrece una de las caractersticas ms importantes del lenguaje C. Su empleo
mejora la organizacin lgica de un programa, aporta un direccionamiento consistente, y generalmente
incrementar la eficiencia y el rendimiento en los programas. El uso de estructuras comunes para definir
elementos comunes permite al programa evolucionar (agregando otro elemento a la estructura), y permite
modificar la asignacin de almacenamiento. Por ejemplo, si un programa procesa smbolos donde cada
smbolo tiene un nombre, tipo, y un valor asociado, no se necesita definir vectores separados (ver Figura
1.12).
typedef struct simbolo
{
char *nombre;
int tipo;
int valor;
} tipo_simbolo;
tipo_simbolo tabla_simbolo[100];
Figura 1.12. Definicin de estructuras.
En el lenguaje C, la asociacin de un tipo de datos estecfico a un grupo de variables se realiza a travs de las
declaraciones. En las siguientes lneas se muestran ejemplos de declaraciones:
int a, b, c;
int dia, semana, mes;
char d, e;
float f, g;
long h, i, j;
double k, l, m;
Cuando se declaren varias variables del mismo tipo, deben estar en lneas separadas y con un comentario
acerca de su uso. Esto no se hace cuando las variables sean autoexplicativas (lnea 2 del ejemplo mostrado).
En este captulo se mostraron algunos puntos a considerar cuando se realiza un programa para computadora.
La intencin es realizar programas aplicando lo que se conoce como un buen estilo de programacin. El
objetivo de realizar programas con buen estilo, es que estos programas sern ms comprensibles al lector,
ser ms fcil realizar modificaciones, y ser ms fcil de mantener. Asimismo, con base a opiniones de
expertos, los programas escritos con buen estilo son ms eficientes que aquellos que son escritos sin aplicar
un buen estilo. Se realizaron algunos ejemplos en base al lenguaje C para clarificar la explicacin. Como se
mencion con anterioridad, no se pretende ofrecer en un captulo un curso de programacin en este lenguaje,
ms bien, se trata de clarificar la explicacin de los conceptos contemplados en base a esta herramienta. Se
incluye al final del captulo una serie de jercicios con el fin de reforzar lo visto en el mismo.
28
Ejercicios
1.1 Indicar a travs de un ejemplo los diferentes estilos utilizados para la indentacin en los programas.
1.2 De acuerdo con Kernigham y Plauger, existen 56 reglas de programacin que facilitan la escritura de
programas con buen estilo, liste ese conjunto de reglas.
1.3 Realizar un ejemplo de programa que haga una tarea especfica de dos maneras: a) sin utilizar funciones
y, b) utilizando funciones.
1.4 Realizar un programa que utilice el paso de parmetros a una funcin por valor y por referencia,
asimismo, identifique las variables globales y locales.
1.5 Escriba un programa que implemente y utilice una funcin para determinar si un nmero es positivo o
negativo. El programa debe leer un nmero entero por teclado y debe mostrar en la pantalla si el nmero
ledo es positivo o negativo haciendo uso de la funcin definida.
1.6 Realizar una funcin que, dada una cadena de caracteres y un carcter, devuelva el nmero de
apariciones de dicho carcter en la cadena. El programa debe leer una cadena de caracteres por teclado
y mostrar en la pantalla el nmero de apariciones en la cadena de cada una de las vocales haciendo uso
de la funcin definida.
1.7 Escribe un programa en C que simule una pequea calculadora que implementa las siguientes
operaciones:
Multiplicacin
Suma
Resta
Divisin
Potencia
Raz cuadrada
Todas las operaciones deben ser implementadas como funciones. La seleccin de la operacin se
realizar mediante un pequeo men desplegado por pantalla. Cada operacin utilizar dos operandos.
1.8 Se tiene una matriz de temperaturas (valores reales positivos y negativos) en distintos puntos medidas en
distintos das (matriz n x m, siendo n, el nmero de puntos, y m el nmero de das). Calcula la media y la
dispersin de temperatura en cada uno de esos das. La dispersin es la raz cuadrada de la varianza, y
la varianza a su vez se calcula de la siguiente forma:
donde
desarrolla un programa en C que contenga una funcin que reciba como argumento un puntero a un
29
vector que contiene todas las temperatura tomadas en un da, o sea un vector de tamao n, y que
devuelva en otro vector de tamao 2, la media y la dispersin. Para ello esta funcin se ayudar de otras
dos funciones de tipo real que calcularn la media y la varianza respectivamente; la primera recibiendo
un puntero al vector de temperaturas y la segunda un puntero al vector de temperaturas y la media ya
calculada. El programa principal deber, utilizando sucesivas llamadas a la funcin, mostrar por pantalla
la media y la dispersin para cada uno de los das (m). Para el problema considera cualquier valor para n
y m que quieras, pero ten en cuenta que debe resultar fcil cambiarlos en el programa (uso de
constantes).
30
Captulo 2
PROGRAMACIN FUNCIONAL
Introduccin
Realizar un programa, o programar bajo el paradigma de la programacin funcional, consiste en construir
definiciones (en base a funciones) y usar la computadora para evaluar tales expresiones. El papel bsico de
un programador es construir funciones con el fin de resolver problemas. Las funciones creadas pueden a su
vez contener otras funciones (por ejemplo, (
los principios matemticos. En este paradigma, la
calculadora de bolsillo. Sin embargo, la habilidad del programador en la construccin de definiciones puede
incrementar su poder de clculo. La asignacin de nombres a las funciones forman expresiones, las cuales
son evaluadas a travs de reglas de reduccin o simplificaciones, transformando las expresiones a un formato
comprensible en la pantalla de la computadora. Un rasgo distintivo de la programacin funcional es que si
una expresin posee un valor bien definido, entonces el orden en el que una computadora
realiza la
evaluacin no afecta el resultado . Es decir, el significado de una expresin est determinado por su valor y la
tarea de una computadora es obtenerlo. De aqu se desprende que las expresiones en un lenguaje funcional
se pueden construdas, manipuladas y razonadas, al igual que cualquier otro tipo de expresin matemtica, a
travs de las conocidas leyes algebraicas. El resultado, como es de esperar, es un marco conceptual para la
programacin, que resulta muy simple, conciso, flexible y muy potente [Bird, 1988].
Aunque los lenguajes funcionales estn basados en el clculo lambda, el cual a su vez se basa en las
definiciones y aplicaciones de funcin, los lenguajes funcionales actuales ofrecen mayores caractersticas
para una programacin conveniente.
Las principales
Esto no sucede en la programacin imperativa donde las instrucciones se evaluan de forma secuencial,
siguiendo la arquitectura de Von Newman y sujeta a la mquina.
31
caractersticas y ventajas que ofrece un lenguaje de programacin funcional son las siguientes: a) los
programas funcionales no contienen instrucciones de asignacin, esto conlleva a que las variables, una vez
que se les ha asignado un valor, ste nunca cambia. De forma ms general, esto (b) no genera efectos
colaterales (side-effect), es decir, una funcin no cambia su valor cada vez que es llamada, esta propiedad
elimina una fuente potencial de errores y vuelve el orden de ejecucin irrelevante, y ya que no existen
efectos colaterales, una expresin
especificacin del flujo de control. Ya que las expresiones pueden ser evaluadas en cualquier momento, el
programador puede libremente reemplazar variables por sus valores y viceversa, esto es, los programas
poseen una referencia transparente. Esta caracterstica hace que los programas funcionales sean
matemticamente ms tratables que sus contrapartes imperativos [Goldberg, 1996].
Lo anterior indica algunas ventajas, es decir, lo que es, ahora digamos lo que no es. El programador funcional
menciona grandes ventajas de este estilo de programar en relacin a los lenguajes convencionales (ya que
sus programas son en orden de magnitud ms cortos), sin embargo, hasta dnde son ciertas? Lo nico
cierto de los programas funcionales, es que no tienen sentencias de asignacin y los convencionales
consisten en su 90% de tales sentencias. Si omitir las sentencias de asignacin trae enormes beneficios,
entonces Qu estuvieron haciendo los programadores de FORTRAN durante veinte aos? Resulta poco
comprensible desarrollar un lenguaje ms poderoso a travs de omitir caractersticas sin importar que tan
malo pueda ser. Uno no puede escribir un programa el cual carece de sentencias de asignacin o es
transparente a las referencias. No existe aqu ningn criterio de la calidad del programa y por lo tanto no hay
forma de entenderlo.
Obviamente esta caracterizacin del programa funcional no resulta del todo convincente. Por lo tanto, se debe
encontrar algo que no solamente explique el poder de un lenguaje funcional, sino tambin, que ofrezca una
clara indicacin hacia donde debe el programador enfocar su esfuerzo.
La base terica de la funcin matemtica como modelo de programacin tiene sus races en los dcadas de
1920 y 1930. Los fundadores de este paradigma son entre otros, Haskell Curry (Inglaterra), Alonzo Church
(USA) y M. Schonfinkel (Alemania). A principios de 1950, John McCarthy basndose en esta teora dise
Lisp, el cual se identifica como el primer lenguaje de programacin funcional. Aunque todava se usa, no es
un lenguaje que rena las exigencias que requieren las aplicaciones actuales. Algunos lenguajes que
surgieron posteriores (en la dcada de 1980) a Lisp fueron, Scheme (una extensin de Lisp), ML, Hope y
Miranda. A principios de 1990 surgi el lenguaje Haskell, el cul integraba las mejores prestaciones de los
lenguajes funcionales creados hasta ese momento. Posteriormente, a mediados de 1990, Jeroen Fokker cre
el lenguaje Gofer en la Universidad de Utrecht, Holanda. Gofer es un lenguaje de programacin funcional que
utiliza un conjunto reducido de instrucciones de Haskell, es decir, es un Haskell ms simple. Este lenguaje fue
pensado para usarse en investigaciones tericas y con fines educativos.
En este captulo se van a mostrar las principales caractersticas que poseen los lenguajes funcionales. De
forma especfica, los ejemplos de programas mostrados, se van a basar en un lenguaje funcional puro:
Haskell. Sin embargo, tambin se van a mostrar ejemplos de programas realizados con el lenguaje Gofer, el
cul tambien es un lenguaje funcional puro. En realidad, Gofer se considera un lenguaje enfocado al mbito
acadmico, fue desarrollado con esa finalidad, este lenguaje posee un conjunto de instrucciones, que es
subconjunto de las instrucciones de Haskell.
32
Uno de los aspectos ms importantes de cualquier programa es, sin duda, el tema de los datos. Los datos no
son otra cosa ms que el universo de valores que puede tomar una expresin. Asimismo, los datos son el
soporte de la informacin, y la informacin es el elemento bsico de cualquier programa. Los lenguajes de
programacin utilizan las variables para almacenar datos y manipularlos en una amplia variedad de maneras.
Est claro que los datos pueden ser de distintos tipos: nmeros, caracteres, booleanos, y tuplas.
Los distintos lenguajes de programacin tienen en cuenta esta particularidad de los datos, y por lo tanto, todos
tienen alguna forma de distincin entre los tipos que manejan. En algunos lenguajes funcionales, entre ellos
Haskell y Gofer, a las variables que se van a manejar se les deben indicar sus tipos a travs de una
declaracin, asimismo, las expresiones deben manejar datos y/o variables con tipos previamente declarados,
no se hacen asumciones implcitas acerca del tipo de los datos as como tampoco conversiones.
En este
sentido se dice que estos lenguajes son fuertemente tipados [Fokker, 1995; Cuningham, 2010].
A continuacin se describen los tipos de datos bsicos utilizados en la programacin funcional (nmeros,
Booleanos, caracteres y cadenas).
Nmeros. los datos numricos pueden ser de dos tipos: enteros y de punto flotante, ejemplos de estas
representaciones son: 37, -65, 0, 23.98, -0.6, 7.0. Los nmeros de punto flotante tambin se pueden
representar como 3.876e3, donde la letra e, indica por 10 elevado a, de esta manera 3.876e3 indica al
nmero 3.876x103, es decir, 3,876.
Los datos numricos utilizan para su procesamiento las operaciones mostradas en la Tabla 2.1. El Gofer es
un lenguaje interpretado y utiliza los identificadores Int y Float para reconocer los datos numricos de tipo
entero y de punto flotante [Fokker, 1995].
En Gofer, los tipos deben escribirse iniciando con mayscula. Si se quiere determinar el tipo de una expresin
se debe utilizar el comando :type seguido de la expresin, por ejemplo:
? :type 7+8
7 + 8 :: Int
En el intrprete Gofer :: significa: es del tipo, el comando type no evala la expresin, solamente determina
el tipo. En las lneas anteriores, la segunda lnea indica que el tipo resultante de la operacin 7 + 8, es entero
(Int).
33
16
-> Resultado
? 5*4
-> Operacin
20
-> Resultado
? 5/2
-> Operacin
-> Resultado
Tabla 2.1. Operaciones aritmticas bsicas reconocidas por los lenguajes funcionales.
Operador
En operaciones de divisin, si esta se realiza entre nmeros enteros, el resultado es truncado a la parte
entera. Si se desea incluir la parte fraccionaria, la operacin debe realizarse con nmeros de punto flotante,
por ejemplo:
? 5.0/2.0 -> Operacin
2.5
-> Resultado
Asimismo, en estas operaciones no se permite combinar tipos enteros con reales. Por ejemplo, la operacin
indicada en la siguiente lnea mostrar un error de aplicacin.
34
? 5.0/2
Aunque no se mostraron en las lneas anteriores es vlido el uso de varios operadores en una expresin, por
ejemplo:
?
38
Resulta claro que cuando aparecen varios operadores debe existir un orden de evaluacin, tal como se
mostr en el caso de los lenguajes imperativos. Sin embargo, este tema se va a tratar ms adelante en la
seccin 2.4 relacionada con los operadores.
Datos Booleanos. La realizacin de programas sera muy limitada si los datos numricos fueran los nicos
tipos de datos disponibles y las nicas operaciones fueron las descritas en la seccin anterior. Sin embargo,
en la vida cotidiana es muy comn realizar la comparacin de nmeros y en base al resultado de la misma
tomar algunas acciones. Por ejemplo, nos gustara saber si dos expresiones numricas son iguales. Para ello
necesitamos los valores de verdad. Hay dos expresiones cannicas para denotar valores de verdad:
verdadero y falso. Estas dos expresiones constituyen el tipo de datos BOOL de valores booleanos (llamado
as por el lgico George Boole en el siglo XIX). Asimismo, a una funcin que devuelve valores booleanos se
le llama predicado. Los valores booleanos son importantes ya que ellos retornan el resultado generado por
las operaciones de comparacin, estas operaciones se muestran en la Tabla 2.2.
Tabla 2.2. Operadores que retornan valores booleanos.
Operador
/=
Comparacin a realizar
Igualdad
Diferentes
Menor que
Mayor que
Menor o igual que
Mayor o igual que
A continuacin se muestra un par de ejemplos escritos aplicando el intrprete de Gofer para ilustrar el uso de
estos operadores y el resultado que retornan:
? 2 = = 3->
Instruccin
False
->
Resultado
->
Instruccin
->
Resultado
?2
True
1+3
Todos los operadores de comparacin tienen el mismo nivel de precedencia y como se observa en la lnea 3
(? 2
35
comparan solamente datos numricos, sino, que pueden manejar expresiones con diferentes tipos de
argumentos. La nica restriccin consiste en que los argumentos sean del mismo tipo (nmeros, caracteres,
booleanos), por ejemplo:
? False == True
->
False
Instruccin
->
->
True
Resultado
Instruccin
->
Resultado
Como lo indica la lnea 3 anterior, los valores booleanos estn definidos de tal forma que False es menor que
True.
Caracteres y cadenas. Adems de manejar los tipos de datos numricos y booleanos, las expresiones en
definidas en un lenguaje funcional pueden tambin manejar los tipos de datos carcter y cadenas de
caracteres. Generalmente las computadoras manejan un conjunto bsico de 128 caracteres en formato
ASCII5, compuesto de signos visibles y caracteres de control. Estos caracteres constituyen el tipo de datos
char, los cuales son proporcionados como caracteres primitivos en la mayora de los lenguajes de
programacin. La convencin para el manejo de este tipo de datos es que deben ir encerrados entre comillas
simples (o dobles para cadenas), por ejemplo:
->
Instruccin
->
Resultado
? H
->
Hola
?
123
Instruccin
->
->
Resultado
Instruccin
->
Resultado
Una caracterstica interesante entre los caracteres y las cadenas de caracteres consiste en la visualizacin,
pues, mientras los caracteres se visualizan entre comillas sencillas, las cadenas de caracteres se visualizan
sin las comillas dobles con que fueron introducidos como argumentos.
Es importante recordar que la cadena 123 es diferente al dato numrico 123, ya que la primera representa
un dato tipo char, mientras que la segunda es un dato tipo numrico. El almacenamiento interno que realizan
las computadoras para este tipo de datos es diferente, ya que mientras a los datos tipo char los almacena y
maneja de acuerdo al formato ASCII, a los datos tipo numrico los almacena y maneja generalmente con el
formato hexadecimal. Por lo tanto, no es posible realizar operaciones aritmticas con los datos tipo char, es
5
36
decir, el intrprete o compilador no las evala, simplemente las despliega. En el paradigma declarativo, al cual
pertenecen los lenguajes funcionales se proporcionan dos funciones para el manejo de datos tipo char, code y
decode. La funcin code :: char num convierte un carcter a su correspondiente cdigo ASCII, mientras
que decode :: num char, realiza la operacin inversa, por ejemplo:
?
97
? decode 97
Como se mencion, con este tipo de datos no se pueden realizar operaciones aritmticas, sin embargo, si es
posible realizar las operaciones de comparacin, por ejemplo, se puede verificar si dos caracteres o cadenas
de caracteres son iguales. Las lneas siguientes muestran algunas operaciones vlidas con este tipo de datos:
?
True
? A
True
? H
False
De las lneas anteriores se observa que los datos tipo char se comparan y evalan de acuerdo a su cdigo
ASCII. En este estndar, las letras maysculas tienen un cdigo menor que las minsculas (A = 65, a = 97).
Adems de los tipos bsicos mencionados, los lenguajes funcionales tambin permiten el manejo de tipos
combinados bajo el nombre de tuplas [Bird, 1988]. El uso de tuplas permite combinar pares de tipos conocidos
para formar nuevos tipos, por ejemplo, el tipo (num, char) consiste en todos los pares de valores para los
cuales el primer valor es un nmero y el segundo un carcter. De manera particular los pares (3, a) y (12.8,
+) son valores de tipo (num, char). De esta forma, es posible el manejo de expresiones como las mostradas
en las siguientes lneas:
? 4
a
? (3, 4) = (4, 3)
False
? (3, 6) < (4, 2)
37
True
, o si
, esta se
Una vez mostrados los tipos de datos bsicos que manejan los lenguajes funcionales, as como la
combinacin de los mismos para formar nuevos tipos, el siguiente prrafo va a tratar de las funciones, las
cuales son el elemento bsico de la programacin funcional.
2.2 Funciones
La base de un programa basado en el paradigma funcional es la funcin matemtica. Una funcin matemtica
, es un mapeo
objetos B conocidos como el rango de tal forma que, para cualquier elemento
aplicacin de
ecuacin
de A, el trmino
de B. Generalmente,
(la
se
se reemplaza por .
Por ejemplo:
4.
indica el
Las funciones pueden tomar cualquier tipo de valor como argumento y regresar
cualquier tipo de valor como resultado. Asimismo, el argumento de una funcin puede ser otra funcin, a estas
funciones se les conoce como funciones de orden alto.
En Gofer existen funciones primitivas, funciones predefinidas y funciones definidas por el usuario. Las
funciones primitivas son pocas y forman parte del compilador, tal como la funcin primPlusInt que suma dos
enteros. A su vez, las funciones predefinidas son funciones de mayor uso, tales como reverse, sort, sum,
sums, entre otras, y se encuentran almacenadas en un archivo llamado estndar.prelude (ver Anexo A).
A la vez, existen funciones definidas por el usuario, por ejemplo en la definicin de la funcin:
fac n = product [1..n]
fac es el nombre de la funcin y n su parmetro. Esta funcin hace uso de una funcin predefinida: product, la
cual realiza el producto de nmeros almacenados en una lista, de tal forma que la aplicacin fac 5, realiza la
operacin
En la definicin de funciones en Gofer, se deben seguir ciertas reglas. Los nombres de las funciones y los
parmetros tienen que empezar con una letra minscula. Despus pueden seguir ms letras (minsculas y
38
maysculas), pero tambin se permite el uso de nmeros y de los smbolos y _. Las letras minsculas
y maysculas son diferentes para el intrprete. Algunos ejemplos de nombres de funciones y parmetros son:
f
sum
x3
tot_de_macht nombreLargo
El smbolo _ se usa muchas veces para hacer ms legibles los nombres largos.
Los nmeros y apstrofos en un nombre pueden ser usados para enfatizar que un nmero de funciones o de
parmetros tienen relacin unos con otros. Esto es solamente til para el usuario humano, para el intrprete el
nombre x3 tiene tan poca relacin con x2 como con qXa_y. Los nombres que empiezan con una letra
mayscula se usan para funciones especiales y constantes, las llamadas funciones constructoras.
En Gofer, existen 16 palabras que no se pueden usar para nombrar funciones o variables. Estas palabras
reservadas tienen un sentido especial para el intrprete y son las siguientes:
case
class
data
else
if
infixr
in
infix
infixl
instance
primitive
then
type
where
let
of
Existen funciones definidas sobre nmeros, valores booleanos, listas y funciones. A continuacin se ofrecer
una descripcin de estas funciones.
Funciones definidas sobre nmeros. Como ya se mencion con anterioridad, existen dos tipos de nmeros
en Gofer:
La letra e, indica por 10 elevado a, de esta manera 3.876e3 indica al nmero 3.876x103, es decir, 3,876.
Se pueden utilizar los cuatro operadores arimticos (+, -, *, /) en operaciones con los nmeros enteros y
reales como ya se mostr en la seccin anterior.
Para operaciones con nmeros reales, Gofer tiene las siguientes funciones primitivas:
sqrt
sin
log
exp
Asimismo, existen dos funciones primitivas que relacionan nmeros enteros y reales.
fromInteger
round
39
Tanto los nmeros enteros como los reales no pueden rebasar un mximo permitido, el cul est fijado por la
arquitectura del procesador existente en la mquina utilizada. En equipos pequeos, esta cantidad esta fijada
a2
15
38
31
308
para
Funciones Booleanas. Una funcin booleana de n variables f(x1, x2, x3,,xn) es una expresin o frmula, que
mapea f a un valor del conjunto de valores booleanos (0 (False), 1(True)). Generalmente, se obtienen un
resultado del tipo booleano cuando las variables son combinadas con operadores relacionales (<, >,).
Por ejemplo, el operador < comprueba si un nmero es menor que otro, si esta relacin se cumple, el
resultado es la constante True, de otra forma el resultado es la constante False. En las siguientes lneas
escritas en el intrprete de Gofer se observa esto,
? 39<21
False
? 21<39
True
Los valorres True y False son los nicos elementos del conjunto de valores booleanos. Las funciones y
valores de las que resultan estos valores se conocen como funciones boolenas (39<21, 21<39).
Los operadores relacionales permitidos en Gofer son los siguientes:
<
menor que
>
mayor que
<=
>=
==
igual que
/=
diferente que
40
? 5*3 /= 10+2
True
Resulta comn observar en programas la combinacin de las funciones booleanas con operadores lgicos
(&& (and, conjuncin), not (negacin), || (or, disyuncin)), por ejemplo:
? 1>2 || 3<5
True
? 1<2 && 5<2
False
? not False
True
? not (5<9)
False
? even 4
True
La funcin (even) descrita en la ltima lnea verifica si un nmero entero es par o impar.
Funciones sobre listas. En el archivo preludio, Gofer tiene definidas varias funciones y operadores
relacionados con listas. Algunas de las funciones sobre listas predefinidas en este archivo son las siguientes:
sort [ ]
reverse [ ]
Asimismo, en el archivo preludio se encuentran dos operadores sobre listas, el operador : que coloca un
elemento al frente de la lista, y el operador ++ que concatena 2 listas en una sola, por ejemplo.
? 3 : [5, 2, 9]
[3, 5, 2, 9]
? [2, 3, 4, 5] ++ [8, 9, 10]
[2, 3, 4, 5, 8, 9, 10]
41
Tambin se encuentra en este archivo la funcin null, la cul es una funcin booleana que se aplica sobre
listas y verifica si la lista est vaca, si esto es cierto, null devuelve True, y False en caso contrario. En este
mismo sentido, la funcin and al ser aplicada sobre listas, se aplica a cada uno de sus elementos y
comprueba si cada uno de ellos es True, por ejemplo.
? null [ ]
True
? and [1<2, 3>2, 1==5]
False
Existen funciones que utilizan dos parmetros, por ejemplo, take. Esta funcin utiliza como parmetros un
nmero n y una lista, y devuelve como resultado una lista con los primeros n elementos de la lista original, por
ejemplo.
? take 4 [1..10]
[1, 2, 3, 4]
Funciones de funciones. Hasta el momento, las funciones vistas han recibido como parmetros nmeros,
valores booleanos o listas. Sin embargo, el parmetro de una funcin tambin puede ser otra funcin. Un
ejemplo es la funcin map, la cul utiliza dos parmetros: una funcin y una lista. Esta funcin aplica el
parmetro funcin a todos los elementos de la lista. Por ejemplo:
? map sqrt [9, 16, 25]
[3, 4, 5]
? map even [1..8]
[False, True, False, True, False, True, False, True ]
Definicin de funciones. Las funciones pueden definirse de varias maneras: a) por combinacin, b) por
distincin de casos, c) por anlisis de patrones, y d) por induccin o recursin. En este caso se va a mostrar
una de las definiciones ms usuales, la definicin a partir de la combinacin con otras funciones. Adems,
esta resulta ser una de las definiciones ms simples, por ejemplo, en la siguiente lnea:
42
Se est definiendo la funcin fac utilizando la funcin predefinida product. En este caso es fcil concluir que se
est definiendo la operacin para obtener el factorial de un nmero. De esta manera, fac 4 equivale al
4. Otros ejemplos de esta forma de definir funciones se ofrecen en las siguientes
producto de
lneas:
impar
= not (even )
cuadrado
comb n k = fac n
(fac k
fac (n-k))
4
Una vez que la funcin se ha definido puede ser utilizada, tal como ocurre en la lnea 3, donde se est
utilizando la funcin fac para definir la funcin comb.
En la funcin formulaABC la expresin
ocasiones, esta situacin adems de la escritura exige ms tiempo de clculo, ya que se calcula en dos
ocasiones la misma expresin. Para evotar esto, y hacer al programa ms eficiente, en Gofer se pueden
reemplazar subexpresiones por un nombre, por ejemplo para reescribir a la funcin mencionada como sigue:
formula2ABC a b c = [( b+d) n]
, ( b d) n]
where d = sqrt (b b 4
n=2.0 a
La palabra where no es el nombre de una funcin. Es una de las palabras reservadas utilizadas por Gofer. En
el uso de where hay algunas definiciones. En el caso mostrado, las definiciones de las constantes d y n.
Estas constantes se pueden usar en la expresin anterior a la parte de where. Fuera de este lugar no se
pueden usar: son definiciones locales. Puede parecer extrao que se llamen a d y n constantes, porque el
valor puede ser diferente para cada vez que se llama a la funcin formula2ABC. Pero durante el cmputo de
una llamada a formula2ABC, con valores para a, b y c, los valores de d y n son constantes.
La simplificacin de expresiones se traduce en una ejecucin ms eficiente tal como puede observarse en las
lneas i-iii vs lneas a-c, al utilizar la opcin estadstica (comando :set +s) del intrprete6:
En este caso reductions es una representacin del tiempo de ejecucin y cells de la mamoria utilizada.
43
pi = 3.15926535
e= exp 1.0
Por lo tanto, y tomando en cuenta los ejemplos anteriores, el procedimiento para definir una funcin es el
siguiente:
a)
b)
c)
Se declara el smbolo ,
d)
Se indica una expresin, que puede contener parmetros, funciones previamente definidas o
funciones primitivas.
Cuando la funcin descrita es booleana, el resultado obtenido, expresin a la derecha del smbolo igual, debe
ser booleano, por ejemplo:
?
?
?
Ejemplos de funciones
Ejercicio 1. Realizar una funcin que reciba como argumento una fecha en formato ddd aaaa, donde ddd es
un nmero comprendido entre 1 y 365, indicando un nmero de da del ao y aaaa el ao. La funcin debe
regresar el mes, da del mes y da de la semana en que ocurri la fecha de entrada, por ejemplo:
? fecha 300 1993
mes: Octubre
dia: Mircoles 27
divisible :: Int -> Int -> Bool
44
divisible t n = t `rem` n == 0
numeroDia d a = ( (a-1)*365
+ (a-1)/4
- (a-1)/100
+ (a-1)/400
+ sum (take (numes d-1) (meses a))
+ d
) `rem` 7
meses a = [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
where feb | bisiesto a = 29
| otherwise
= 28
mes 1
= "mes: enero\n"
mes 2
= "mes: febrero\n"
mes 3
= "mes: marzo\n"
mes 4
= "mes: abril\n"
45
mes 5
= "mes: mayo\n"
mes 6
= "mes: junio\n"
mes 7
= "mes: julio\n"
mes 8
= "mes: agosto\n"
mes 9
= "mes: septiembre\n"
&& d<=31
= 1
| d>=32
&& d<=59
= 2
| d>=60
&& d<=90
= 3
| d>=91
&& d<=120 = 4
numdia d
| d>=1
&& d<=31 = d - 0
| d>=32
&& d<=59 = d - 31
| d>=60
&& d<=90
| d>=91
= d -59
46
Ejercicio 2. Escriba una funcin dividir, de manera que, dada una lista de caracteres de como resultado otra
lista, pero ahora dividida en lneas. Cada vez que haya dos caracteres seguidos que sean iguales se insertar
en el resultado una nueva lnea (entre los dos caracteres iguales), por ejemplo:
? dividir "abbcdefffabdde"
ab
bcdef
f
fabd
de
En la Figura 2.3 se muestra el programa dividir.hs realizado en Gofer y que realiza la tarea descrita. En este
caso, la funcin dividir recibe como argumento una lista de caracteres (lst), posteriormente y a travs de la
palabra reservada let se establecen las condiciones para el despliegue de caracteres por lnea. El programa
47
Figura 2.2. Ilustracin de la ejecucin del programa fecha.hs, se observa que se proporcionaron como
argumentos los valores de 67, el cual corresponde a un valor de da entre 1 y 365, as como el ao de 2008.
Se observa que estos valores corresponden al da lunes 8 de marzo del 2008.
inicia comparando el primer elemento de la lista (x) con el elemento posicionado en la cabeza de lista (h)7. Si
estos elementos son iguales, se inserta un carcter de control \ en esa posicin con el fin de realizar el salto
de lnea.
dividir
En Gofer, los elementos de una lista se identifican por un elemento colocado en la cabeza (header, h) de la
48
Por otro lado, si la condicin anterior no se cumple, el carcter comparado se agrega a la lista. La Figura 2.4
muestra un ejemplo de la ejecucin del programa descrito en la Figura 2.3, se observa la entrada de una
cadena de caracteres, la cual como ya fue mencionado con anterioridad debe ir entre comillas dobles. Se
observa que esta funcin se almacen en un archivo tipo scripts (biblioteca) el cul fue almacenado
previamente con el fin de que Gofer busque en ese archivo la funcin a ejecutar (de otra manera la buscara
en el archivo de uso estndar: prelude.standard), antes de ejecutar la funcin dividir.
Con la finalidad de que el estudiante aprenda el uso de algunas funciones primitivas, se deja que en los
siguientes ejercicios realice una breve descripcin de cada funcin desarrollada. Para esto se puede apoyar
en los textos de programacin funcional realizados por Fokker (1995) y Cuningham (2010).
Ejercicio 3. Escriba una funcin nded (nmero de elementos distintos), que, dada una lista cualquiera de
nmeros, devuelva cuantos nmeros distintos existen en la lista. Una posibilidad de resolver este problema es
contar solamente la primera ocurrencia de cada nmero en la lista.
49
nded2::(Eq a)=>[a]->Int
nded2 = let cont (x,xs) y | elem y xs = (x,xs)
| otherwise = (x+1,y:xs)
in fst .(foldl cont (0,[]))
(a)
(b)
Figura 2.5. Programa (a) y ejemplo de ejecucin (b) correspondiente al ejercicio 3.
Ejercicio 4. Escriba una funcin diag que tome una lista de caracteres como parmetro y que d como
resultado los caracteres en una diagonal, por ejemplo:
? diag [a,b,c,d,e]
a
b
c
d
e
El programa desarrollado (en este caso se utiliz Haskell), as como un ejemplo de su ejecucin se muestra
en la Figura 2.7.
(a)
50
(b)
Figura 2.6. (a) Funcin que realiza la tarea descrita en el Ejercicio 4, (b) ejemplo de ejecucin de la funcin
solicitada en el Ejercicio 4.
Ejercicio 5. Escriba una funcin elimDobles, que, dada una lista (que puede ser infinita), devuelva una nueva
lista, con solamente una ocurrencia de cada elemento de la lista original. El problema en este ejercicio es que
la lista puede ser infinita. Por eso, no puede usar las funciones foldr y foldl.
La Figura 2.7 (a) ilustra la funcin que realiza la tarea solicitada en el Ejercicio 5, asi como un ejemplo de su
ejecucin (Figura 2.7 (b)).
elimDobles []=[]
elimDobles (x:xs) = x:(elimDobles (filter (/=x) xs))
(a)
51
(b)
Figura 2.7. (a) Funcin elimDobles y (b) Ejecucin de la funcin.
Aunque en esta seccin se mostraron aspectos generales relacionados con el manejo de funciones en los
lenguajes funcionales, especficamente Gofer y Haskell, tambin se mostraron en los ejercicios el uso de
algunos operadores. Sin embargo, la seccin que sigue va a mostrar ms aspectos relacionados con el uso
de los operadores en este tipo de lenguajes.
2.3 Operadores
Los operadores nos permiten realizar operaciones entre los argumentos, variables y literales presentes en una
expresin. Aunque en las secciones anteriores ya se mostraron como los operadores actan en la realizacin
de operaciones, en esta seccin se van a mostrar aspectos adicionales de estos elementos. En Gofer el
usuario puede definir sus propios operadores. En el archivo prelude (ver Anexo A) estn definidos ms de
doscientas funciones estndar y operadores.
Nombres para operadores. Los nombres de operadores contienen uno o ms smbolos. Un operador puede
consistir en un smbolo como +, pero tambin de dos (&&) o ms (!^!) smbolos. Los smbolos que se pueden
usar para formar operadores son los siguientes:
#
@
$
^
%
|
&
<
>
52
++
/\
&&
...
||
<+>
<=
?
==
:->
/=
//
@@
-*- \/
Precedencia de operadores. En la Tabla 2.1 se mostraron los operadores que se pueden aplicar con datos
numricos. La mayora de los operadores mostrados se puede utilizar como un operador binario infijo (en
medio de), es decir, entre dos argumentos, por ejemplo,
(
pueden ser utilizados como un operador unario de prefijo (antes del argumento), esto es, estos
indica el negativo de
Adems de lo
indicado, otro aspecto a considerar en el uso de los operadores utilizados con datos numricos, es su orden
de evaluacin o precedencia. Esto es importante para determinar el resultado esperado de una operacin
cuando en una expresin aparecen varios operadores. Las reglas de precedencia de operadores nos indican
que operador es el que tiene mayor prioridad en su evaluacin. Al igual que otros lenguajes de programacin,
el uso de parntesis permite modificar el orden de evaluacin. Se puede considerar que, en general, el nico
propsito de las reglas de precedencia es reducir el nmero de parntesis en una expresin. La Tabla 2. 3
muestra el orden de evaluacin de los operadores empleados en operaciones con datos numricos.
Tabla 2.3. Reglas de precedencia en operadores numricos.
Operador
Prioridad de evaluacin
Exponenciacin
Multiplicacin, Divisin flotante, Divisin entera,
Residuo de una divisin.
Suma, Resta
De acuerdo con lo observado en la Tabla 2.3, el operador con la mayor prioridad de evaluacin es el de
exponenciacin. Con el fin de ilustrar las reglas de precedencia se muestran las siguientes operaciones a
continuacin:
i) 3 2 5
ii) 3 + 4.5 7
significa (3
5
significa 3 + (4.5 7)
Como mencion con anterioridad, este orden de evaluacin se puede modificar, al igual que en la notacin
matemtica formal, con el uso de parntesis, por ejemplo, (
Orden de asociacin. Adems del orden de evaluacin, existe otro elemento que reduce el uso de parntesis
en las expresiones con operadores de igual prioridad: el orden de asociacin. En las expresiones aritmticas
los operadores generalmente se asocian de izquierda a derecha, por ejemplo, 3 + 4 + 5, equivale a: (3 + 4) +
5. El operador de exponenciacin asocia de derecha a izquierda, de este modo, 2
5 equivale a: 2
(3
5).
53
orden de precedencia indica el orden en que se van a realizar las operaciones aritmticas, de tal forma que si
aparecen en Gofer las operaciones indicadas en la siguiente lnea:
? 3+7
17
Tabla 2.4 Precedencia de operadores manejados en Gofer.
Asociacin
infixl
Prioridad
9
Operador
!!
infixr
infixr
infixl
infixl
infix
9
8
7
6
5
.
^
*, /, div, quot, rem, mod
infixr
infix
infix
5
4
4
++, :
==, /=, <, <=, >=, >
`elem`, `notElem`
infixr
infixr
infixr
3
2
0
&&
||
$
Operacin
Selecciona un elemento
de una lista
\\
Exponenciacin
Sumar, restar
Eliminar elemento de
una lista
Incremento, lista.
Comparadores
Es (no es ) elemento de
una lista
Y Lgico
O Lgico
en la Tabla 2.4, se indica a travs de prefijos (infix, infixr, infixl) el orden en que las operaciones se asocian,
para el caso de las operaciones de sumar y restar el orden de asociacin es de izquierda a derecha (infixl), es
decir, en la lnea anterior, primero se realiza la operacin de resta (-1) y posteriormente se realiza la operacin
de suma (-1+8). El prefijo infixr indica que la asociacin es de derecha a izquierda, tal como ocurre con las
operaciones de exponenciacin (3^2^4 = 3^(2^4)), lgicas (||, &&) y de incremento (++).
54
Operadores Booleanos. Los operadores booleanos se muestran en la Tabla 2.2, los seis operadores tienen
el mismo nivel de precedencia, y tal como se muestra en la tabla 2.4, tienen un nivel de evaluacin menor al
de los operadores aritmticos, de tal forma que la operacin:
operadores de comparacin no se limitan solamente a nmeros, sino, que pueden tomar argumentos de
cualquier tipo, la nica restriccin es que estos argumentos deben ser del mismo tipo.
Operadores Lgicos.
operadores lgicos:
&&
||
not
Y lgico
O lgico
negacin lgica
->
Instruccin
True
->
Resultado
->
Instruccin
False
->
Resultado
? not False
->
Instruccin
True
->
Resultado
Las reglas de precedencia en estos operadores son como sigue: el operador not tiene la mayor prioridad, le
siguen el && y finalmente el ||. Sin embargo, si existe alguna duda en la precedencia de los operadores
siempre queda el recurso del uso de los parntesis para evitar ambigedades.
Con el fin de dar un ejemplo8 en el uso de estos operadores, vamos a suponer que deseamos crear una
funcin para determinar si un ao es bisiesto o no. De acuerdo con el calendario Gregoriano, un ao es
bisiesto si es divisible por 4, excepto si es divisible por 100, en ese caso tambin debe ser divisible por 400.
Lo anterior puede ser expresado de diferentes maneras. Una consiste en definir:
bisiesto a = (a mod 4 == 0) && (a mod 100 =/ 0 || a mod 400 == 0)
Ejemplo tomado de Richard Bird and Philip Wadler. Introduction to functional programming. Prentice Hall International (UK)
Ltd, Simon & Schuster International Group, UK, 1988.
55
En el primer caso se observa el uso de operadores lgicos y booleanos, mientras que en el segundo se
observa el uso de la combinacin (if otherwise) para definir la funcin bisiesto a.
?[1, 1]=[1]
False
?[1, 2,3]=[3, 2, 1]
False
Construccin de una lista. Existen varias maneras de construir una lista, por enumeracin, por construccin,
y a travs de intervalos numricos. A continuacin se va a mostrar brevemente cada una de las formas
mencionadas.
a) Enumeracin. La enumeracin de elementos es la forma ms simple de construir una lista. Los elementos
deben ser del mismo tipo, por ejemplo:
[1, 3, 5, 7]
::
[Int]
[1+2, 3*4]
::
[Int]
::
[[Int]]
[False, True]
::
[Bool]
::
[Bool]
::
56
No hay restricciones en el nmero de elementos que puede contener una lista. La lista con solo un elemento
se le conoce como singleton, por ejemplo, la lista [[1, 2, 3]] es un singleton.
b) Construccin. Otra forma de crear o construir una lista consiste en utilizar el operador :. Este operador
agrega un elemento al inicio de una lista hacindola ms grande. De esta manera, si xs es la lista [1, 2, 3],
entonces 1:xs es la lista [1, 1, 2, 3]. Utilzando la lista vaca [ ] y el operador : se puede construir cualquier lista,
por ejemplo, 1: (2: (3: [ ])) forma la lista [1, 2, 3]. El operador : asocia por la derecha, de esta forma, la lista
anterior se genera tambin de la siguiente forma: 1: 2: 3: [ ].
c) Intervalo numrico. La tercera foma de construir listas es aplicando un intervalo numrico. Es decir, la lista
se forma con todos los elementos includos en el intervalo indicado por su valor inicial, su valor final y dos
puntos consecutivos, por ejemplo:
? [1 .. 4]
->
Instruccin
[1, 2, 3, 4]
->
Resultado
? [2.5 .. 6.0]
->
Instruccin
->
Resultado
El valor para la expresin [x .. y] se calcula evaluando la funcin enumFromTo x y. Esta funcin se define
como se indica a continuacin:
enumFromTo x y | y < x = [ ]
| otherwise = x : enumFromTo (x + 1) y
De esta forma si y es menor que x el resultado es una lista vaca; de otra manera x es el primer elemento y el
siguiente elemento es el primero (x) ms una unidad.
Funciones utilizadas en listas. Las funciones que se emplean en las listas generalmente se definen a travs
de patrones. Es decir, la funcin se define con la lista vaca y la lista de la forma x:xs. Algunas funciones ya se
han visto en las secciones anteriores y en los ejemplos mostrados, aqu se van a indicar la operacin de tales
funciones y otras ms.
Comparacin y ordenamiento. Dos listas son iguales si contienen los mismos elementos y en el mismo
orden. Esta es la definicin de la funcin eq con la cul se prueba la igualdad entre listas. La definicin de la
misma es como sigue:
[]
eq
[]
True
[]
eq
(y:ys)
False
(x:xs)
eq
[]
False
(x:xs)
eq
(y:ys)
x == y &&
xs
eq ys
57
En esta definicin, tanto el primero como el segundo parmetro, pueden o no estar vacos. Asimismo, en la
definicin se contemplan las posibles combinaciones que puede tener la igualdad. Las definiciones de las
funciones siguientes pueden consultarse en el archivo prelude.standard (ver Anexo A). La funcin sort se
utiliza para ordenar listas en orden ascendente, si se desea el ordenamiento en orden descendente primero se
aplica sort y posteriormente reverse. Ejemplo de estas funciones se muestra en las siguientes lneas:
? sort [1, 1, 5, 3, 8, 4, 2]
[1, 1, 2, 3, 4, 5, 8]
? reverse (sort [3, 1, 2])
[3, 2, 1]
Concatenacin. El operador ++ aplicado en listas se emplea para unir dos listas con elementos del mismo
tipo. A esta operacin se le conoce como concatenacin, por ejemplo: [2, 4, 5] ++ [6, 8] genera la lista [2, 4, 5,
6, 8]. Asimismo se utiliza la funcin concat para concatenar listas de listas, por ejemplo:
? concat [ [1, 2, 3], [ ], [4, 5, 6, 7]]
[1, 2, 3, 4, 5, 6, 7]
Seleccionar partes de una lista. Una estructura tipo lista generalmente se compone de dos elementos, una
cola y una cabeza (ver Figura 2.8). Gofer utiliza dos funciones para seleccionar cada uno de estos elementos:
head [x : xs] = x, y tail [x : xs] = xs.
Figura 2.8. Elementos bsicos de una lista, cabeza (head), cola (tail), elementos iniciales (init), ltimo
elemento (last).
Asimismo, la funcin last es una funcin recursiva que selecciona el ltimo elemento de una lista. De forma
similar la funcin init selecciona todos los elementos de una lista excepto el ltimo (ver Figura 2.8).
En la Figura 2.9 se pueden observar de manera ilustrativa la operacin de tres funciones relacionadas con la
seleccin de partes de una lista. La funcin take n se usa con un parmetro que indica los elementos iniciales
de una lista a seleccionar, por ejemplo: take 2 [1, 2, 3, 4, 5, 6] regresa como resultado la lista [1, 2]. A su vez
la funcin drop n indica el nmero de elementos que se van a eliminar de la lista, por ejemplo, drop 2 [1, 2, 3,
4, 5] regresa como resultado la lista [3, 4, 5]. El operador !! n indica la seleccin de un elemento de la lista en
la posicin n, por ejemplo, !!3 [1, 2, 3, 4, 5] da como resultado el elemento 4, hay que recordar que los
elementos de una lista inician en la posicin 0.
58
Si en el uso de take n, la lista es ms pequea que los elementos indicados por n, se selecciona la lista
completa.
Para el caso de drop se elimina la lista completa dando como resultado la lista vaca. La aplicacin del
operador !! inicia desde el primer elemento de la lista, por lo tanto para listas muy grandes se destina
demasiado tiempo, en este caso es preferible el uso de las funciones map o foldr.
Figura 2.9. take 3 devuelve los primeros 3 elementos de la lista, drop 3 elimina los primeros 3 elementos de la
lista, !!3 selecciona el elemento en la posicin 3 de la lista.
Propiedades de una lista. Las listas tienen algunas propiedades que ofrecen informacin importante sobre
ellas, entre estas estn: la longitud de la lista, saber si un elemento forma parte de la lista y saber si un
elemento no forma parte de la lista. Las funciones relacionadas con esta informacin se muestran a
continuacin.
Para determinar la longitud de una lista se emplea la funcin length. Asimismo se utiliza la funcin elem para
saber si un elemento se encuentra en una lista y notElem indica si un elemento no est en una lista. Un
ejemplo del uso de estas funciones se muestra en las siguientes lneas:
? length [1, 2, 3, 4, 5, 6, 7, 8, 9]
->
9
? elem 2 [1, 2, 3, 4, 5, 6, 7, 8, 9]
Instruccin
->
->
Resultado
Instruccin
True
->
Resultado
? notElem 2 [1, 2, 3, 4, 5, 6, 7, 8, 9]
->
Instruccin
False
->
Resultado
Como se puede observar, el resultado de las funciones elem y notElem es un valor Bool indicando si el
elemento especificado se encuentra, o no, en la lista.
Funciones de orden superior. En la programacin funcional, a las funciones se les puede dar mayor
flexibilidad si utilizan funciones como parmetros o argumentos. Cuando esta situacin se presenta se le
conoce como funciones de orden superior, algunas de ellas son las funciones map, filter y fold las cuales se
aplican sobre todos los elementos de la lista. La funcin map aplica su parmetro funcin a cada elemeneto
que forma la lista especificada. Por su parte, la funcin filter elimina los elementos de una lista que no
cumplan con la condicin indicada (representada por una funcin booleana). La funcin fold inserta un
operador entre todos los elementos de una lista empezando a la derecha con un valor dado. Las lneas
siguientes muestran un ejemplo del uso de estas tres funciones:
59
->
Instruccin
->
Resultado
->
Instruccin
[1, 3, 5, 7, 9]
? fold (+) 1 xs = [1, 2, 3]
->
->
(1 + (2 + (3 + 1) ) )
Resultado
Instruccin
->
Resultado
Existen ms funciones que se aplican a las listas, sin embargo, se deja al lector que se instruya acerca de su
uso cuando sea requerido.
A continuacin se van a mostrar algunos programas relacionados con las aplicaciones de las listas en la
solucin de problemas.
Ejercicio 6. En sistemas de numeracin en base k (con k un nmero entero y k > 1), un nmero puede ser
representado por una lista de nmeros, todos menores que k y mayores o iguales a cero. En el sistema de
numeracin en base 10 (k = 10), la lista [9, 8, 4] representa el numero 984 (
sistema de numeracin en base tres (k = 3), la lista [2, 0, 1] representa el numero 19 (
Escriba una funcin listaNum, que, dados un nmero k y una lista ms de nmeros m
). En el
).
,
devuelva el nmero representado por la lista en el sistema de numeracin en base k. Defina la funcin en
base a foldl.
La funcin que resuelve el problema indicado se muestra en las siguientes lneas, en la Figura 2.10 se ilustra
la ejecucin de la misma.
listaNum k xs= let f (acc, 0) x =(acc + x, 0)
f (acc, i) x =(acc + (x * k ^ i), I - 1)
in fst $ foldl f (0, (length xs) - 1) xs
Ejercicio 7. Defina la funcin esta que controle si existe cierto elemento en una lista de elementos. Defina la
funcin de las siguientes maneras:
1.
Tome todos los elementos iguales al elemento buscado y coloque estos en una lista. Compruebe
despus si esta lista est vaca o no.
2.
Haga una nueva lista en la que todos los elementos iguales al elemento buscado sean reemplazados
por 1 y los otros elementos por 0. Sume los elementos de la lista resultante y compruebe si el
resultado es igual a 0 o no.
60
3.
Compruebe para cada elemento si es igual al elemento buscado o no. Despus compruebe si uno de
estos tests devolvi True.
En la Figura 2.11 se observa la definicin de esta funcin en el programa Gofer, en la Figura 2.12, se ilustra la
ejecucin de la funcin solicitada.
Figura 2.11. Definicin de la funcin esta en Gofer, observe el empleo de las funciones map y filter.
61
Figura 2.12. Ilustracin del uso de la funcin esta, se observa que el resultado de esta funcin es un valor
booleano indicando si elemento se encuentra o no en la lista.
Ejercicio 8. Escriba una funcion recursiva sc (sublistas crecientes), de tal forma que, dada una lista, devuelva
una lista de listas que existan en todas las sublistas no decrecientes de la lista. Escriba tambien una definicion
de sc usando foldr. Por ejemplo, sc [6, 1, 4, 8] = [[ ], [6], [1], [1, 4], [4], [1, 4,8 ], [4, 8], [1, 8], [6, 8], [8]].
(a)
62
(b)
Figura 2.13. (a) Definicin de la funcin sc, (b) ejemplo de su aplicacin en Gofer.
En la Figura 2.13, se aprecia tanto la definicin de la funcin que realiza la tarea solicitada como un ejemplo
de su ejecucin.
Ejercicio 9. Escriba una funcin dividir, que, dados una lista no decreciente xs y un elemento x, devuelva una
tupla de dos listas (ys, zs), con xs = ys ++ zs, donde todos los elementos de ys sean menores o iguales que x,
y todos los elementos de zs sean mayores que x. Escriba la funcion insertar, que, dados una lista no
decreciente ys y un elemento y, devuelva una lista no decreciente igual a ys ms el elemento y insertado en el
lugar correspondiente.
(a)
63
(b)
Figura 2.14. En (a) se muestra la estructura de las funciones dividir e insertar, en (b) se ilustra un ejemplo de
su ejecucin.
En la Figura 2.14, se muestra la definicin de las funciones dividir e insertar que realizan la tarea descrita.
Asimismo, se muestra en la Figra 2.14 (b) un ejemplo de la ejecucin de la funcin solicitada.
Ejercicio 10. Escriba la funcin recursiva esSubLista, la cual, dadas dos listas, devuelva True si la segunda
lista es una sublista de la primera, y False si no. Decimos que ys es una sublista de la lista xs si existe una
lista creciente de nmeros positivos is, con ys = [xs !!i| i < -is]. Ejemplos:
? esSubLista "muchsimo" "uso"
True
? esSubLista [1,4,2,5,7] [2,1]
False
(a)
64
(b)
Figura 2.15. (a) Definicin de la funcin esSublista, (b) ejemplo de ejecucin de la funcin esSublista.
En la Figura 2.15 (a) se ilustra la estructura de la funcin esSublista la cul realiza la tarea solicitada en el
Ejercicio 10, en la figura 2.15 (b) se muestra un ejemplo de la ejecucin de la funcin realizada en Haskell.
A continuacin se va a mostrar el menejo de una estructura muy utilizada en la solucin de una amplia
variedad de problemas, me refiero a los rboles. En este caso, solamente se va a mostrar el manejo de
rboles binarios.
2.5 rboles
Las listas y las tuplas son dos formas bsicas de estructuras de datos. Sin embargo, estas estructuras no
siempre resultan suficientes para representar la informacin que se requiere en ciertos tipos de problemas,
bsqueda por ejemplo. En este caso, se requiere el empleo de un nuevo tipo de datos: el rbol (ver Figura
2.16). Una lista es una estructura lineal (ver Figura 2.16); cada vez que se aumenta un elemento, la lista
resultante es ms larga. Algunas veces, no se necesita una estructura lineal, sino una estructura de rbol.
Existen diferentes estructuras de rbol, sin embargo, una de las ms utilizadas en la solucin de una amplia
variedad de problemas es el rbol binario, tal como se ilustra en la figura 2.16. El rbol binario tiene la
caracterstica de que cada nodo se divide en dos: un nodo izquierdo y un nodo derecho, esta divisin sigue
hasta llegar a las hojas del rbol.
65
En la Figura 2.16 se muestra una lista y un rbol tipo binario. La estructura de la lista usa el operador : con
dos parmetros, o con la lista vaca ([ ]). En este caso, la estructura tipo rbol no se construye con
operadores, sino a travs de funciones (funciones constructoras): Nodo (con tres parmetros) u Hoja (sin
parmetros). Las funciones constructoras del
constructoras empiezan por una letra mayscula para distinguirlas de funciones normales. Si se escribe una
funcin constructora como operador, el nombre debe empezar con el smbolo .. (dos puntos). La funcin
constructora (:) de listas es un ejemplo de un operador como funcin constructora. El constructor [ ] es la nica
excepcin. Con una definicin de datos se definen las funciones constructoras que se pueden usar con un
nuevo tipo. En esta definicin de datos estn tambin los tipos de los parmetros de las funciones
constructoras. Por ejemplo, la definicin de datos para rboles como estn descritos en la Figura 2.16 es:
data Arbol a = Nodo a (Arbol a) (Arbol a)
| Hoja
(a)
(b)
Figura 2. 16. (a) Ilustracin de una estructura de datos tipo lista, (b) estructura de datos tipo rbol.
La definicin anterior se puede leer como: Un rbol con elementos del tipo a ( rbol sobre a) puede ser
construido de dos formas:
(1) Aplicando la funcin Nodo a tres parmetros (uno del tipo a y dos del tipo rbol sobre a), o
66
tomarse decisiones en dos sentidos en cada punto de un proceso. Suponga que se desea encontrar todos los
duplicados de una lista de nmeros. Considrese el siguiente algoritmo:
1. El primer nmero de la lista se coloca en un nodo que se ha establecido como la raz de un rbol binario con
subrboles izquierdo y derecho vacos.
2. Cada nmero sucesivo en la lista se compara con el nmero en la raz, aqu se tienen 3 casos:
a)
3. Si alguno de los subrboles esta vaco, el nmero no es un duplicado y se coloca en un nodo nuevo en dicha
posicin del rbol.
4. Si el subrbol no est vaco, se compara el nmero con la raz del subrbol y se repite todo el proceso con el
subrbol.
La bsqueda de un elemento en una lista es computacionalmente costosa cuando se tiene una gran cantidad
de datos, ya que debe buscarse el elemento en toda la lista. Una mejora a este proceso es ordenar primero
los elementos de la lista. Sin embargo, mantener los datos ordenados en una estructura tipo rbol hace que el
proceso de bsqueda sea ms eficiente. Buscar un valor en un rbol de bsqueda es muy simple. Si el valor
buscado es igual que el valor en el nodo actual, el valor existe y se ha encontrado. Si el valor buscado es
menor que el valor en el nodo actual, entonces se tiene que buscar en el subrbol izquierdo. Sin embargo, si
el valor buscado es mayor que el valor en el nodo actual, se tiene que buscar en el subrbol de la derecha. La
funcin elemArbol que busca un elemento en un rbol se construye como sigue:
elemArbol :: Ord
a => a ->
Bool
elemAbol e Hoja
elemArbol e (Nodo x izq der)
=
|
False
e == x = True
67
Si el rbol est construido equilibradamente (rbol balanceado), en cada paso de la bsqueda se divide por
la mitad el nmero de elementos que se deben registrar. De este modo, se encuentra rpidamente el
elemento buscado o (si no existe), una hoja Hoja: un conjunto de mil elementos se debe dividir por la mitad
solamente 10 veces, y un conjunto de un milln de elementos solamente veinte veces. Comprese esto con el
promedio de medio milln de pasos que usa la funcin elem con un conjunto de un milln de elementos. En el
peor de los casos, buscar en un conjunto de n elementos requiere n pasos con elem, pero con elemArbol
solamente requiere log 2n pasos.
Es muy til usar rboles de bsqueda si se tiene una gran cantidad de datos entre los que se tiene que
buscar muchas veces.
Construccin de un rbol de bsqueda. La forma de un rbol de bsqueda puede determinarse de forma
manual para una coleccin de datos determinada. Sin embargo, es mucho trabajo que sencillamente no le
gusta a nadie y que se puede automatizar. Tal como la funcin insert que coloca un elemento en su posicin
correspondiente
::
listaArbol
Ordenamiento en rboles de bsqueda. Las funciones que se han mostrado y relacionadas con el manejo
de rboles pueden ser aplicadas en un nuevo algoritmo de ordenamiento. Sin embargo, se necesita una
funcin ms, una que se encargue de colocar los elementos de un rbol de bsqueda en una lista. Tal funcin
es la siguiente:
labels
::
Arbol
->
labels Hoja
[]
[a]
++ labels der
Contrariamente a insertArbol, esta funcin llama recursivamente al subrbol izquierdo y (despus) al subrbol
derecho. De ese modo, se recorre cada elemento en el
equivalente en la lista, el resultado es una lista ordenada (a condicin de que el parmetro sea un rbol de
bsqueda). Con las funciones sobre rboles de bsqueda; podemos escribir un algoritmo para ordenar una
lista. Primero, hacemos de la lista un rbol de bsqueda (con listaAarbol), y despus transformamos este
rbol en una lista ordenada (con labels) como se describe en las siguientes lneas:
Ordenar
::
Ord a =>
[a]
ordenar
labels . listaAarbol
->
[a]
68
Adems del caso mostrado, el cual es una de las aplicaciones ms utilizadas de un rbol binario, se tienen
las siguientes:
o
rboles GGM. Tipo de rbol que se utiliza en aplicaciones criptogrficas para generar un rbol de
nmeros pseudo-aleatorios.
rboles sintctico. ste rbol es construido por los compiladores y (implcitamente) calculadoras para
analizar expresiones.
Poli-rboles. Estructura de datos representada por mltiples rboles, frecuentemente utilizada para el
tratamiento eficiente de procesos, aspecto utilizado en los sistemas operativos. Asimismo, se aplica
esta estructura para manejar la calidad del servicio de los routers, as como en algoritmos de
planificacin aplicados en Robtica, Visin, etc.
Bases de datos. Aunque la mayora de las bases de datos utilizan alguna forma de rbol para
almacenar datos en el disco, las bases de datos que mantienen a todos (la mayora) de sus datos en
la memoria suele utilizar rboles binario para hacerlo.
69
(a)
(b)
Figura 2.17. En (a) se muestra la descripcin del programa que realiza la tarea indicada en el Ejercicio 11, (b)
pantalla que muestra la ejecucin del programa.
70
Ejercicios
2.1 En el mtodo de Newton, la prueba para determinar una aproximacin y suficientemente buena para la
raz cuadrada x de un nmero es en base a cumplir:
abs (y
2 x) < eps
a) reescriba la funcin sqrt para que calcule la raz cuadrada de un nmero en base a esta prueba.
Asimismo, otra prueba utilizada para detener el clculo es cuando los valores de dos aproximaciones
sucesivas y y y son muy cercanos.
b) reescriba nuevamente la funcin sqrt utilizando esta nueva prueba.
2.2. Defina la funcin sumsqrs la cual toma como argumento tres nmeros y debe retornar la suma de los
cuadrados de los dos ms grandes.
2.3. Documente los programas descritos en los ejercicios 6 al 11 mostrados en la Seccin 2.4. y 2.5.
2.4. La funcin initree, usada en la sntesis del borrado de rboles de bsqueda binarios, es especificada por
la ecuacin:
71
Captulo 3
Evaluacin Perezosa
El proceso para evaluar (ejecutar, reducir) un programa escrito en un lenguaje de programacin, consiste en
tomar cada una de sus componentes (expresiones, funciones) e ir transformndolas hasta que no puedan
transformarse ms. La expresin resultante se denomina representacin cannica y es mostrada al usuario.
Existen valores que no tienen representacin cannica (por ejemplo, las funciones) o que tienen una
representacin cannica infinita (por ejemplo, el nmero pi ).
En los lenguajes de programacin existen diferentes estrategias para evaluar las expresiones o funciones que
forman el cuerpo del programa. En los lenguajes imperativos, el orden de ejecucin de las expresiones se
define de manera implcita por la estructura del cdigo fuente. En este paradigma, la forma de evaluacin ms
utilizada es la conocida como estrategia de evaluacin impaciente [Bubenik, 1989]. En este tipo de evaluacin,
una expresin se evala tan pronto como se asigna a una variable. Estea estrategia de evaluacin tiene
ventajas y desventajas. Una ventaja radica en que elimina la necesidad de realizar una planificacin y
seguimiento en la evaluacin de expresiones. Una desventaja en este tipo de estrategia, es que realiza la
evaluacin de expresiones aunque no se requieran de manera inmediata, o bien retrasan la evaluacin de
expresiones que son requeridas. Sin embargo, los modernos compiladores se enfocan a la optimizacin de
sus recursos y reducen en gran medida las desventajas tradicionales en las estrategias de evaluacin.
Por otro lado, existen las estrategias de evaluacin aplicadas en el paradigma declarativo. Bajo este esquema,
una expresin es evaluada hasta que sea requerida o solicitada, y no cada vez que aparezca en el programa.
La estrategia de evaluacin generalmente utilizada por estos lenguajes es conocida como: Evaluacin
perezosa [Wadsworth, 1971]. En la evaluacin perezosa, tambin conocida como evaluacin por demanda, la
evaluacin de una expresin se retarda hasta que su valor es requerido, esto evita la repeticin en la
evaluacin de una misma expresin. Esta caracterstica en la evaluacin permite reducir el tiempo de
ejecucin de ciertas funciones por un factor exponencial en relacin con su contraparte aplicada en el
paradigma imperativo.
72
El contenido de este captulo se relaciona con este tipo de evaluacin, la cual como mencion es una de las
caractersticas de los lenguajes de programacin declarativos: funcionales y lgicos.
La estrategia de evaluacin perezosa, tambin conocida como evaluacin por demanda, fue introducida por
primera vez en el Clculo Lambda por Christopher Wadsworth (1971), y en la teora de los lenguajes de
programacin por Henderson (1976) y Friedman (1976). En esta tcnica, la evaluacin de una expresin se
realiza hasta que su valor sea requerido, este procedimiento evita repetir la evaluacin en caso de ser
necesaria en posteriores ocasiones. Esto permite, entre otras cosas, reducir el tiempo de ejecucin de ciertas
funciones de forma exponencial, comparado con otros tipos de evaluacin.
Algunas ventajas que ofrece la evaluacin perezosa son las siguientes:
La evaluacin perezosa reduce tambin el consumo de memoria de una aplicacin, ya que los valores se
crean solo cuando se necesitan. Sin embargo, es difcil de combinar con las operaciones tpicas de
programacin imperativa, tales como el manejo de excepciones o las operaciones de entrada/salida, ya que
el orden de las operaciones puede quedar indeterminado. Adems, la evaluacin perezosa puede conducir a
fragmentar la memoria ocasionada por problemas en su manejo, por ejemplo, la simple expresin en Haskell:
foldl (+) 0 [1..10^8] , necesita gigabytes de memoria para su ejecucin.
Aplicaciones. Esta estrategia de evaluacin se utiliza generalmente en los lenguajes de programacin
funcional, tales como Hakell y Gofer. Como ya mencion con anterioridad, en este tipo de evaluacin, una
expresin se evala hasta que su valor se necesita. Por ejemplo, en la expresin
la linea del
travs de una referencia a tal variable en una expresin posterior, tal como
A continuacin voy a mostrar como esta estrategia de evaluacin trabaja, tomando como base el lenguaje de
programacin Haskell y dos de sus principales recursos: tiempo de ejecucin y espacio de memoria utilizado
[Heinrich, 2016].
73
La reduccin de expresiones. Los programas escritos en Haskell son ejecutados mediante la evaluacin de
expresiones. La principal idea detrs del proceso de evalucin es la funcin. Por ejemplo, dada la funcin
indicada en la siguiente lnea:
La evaluacin procede considerando las funciones + y *, como se describe en las siguientes lneas:
Se puede notar en este caso que el trmino (1 + 2) se evalu dos veces. Sin embargo, sabemos que estas
dos expresiones, son de hecho la misma, ya que corresponden al mismo argumento de la funcin. Asimismo,
se observa que el proceso de evaluacin se basa en las reglas de prioridad en los operadores (el parntesis
tiene mxima prioridad y las operaciones aritmticas de + y * se evalan de izquierda a derecha).
Con el fin de evitar la duplicacin de trminos se puede aplicar un mtodo conocido como reduccin de
grafo. Cualquier expresin puede repesentarse como un grafo, para el caso de la expresin anteriormente
descrita, su representacin es como se muestra en la Figura 3.1.
74
En este mtodo, el nombre de la funcin se escribe en el cuadro blanco, mientras que los cuadros grises
apuntan a los argumentos de la misma. Este mtodo muestra la manera en que los compiladores representan
expresiones con apuntadores a memoria. Cualquier funcin definida por el programador corresponde a una
regla de reduccin. En este caso, la funcin
El crculo etiquetado como x es un espacio de almacenamiento para el subgrafo. Notar como ambos
argumentos de la funcin de multiplicacin apuntan al mismo subgrafo. Compartir un subgrafo, como se ilustra
en la Figura 3.2, es la clave para evitar la duplicacin de clculos. Cualquier subgrafo que corresponda a una
regla se le conoce como una expresin reducible, o redex. Siempre que se tenga un redex, es posible
reducirlo, es decir, actualizarlo de acuerdo a una regla. En la expresin de ejemplo (ver Figura 3.1), se tienen
dos redex: 1) se puede reducir la funcin cuadrado, o 2) se puede reducir la adicin (+). Si primero reducimos
el redex de la funcin cuadrado y despus continuamos con el redex de la adicin, obtenemos una secuencia
como la mostrada en la Figura 3.3.
En el penltimo grafo se observa el redex que corresponde a la regla para la multiplicacin, al realizar la
reduccin se obtendr el valor final de 9.
Forma Normal y Forma Normal Dbil. Se dice que una expresin est en su forma normal cuando ya no es
posible realizar ms reducciones. Este es el resultado final de un evaluacin. En la Figura 3.3, la forma normal
de la expresin inicial es el nmero 9 mostrado en el grafo final. Sin embargo, algunos constructores tales
como los constructores aplicados en listas (:, [ ]) tambin dan lugar a formas normales. Estos elementos lucen
igual a funciones, pero, ya que fueron generados por una declaracin de datos y al no tener el lado derecho,
no existe una regla de reduccin para ellos. Por ejemplo, el grafo mostrado en la Figura 3.4 ya se encuentra
en su forma normal y representa la lista 1: 2: 3: [ ].
Asimismo, existen dos requerimiento adicionales para que un grafo se considere est en su forma normal: 1)
debe ser finito, y 2) no tener ciclos. Algunas veces se puede observar que ocurre el caso contrario en un
grafo, pero esto se debe por la recursividad. Por ejemplo, la expresin: unos = 1 : unos, se representa por el
grafo cclico mostrado en la Figura 3.5. Este grafo no tiene redex y tampoco est en su forma normal debido a
que la presencia del constructor : causa un ciclo donde la cola de la lista, apunta de manera recursiva al
principio de la misma, resultando en una lista infinita.
75
Figura 3.4. ilustracin de la representacin en Forma Normal para la expresin tipo lista 1: 2: 3: [ ].
76
En Haskell, generalmente no se evala todo el proceso que conlleva a la forma normal, en su lugar, muy
seguido el proceso se detiene al alcanzar la Forma Normal Dbil (WHNF, por sus siglas en ingls). Se dice
que un grafo es un WHNF si el nodo que se encuentra en el tope superior es un constructor (ver Figura 3.6).
Cualquier expresin que no pueda ser representada como un grafo WHNF es una expresin que no se puede
evaluar o procesar. Asimismo, una expresin que inicia con un constructor es un WHNF, sin embargo, los
argumentos de este constructor pueden ser expresiones que no se puedan evaluar. Un ejemplo interesante de
un grafo WHNF es la expresin mostrada en la Figura 3.5, en la cual el nodo que se encuentra en el tope es
un constructor. En Haskell es posible representar y manejar listas infinitas, esta es una manera para
desarrollar cdigo modular.
Orden de evaluacin, Evaluacin Perezosa. Cuando en una expresin se pueden aplicar varias reglas de
reduccin, cul debe ser el orden de reduccin?. La mayora de los lenguajes funcionales aplican la
estrategia de evaluacin perezosa, la cual siempre trata de reducir en primera instancia la funcin que se
encuentra en el tope del grafo. Para esto, algunos argumentos de la funcin deben ser evaluados, pero esto
se har hasta que sea necesario. Por ejemplo, considere una funcin definida por varias ecuaciones que
utilizan correspondencia de patrones. Para cada ecuacin, los argumentos sern evaluados de izquierda a
derecha hastaque los nodos en el tope sean constructores que correspondan al patrn. Si el patrn resulta ser
una simple variable, entonces el argumento no es evaluado; por otro lado, si el patrn es un constructor, esto
indica que se debe evaluar un WHNF. Con el fin de clarificar lo anterior, considere la funcin (&&) la cul se
encarga de implementar la lgica AND. La definicin de esta funcin se describe en las siguientes lneas:
(&&) :: Bool -> Bool -> Bool
True && x = x
False && x = False
Esta definicin introduce dos reglas de reduccin dependiendo si el primer argumento es True o False (ver
Figura 3.7).
77
Figura 3.8. (a) Representacin y (b) Reduccin de la expresin: ('H' == 'i') && ('a' == 'm').
En esta expresin, ambos argumentos son reducibles. La primer ecuacin (H == i) de la funcin (&&) verifica
si el primer argumento empata con el constructor True. Por lo tanto, la evaluacin perezosa realizar la
78
reduccin del primer argumento (ver Figura 3.8 (b)). El segundo argumento ('a' == 'm') no se evala, ya que la
funcin que se encuentra en el tope del grafo ha sido reducida. La estrategia de evaluacin perezosa siempre
trata de reducir el nodo que se encuentra en el tope, as que al hacer esto aplicando la regla de reduccin
para la funcin (&&), y obtener el resultado False esta expresin se encuentra en su forma normal, y ah
termina el proceso de reduccin. En la reduccin de (&&) no se requiere evaluar el segundo argumento, con lo
que se ahorra tiempo de procesamiento. En algunos lenguajes imperativos se aplica una tcnica similar
(evaluacin de corto-circuito), sin embargo, este mtodo se realiza generalmente en el lenguaje y solo se
aplica para operadores lgicos. En Haskell, este proceso se aplica a todas las funciones y es consecuencia de
la evaluacin perezosa.
De forma general, se puede decir que, la forma normal obtenida por la evaluacin perezosa es igual a la
obtenida por la evaluacin impaciente utilizada en los lenguajes imperativos, la nica diferencia es que la
evaluacin perezosa utiliza menos pasos en la reduccin, asi como la ventaja de manejar grafos con ciclos
infinitos, algo que el mtodo de evaluacin impaciente no permite.
Por ejemplo, la representacin textual para indicar la secuencia de reduccin para la expresin cuadrado (1 +
2) (ver Figura 3.3), se muestra en las siguientes lneas:
cuadrado (1+2)
=> let
= (1+2) in
=> let
= 3 in
=> 9
La palabra let en la sintxis nos permite compartir la subexpresin x = (1+2). Se puede observar nuevamente
en este ejemplo, como la estrategia de evaluacin perezosa, reduce primero la funcin cuadrado y
posteriormente evala el argumento x.
Un segundo ejemplo relacionado con la reduccin en representacin textual se muestra enseguida. En este
caso, la expresin a reducir es la mostrada en la Figura 3.8, y se relaciona con la funcin lgica AND:
79
Tiempo. El tiempo utilizado por un programa para resolver un algoritmo depende de muchos factores, tales
como,
el hardware utilizado, lenguaje de programacin, tcnica de programacin, entre otras. sin embargo,
se ha vuelto un estndar, considerar los ciclos de reloj o pasos de programacin que un programa utiliza en la
solucin de un algoritmo como una medida de su eficiencia. Estas medidas vienen identificadas bajo el
nombre de complejidad computacional. Los lenguajes que utilizan la estrategia de evaluacin perezosa,
sostienen que emplean menos pasos en la evaluacin de expresiones que los utilizados por la estrategia de
evaluacin insistente o ansiosa, empleada por la mayora de los lenguajes imperativos. Debido a que esta
evaluacin no incurre en sobrecarga administrativa, algunos expertos recomiendan el uso de esta estrategia
en aplicaciones de alto nivel tales como, el procesamiento de imgenes, visin computacional, y simulacin,
entre otras. El aspecto de cdigo simple y modular es una caracterstica de la evaluacin perezosa.
Espacio. Con el espacio utilizado, la situacin para la evaluacin perezosa no luce muy bien. En este caso el
problema estriba en que la memoria utilizada por una expresin no evaluada difiere significativamente de la
memoria utilizada por una expresin en su forma normal. Es decir, la memoria usada por una expresin es
directamente proporcional al nmero de nodos que contiene un grafo. Por ejemplo, la expresin ((((0 + 1) + 2)
+ 3) + 4) requiere mucho ms memoria de almacenamiento que su forma normal. Por otro lado, la expresin
enumFromTo 1 1000, la cual tiene la sintxis [1 .. 1000]. Esta funcin se puede representar con solamente
tres nodos y por consiguiente requiere mucho menos memoria que su representacin en forma normal, la cul
podra ser la lista 1:2:3:,,:1000:[ ] que contiene por lo menos mil nodos. Para paliar con esta situacin, la
evaluacin perezosa toma control del proceso y se asegura que la expresin sea evaluada lo ms pronto
posible. Para este propsito, Haskell provee el uso de funciones tipo seq definidas como, seq :: a -> b -> b.
Como la definicin de tipo sugiere, esta funcin retorna su segundo argumento comportndose de forma
parecida a una funcin constante. Sin embargo, al evaluar la expresin seq x y, primero se reducir x a una
forma WHNF y posteriormente se continua con la evaluacin de y. En contraste, la evaluacin de la expresin
const y x dejar el argumento x sin tocar y contina con la evaluacin de y.
Enseguida se mostrar un ejemplo del uso de la funcin seq y que cualquier programador en Haskell debera
conocer: la funcin foldl. Para este ejemplo se va a realizar la tarea de sumar todos los nmeros del 1 al 100.
Se va a expresar lo anterior a travs de un parmetro acumulador, por ejemplo, left fold, tal como se muestra
en la siguiente lnea:
80
En el archivo prelude (ver Anexo A), se puede observar la definicin de la funcin foldl como sigue:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f a [ ]
= a
=a
81
= True
82
puede implementarse con la funcin zipWith. Cabe mencionar que la funcin zipWith compara dos listas
empleando una funcin binaria:
genrica, es decir, su implementacin no tiene conocimiento acerca de las funciones and y (&&). Las tres
funciones son independientes y modulares, y sin embargo, gracias a la evaluacin perezosa, pueden trabajar
unidas y generar un resultado eficiente cuando se combinan. La clave de este resultado, es que la evaluacin
perezosa evala solamente lo que es necesario, no ms. A este procedimiento tambin se le conoce como
evaluacin por demanda.
Continuando con la modularidad del cdigo, mostrar una de las estructuras de datos ms sorprendentes y
que es posible gracias a la evaluacin perezosa, una que todo programador de Haskell debe conocer: listas
infinitas. Cmo se puede almacenar una lista infinita en la memoria de una computadora?, bueno,
nuevamente el truco consiste en no evaluar todo a la vez. Se va a mostrar un ejemplo muy simple: la lista
infinita que consiste en todos los nmeros naturales empezando con 0: [0..]. Al teclear la lista anterior en un
intrprete de Haskell, se empezar a visualizar en la pantalla de la computadora la lista [0, 1, 2, y no parar
hasta que sea interrumpida (ver Figura 3.9); despus de todo es una lista infinita. En el manejo de listas
infinitas resulta ms razonable mostrar en la pantalla solamente parte de esta lista, por ejemplo, la expresin
head [0..], retornar solamente el elemento inicial de la lista, en este caso: 0. Esto es posible debido a que la
expresin head [0..] aplica de manera sintctica la funcin enumFrom 0, la cul se define como:
83
Como se puede observar, esta funcin coloca el nmero inicial, argumento, al principio de la lista. Ya que
esta funcin no se detiene, la evaluacin perezosa reducir a esta expresin (head [0..]) como se indica en las
siguientes lneas:
Figura 3.9. Ilustracin de una lista infinita creada con la funcin iterate (acdosa xs ys = map fst $ iterate
(\(xs,ys)->(ys,xs++ys)) (xs, ys)).
head (enumFrom 0)
=> head (0 : enumFrom (0+1))
=> 0
El punto aqu es que el comando head retorna el elemento al inicio de la lista y la cola es descartada, no se
evala. Las listas infinitas pueden tener usos muy diversos, por ejemplo, uno de ellos consiste en la
decoracin de ventanas con los elementos de la lista que estn en cierta posicin. Esto se realiza con la
expresin zip [0..] xs. La manera en que la estrategia de evaluacin perezosa reduce a esta expresin se
muestra en las siguientes lneas:
zip [0..] "Oh"
= zip (enumFrom 0) "Oh"
=> zip (0 : enumFrom (0+1)) "Oh"
=> (0,'O') : zip (enumFrom (0+1)) "h"
=> (0,'O') : zip ((0+1) : enumFrom ((0+1)+1)) "h"
=> (0,'O') : ((0+1), 'h') : zip (enumFrom ((0+1)+1)) [ ]
84
Otro uso para las listas infinitas consiste en mostrar, guardar, todos los valores de inters de una gran
cantidad de datos. Un ejemplo tpico de este uso, consiste en el clculo de la raz cuadrada de un nmero
aplicando el mtodo de Newton Rapson. Esto es, dada una aproximacin x a la raz cuadrada de un nmero
y, una mejor aproximacin viene dada por la funcin better descrita como:
Se puede utilizar en este caso la funcin iterate para generar una lista infinita con los valores [
-> [ ]
iterate f x = x : iterate f (f x)
La idea aqu, consiste en aplicar de forma repetida la funcin better con el fin de generar una lista infinita de
una aproximacin cada vez ms precisa a la raz cudrada de y. Sin embargo, se tiene que seleccionar un solo
valor de esta lista. La siguiente funcin regresar el primer valor ms cercano a un umbral (eps)
predeterminado:
within eps (x : y : ys) =
if abs (x - y) <= eps then y else within eps (y : ys)
Colocando todo junto, es posible desarrollar una funcin que calcule la raz cuadrada como sigue:
squareRoot
Lo relevante
independiente una de la otra, este cdigo es ahora modular. Por ejemplo, se puede implementar un mtodo
diferente para obtener las aproximaciones modificando solamente la parte de generacin. O bien, es posible
implementar un mtodo de seleccin diferente reemplazando la funcin within por la siguiente:
85
Estructuras de control definidas por el usuario. Hasta ahora se ha mostrado como el mtodo de
evaluacin perezosa permite el reuso de funciones de bibliotecas en un contexto amplio. Sin embargo, existe
otro beneficio, el cual tiene que ver menos con reuso y ms con la expresividad, es decir, la habilidad para
implementar estructuras de control como funciones. La mayora de los lenguajes imperativos tienen un
conjunto limitado de estructuras de control, las cuales estn integradas en el lenguaje y el programador no
puede agregar nuevas estructuras. En Haskell el asunto es algo diferente, el programador no est limitado a
un conjunto restringido de estas estructuras, de hecho, no existe un conjunto predefinido de estructuras de
control, todo lo que requiere el programador puede ser expresado a travs de funciones. Un aspecto
interesante para la definicin de estructuras de control consiste en el manejo de funciones de orden alto,
aplicando combinadores como map, filter y foldr. Otro elemento que interviene en el desarrollo de estas
definiciones es la evaluacin perezosa. Como un ejemplo de lo mencionado, se va a mostrar como
reimplementar la estructura estndar if then else como una funcin:
y:ys)
myIf :: Bool -> a -> a -> a
myIf True x y = x
myIf False x y = y
Para evaluar la expresin myIf (x /= 0) (100 / x) 0, la divisin se va a realizar cuando el valor del argumento x
es distinto a cero. Gracias a la evaluacin perezosa, es posible expresar diferentes constructores como
funciones, donde otros lenguajes se apoyaran en el manejo de macros. Otro ejemplo de estructura de control
dfinida por el usuario podra ser la funcin forM, la cual puede ser aplicada como un ciclo for:
forM [1..100] (\i -> )
En esta funcin, el primer argumento es una lista de valores que son transferidos durante la ejecucin al
segundo argumento.
Otra vez, gracias a la evaluacin perezosa, la lista de nmeros no se evala en su totalidad en su forma
normal, sino que,
sus elementos son solicitados paso a paso siempre que la accin es ejecutada, tal como
sucede en un tradicional ciclo for. Los elementos de las listas no se sujetan solamente a nmeros, esta
funcin se aplica a cualquier estructura de tipos, por ejemplo, una lista de cadenas:
Gran parte de lo mostrado n esta seccin fue tomado del trabajo de Heinrich Apfelmus. The Incomplete Guide of Lazy
Evaluation in Haskell. Consultada en marzo del 2016 en, https://hackhands.com/lazy-evaluation-works-haskell/.
86
Considerando lo mencionado, la pregunta es: se debera escribir cdigo como el anterior en la prctica?, la
respuesta es S, como se ha observado en la prctica, muy seguido se escribe cdigo que posteriormente se
desecha, despus de todo, tal vez nunca se requiera calcular el mnimo, sino, la longitud de la lista. En lugar
de gastar tiempo para determinar la mejor manera de implementar la funcin para obtener el mnimo,
podemos ahorrar tiempo considerando lo primero que funciona, por ejemplo la combinacin de las funciones
head y sort. Y gracias a la evaluacin perezosa, el resultado funcionar razonablemente bien. Por supuesto,
una vez que el cdigo es estable, podemos regresar y tratar de optimizar la funcin que result deficiente. En
general, es mejor iniciar con un cdigo que funciona correctamente, aunque algo lento, que elegir un cdigo
rpido, pero con resultados incorrectos. Enseguida se va a mostrar un algoritmo de ordenamiento y ver como
la combinacin de head y sort toma un tiempo lineal. Se observar una variante del algoritmo Quicksort:
= []
En este algoritmo se asume que siempre se escoje un buen elemento pivote x, de tal forma que las listas ls y
rs son ms pequeas, y que el pivote tiene una longitud cercana a n/2, donde n es la longitud de la lista xs.
Para estimar el tiempo de ejecucin del algoritmo anterior, se considera el hecho de que la evaluacin
perezosa no toma ms pasos de reduccin que la evaluacin persistente. El objetivo consiste en evalur el
ordenamiento de forma recursiva, pero reduciendo solamente las acciones de ordenamiento del lado
izquierdo, dejando las del lado derecho sin evaluar, tal como se indica en las siguientes lneas:
87
y que la evaluacin perezosa procede con la evaluacin de los elementos iniciales (head) en las
concatenaciones (++) utilizando otro tiempo de O(log n) en los pasos de reduccin, debido a que este es el
nmero de concatenaciones aplicadas antes de alcanzar al elemento y ms pequeo. Este proceso resulta
significativamente ms rpido que el lineal, y todo lo que se necesita hacer es utilizar la combinacin head
(sort xs) la cual necesita solamente O(n) pasos de reduccin para generar al elemento ms pequeo.
En esta seccin se mostraron varios aspectos relacionados con la estrategia de evaluacin perezosa y cmo
este mecanismo de evaluacin nos permite construir cdigo reusable, modular y eficiente.
88
1. Introduction
Haskell program
expaa
expaca
expab
expac
expacb
expad
expada
expadb
expadc
1. Introduction
5
2
Figura 3.10. Modelo de evaluacin de expresiones en Haskell. (a) Programa Haskell, (b) Reduccion de
expresiones por reemplazo [Takenobu, 2004].
Una vez que ya se tienen valores en lugar de expresiones, estos realizan la operacin (matemtica, booleana,
lgica, relacional, etc.) indicada a travs de sus operadores y generan el resultado final (paso 4).
89
La tcnica de evaluacin perezosa, evaluacin retardada o evaluacin por demanda, utiliza varias tcnicas
durante el proceso de la evaluacin de expresiones en los cdigos que tienen implementado este mecanismo
de evaluacin. En la Figura 3.11 se muestran la mayora de tales tcnicas. En la seccin anterior, se mostr la
operacin de reduccin de expresiones utilizando la forma normal y WHNF utilizando para ello una
representacin de expresiones a travs de una estructura tipo rbol.
En esta Seccin se van a mostrar otras tcnicas aplicadas por esta estrategia de evaluacin, tales como la
tcnica de llamada por demanda y reduccin retardada de grafos, entre otros.
Llamada por demanda (call-by-need). Existen varios mtodos que emplean los diferentes lenguajes de
programacin durante la evaluacin de expresiones. Entre estos mtodos se encuentran los siguientes: i)
llamada por valor, ii) llamada por nombre, y iii) llamada por demanda. Los primeros dos mtodos son
generlmente utilizados por los lenguajes imperativos (C, C++, Java, etc.), mientras que el tercero es aplicado
por los lenguajes funcionales. Una breve descripcin de los primeros dos mtodos se ofrece a continuacin.
Llamada por valor. Este es un mtodo de evaluacin bastante comn en los lenguajes de programacin.
Muchos lenguajes de programacin, tanto imperativos como funcionales aplican esta estrategia de evaluacin.
La esencia de este mtodo se basa en la existencia de dos categoras de expresiones: trminos y valores. Los
90
valores son expresiones lambda as como otros trminos expresados en forma normal, por lo que no pueden
ser reducidos ms. Todos los argumentos de una funcin debern ser reducidos a su forma normal antes que
el proceso de reduccin pueda seguir. Para una sencilla expresin aritmtica (mostrada en las lneas
siguientes), el proceso de reduccin ser como sigue [Diel, 2015].
En este ejemplo, no pierda de vista como la subexpresin (2 + 2) es evaluada a su forma normal antes de
seguir con el proceso. Para un clculo lambda sencillo el intrprete basado en este mecanismo es muy
simple. Parte de la evaluacin en tiempo de ejecucin involucra la creacin de cierres, ambientes que
controlan el alcance de variables locales. En el diseo de un sencillo lenguaje basado en este mecanismo de
evaluacin, deben considerarse dos posibles valores en los cuales la reduccin puede converger, VInt y
VClosure. Ver las siguientes lneas de este lenguaje:
data Expr
= Var Int
| Lam Expr
| App Expr Expr
| Lit Int
| Prim PrimOp Expr Expr
deriving Show
data PrimOp = Add | Mul
deriving Show
data Value
= VInt Int
| VClosure Expr Env
deriving Show
type Env = [Value]
emptyEnv :: Env
emptyEnv = [ ]
La funcin evaluadora simplemente mapea el mbito local y un trmino al valor final. Cada vez que una
variable es referida se busca en el ambiente. Cada vez que un trmino lambda se introduce se extiende al
ambiente con el mbito local del cierre. Ver las siguientes lneas referidas a lo comentado:
eval :: Env -> Expr -> Value
eval env term = case term of
Var n -> env !! n
Lam a -> VClosure a env
App a b ->
91
Llamada por nombre. En este mecanismo de evaluacin, los trminos involucrados en una expresin lambda
simplemente son reemplazados, y el proceso de evaluacin procede de izquierda a derecha iniciando con el
trmino ms externo y reducindolo a un valor. En este caso, si una expresin no se usa no es evaluada.
Enseguida se va a mostrar la misma expresin utilizada como ejemplo en la estrategia de evaluacin anterior,
la cual tiene la misma forma normal, pero llega al resultado a travs de una secuencia de reducciones
diferente:
En este mtodo, un argumento se evala cada vez que la funcin es llamada y su valor es requerido por la
funcin. Por lo tanto, en este caso cada argumento se evala una o ms veces.
En el mtodo de llamada por demanda, un argumento es evaluado10 cuando su valor es requerido, en este
momento, su valor es almacenado y el programa atiende otras demandas. En este caso, cada argumento se
evala a lo ms una vez. Este mtodo es generalmente utilizado por la estrategia de evaluacin perezosa, y,
aunque es semnticamente equivalente al de evaluacin por nombre, resulta ms eficiente ya que no se
10
92
necesita
recalcular el valor de una expresin. Este mecanismo de evaluacin garantiza los siguientes
aspectos:
Haskell es un lenguaje que se caracteriza por aplicar este mtodo de evaluacin, lo cul es un aspecto clave
que los diseadores de lenguajes han usada para clasificarlo como un lenguaje funcional puro. Este mtodo
permite simular en el lenguaje el canal de entrada como una lista infinita, la cual es evaluada hasta que
suficientes datos han sido ledos. Con el fin de comprender el mecanismo utilizado por esta estrategia de
evaluacin, considere una sencilla funcin que maneja dos argumentos, y retorna la suma de los mismos.
fun add(a, b)
{
return a + b
}
Llamada a la funcin:
add(3 * 2, 4 / 2)
La evaluacin por necesidad realizar lo siguiente:
a=3*2
b=4/2
return a + b = 3 * 2 + 4 / 2
Por lo que, la funcin retornar la expresin 3 * 2 + 4 / 2. As que, como se puede observar, casi no hubo
gasto en recursos computacionales. La expresin ser computada solamente si su valor es necesitado. En
contraparte, la evaluacin de la funcin anterior, aplicando la llamada por nombre sera en este caso la
siguiente:
a=3*2=6
b=4/2=2
return a + b = 6 + 2 = 8
Como se puede observar, la funcin retornar el valor de 8.
Adems de realizar cdigo ms eficiente, otro de los aspectos que hacen interesante el uso de la estrategia
de evaluacin por demanda, es que permite el manejo eficiente de clculos con listas infinitas, por ejemplo:
fun takeFirstThree(list)
{
return [list[0], list[1], list[2]]
}
takeFirstThree ([0 ... infinity])
93
Unlenguaje que utilice el procedimiento de llamada por nombre tratar de crear una lista de 0 a infinito. Un
lenguaje funcional aplicando el mtodo de evaluacin de llamada por demanda simplemente
retornar
[0,1,2].
Un ejemplo ms que ilustra el uso de este mtodo se describe a continuacin. En este caso, se muestra un
segmento de programa escrito en Haskell, que permite el clculo del n-simo trmino de la secuencia de
Fibonacci. Se considera que la funcin fib que genera la secuencia, no tiene una condicin de terminacin.
94
4. Evaluation
Figura 3.12), esto es, con el fin de evaluar f2 e, primero debemos obtener e, debido a que en este caso el
forcing
(drive the evaluation of the thunk)
case ds of
x:xs -> f x xs
[]
-> False
forcing
(drive the evaluation of the thunk)
= True
f Nothing = False
4. Evaluation
(a)
Referencesthe
: [H1]
Ch.3, [D2], [D1], [H5], [W1]
Lazy patterns postpone
evaluation.
case expression
case ds of
x:xs -> f x xs
[]
-> False
function definition
f Just _
irrefutable patterns
[H1] 3.17
= True
f ~(Just _ ) = True
f Nothing = False
f ~(Nothing) = False
(b)
Figura 3.12 . Dos ejemplos
y (b)
dekinds
evaluacin
aplicando el mtodo de correspondencia de patrones.
There(a)
are
two
of pattern-matching.
11
11
De acuerdo con Bloss (1988), un thunk es la representacin durante el tiempo de ejecucin en el retardo
de la evaluacin de una expresin. En este sentido, las diferentes maneras de evaluar expresiones
manifiestan este elemento de formas diferentes, pero, de forma abstracta todas hacen lo mismo: retardan la
evaluacin de una expresin hasta que su valor es requerido, en este punto, evalan y almacenan este valor
hasta que sea requerido en prximas demandas sin recalcular su valor. Mientras que el retardo en la
95
de una correspondencia entre patrones, no ms all!. Por ejemplo, suponga que se desea evaluar f2
(safeHead [3^500, 49]). En este caso, f2 podra forzar la evaluacin de la llamada a (safeHead [3^500, 49]), la
cul a su vez, evaluara a Just (3^500), cabe notar que 3^500 no es evaluado, ya que la funcin safeHead no
requiere su valor, as como tampoco f2. La evaluacin posterior de 3^500 depende en como el resultado de f2
sea utilizado.
El eslogan a recordar con respecto a este mtodo es el siguiente: la correspondencia de patrones
dirige/maneja la evaluacin. Con el fin de reiterar los siguientes puntos importantes:
Se va a mostrar un segundo ejemplo con el fin de comprender mejor este mtodo. En este caso se va a
evaluar take 3 (repeat 7). Las definiciones de estas funciones (ver Anexo A), son las siguientes:
repeat :: a -> [a]
repeat x = x : repeat x
take :: Int -> [a] -> [a]
take n _ | n <= 0 = [ ]
take _ [ ]
= []
requiere la
evaluacin de (3-1). }
= 7 : take 2 (repeat 7)
{ 2 <= 0 es False, as que se debe expandir nuevamente repeat 7. }
= 7 : take 2 (7 : repeat 7)
{ Lo que sigue es similar a lo explicado. }
= 7 : 7 : take (2-1) (repeat 7)
96
= 7 : 7 : take 1 (repeat 7)
= 7 : 7 : take 1 (7 : repeat 7)
= 7 : 7 : 7 : take (1-1) (repeat 7)
= 7 : 7 : 7 : take 0 (repeat 7)
=7:7:7:[]
Cabe mencionar, que, aunque el proceso de evaluacin podra ser implementado exactamente igual a lo
descrito por las tcnicas anteriores, la mayora de los compiladores Haskell operan de una forma ms
sofisticada. Generalmente, aplican una tcnica llamada reduccin de grafos, en la cual, la expresin que es
evaluada se representa como un grafo, de tal forma que las diferentes partes de la expresin pueden
compartir apuntadores a la misma subexpresin (ver Figura 3.13 (a)). Esto permite asegurar que no haya
trabajo duplicado o innecesario. Por ejemplo, si f x = [x, x], en la evaluacin de f (1 + 1) solo se realizar
una suma, ya que la subexpresin 1 + 1 se comparte entre las ocurrencias de x.
A continuacin se va a describir lo relacionado con la implementacin de la tcnica de reduccin de grafos en
los compiladores Haskell.
Reduccin de grafos. El proceso de reduccin de grafos fue una aportacin de Wadsworth (1971) para el
clculo lambda (). Esta tcnica muy seguido es implementada en los lenguajes funcionales como una
alternativa para representar expresiones y reducirlas a su forma normal. Un aspecto relevante de esta tcnica
es que permite compartir informacin durante la evaluacin. Compartir la informacin en un grafo se visualiza
cuando mltiples vrtices apuntan al mismo nodo (ver Figura 3.13 y 3.15 (a)), en un programa mltiples
punteros apuntan al mismo nodo, esto evita la repeticin de clculos. Por ejemplo, al manejar una sustitucin
M[x := N] durante la reduccin, se reemplaza cada instancia de x en M a travs de un apuntador a N, ms que
una copia de N (ver Figura 3.13).
En la Figura 3.13 se muestran dos cosas importantes que un reductor de grafos (interpretador) debe hacer: i)
las variables son reemplazadas por punteros, esto permite tener un espacio comn compartido, ii) la raz del
grafo (redex), es sobreescrito (reemplazado) con el resultado de la reduccin (nodo raz en el grafo reducido
de la Figura 3.13), solamente el nodo raz puede sobreescribirse, siempre con una expresin equivalente a la
original. Si el nodo raz (nodo * en la Figura 3.13) tiene varios apuntadores dirigidos a l, la sobreescritura les
permite a estos nodos disponer del resultado de la reduccin (cmputo compartido).
En este sentido y con respecto a lo mencionado en los tres puntos anteriores, durante la evaluacin se
reemplaza la parte de un grafo que representa un redex, por ejemplo una funcin de aplicacin, con el
resultado de reducir este redex.
Esta tcnica, proporciona el esquema ms eficiente para compartir recursos computacionales: espacio y
clculos.
97
Figura 3.13. Reduccin de un grafo. Las lneas punteadas indican las posibles sustituciones [Daniel, 1989].
Continuando y ampliando el proceso de reduccin (evaluacin) de un grafo, se van a mencionar tres pasos
ms: 1) se debe tener una estrategia de bsqueda para encontrar los trminos redex12 (ver Anexo C), durante
este proceso, los nodos solamente se visitan una vez, 2) se debe contar con un procedimiento que permita
copiar los trminos compartidos antes de su sustitucin, en este paso se realiza una eliminacin peridica de
nodos de indireccin, este proceso funciona como un ordenamiento, y 3) se debe tener un proceso de
reduccin (sustitucin) aplicados a los trminos redex seleccionados [Daniel, 1989].
El grafo utilizado en esta estrategia de evaluacin se considera formado por cuatro tipos de nodos:
o
Nodo-hoja. Este nodo puede representar una constante o una variable. Las constantes pueden
incluir nombres de funciones definidas por el usuario.
Nodo-@. A este nodo se le conoce como nodo de aplicacin y representa bsicamente a una
funcin.
Nodo-. Este nodo representa a una bastraccin, es decir, permite representar a cualquier funcin
de los programas de usuarios.
Nodo-:. Nodo constructor, permite representar a tipos de datos construidos tales como: listas, tuplas,
datos definidos por el usuario.
Asimismo, cada nodo del grafo se representa en la memoria como una secuencia contigua de palabras
conocidas como celdas. A su vez, cada celda contiene una etiqueta y uno o ms campos, cada campo
almacena, ya sea un valor (nmero), o un apuntador (direccin) (ver Figura 3.14).
12
Un redex se puede definir como una funcin integrada que se aplica a un nmero preciso de argumentos, o
bien, como una abstraccin aplicada a uno o ms argumentos.
98
Figura 3.14. (a) Estructura bsica de un grafo, (b) Ejemplo de la expresin ((+ 11) 22) [Koopman, 1989].
La Figura 3.14 (a), muestra la representacin general de un nodo, en la cual se puede observar la existencia
de etiquetas tanto del lado izquierdo, como del lado derecho del nodo. Asimismo, en la Figura 3.14 (b) se
ilustra el grafo que representa la expresin ((+ 11) 22), los nmeros (0, 1) que aparecen a un lado de la
etiqueta tienen solo carcter informativo.
de un grafo en programas
funcionales y aplicando la estrategia perezosa. Algunos ejemplos grficos de la aplicacin de este algoritmo
se ilustran en las Figuras 3.16-18.
repeat
unwind the spine of the graph to the first non-@ node
args = stack depth
case node-type of
leaf-node: if it is a -name
then get the definition
else if it is a primitive and args arity
then reduce any strict arguments
apply the appropriate rule
overwrite the root of the redex
-node:
if args > 0
then copy the body
substitute the argument
overwrite the root of the redex
:-node:
(don't do anything)
end
until expression in WHNF
Figura 3.15. Algoritmo de reduccin de grafo [Davies, 2016].
Como ya mencion con anterioridad, un programa escrito en lenguaje funcional (secuencia de expresiones)
puede ser representado a travs de un grafo (ver Figura 3.16 (b)), el cual puede compartir subexpresiones
(ver Figura 3.16 (a)) que son evaluadas al mismo tiempo. Un grafo es reducido iniciando un proceso de
acceso aleatorio a la memoria, ms que utilizar una pila.
99
5. Implementation of evaluator
Graph
exp
exp
exp
exp
exp
exp
shared term
exp
exp
exp
5. Implementation of evaluator
of(a)mapping
a of
code
to
An expression can be Example
also represented
in the form
Graph.
a graph
So, graph is reduced using heap (random access memory) rather than stack.
main
function
References : [D3], [D2], [D5], [W5], [H4] Ch.12, [B8] Ch.3
code
build
head [1..]
function
thunk
code
code
build
head
[1..]
function
thunk
code
code
(b)
Figura 3.16. Ilustracin de grafos. (a) Grafo mostrando expresiones compartidas, (b) Representacin de un
cdigo Haskell en grafo.
100
5. Implementation of evaluator
which first ?
1+2
+
for call-by-value
for call-by-need
square (1+2)
(1+2) * (1+2)
3
3
square
+
1+2
*
1
5. Implementation of evaluator
exp
an expression
exp
exp
exp
...
exp
exp
exp
exp
exp
exp
exp
exp
El lenguaje Haskell utiliza el concepto de mquina abstracta (STG-machine) como evaluador de un grafo a
References : [D3], [D2], [D5], [W5], [H4] Ch.11, 12, [B8] Ch.3
101
travs deofcapas
5. Implementation
evaluator(ver
Figura 3.19).
Haskell code
Concept layer
take 5 [1..10]
:
Graph
(internal representation
of the expression)
STG-machine
STG Registers
Stack
Heap
R1, ...
Figura 3.19. Ilustracin del concepto de capas utilizada por la mquina abstracta
del: compilador
Hakell
References
[H5], [H6], [H7],
[D15]para
realizar la evaluacin de grafos.
En la capa de la mquina virtual se encuentran los siguientes elementos: a) Registros STG, los cuales son
utilizados principalmente por las llamadas y retornos de funciones, as como el manejo de ciclos de control, b)
Pila, el rea de pila se utiliza generalmente para manejar la continuacin de instrucciones en ciclos anidados,
as como el paso de argumentos en las funciones, c) Heap, rea de menoria de acceso aleatorio,
pprincipalmente utilizada para el almacenamiento de objetos (datos, funciones, thunks). Asimismo, existe un
rea de tipo esttica la cual se utiliza para el almacenamiento de cdigo y de objetos estticos.
102
En la figura 3.21, se muestra en forma textual la reduccin paso a paso de la expresin aritmtica
representada con el grafo de la Figura 3.20. se observa como los trminos repetidos solamente se indican una
sola vez. El proceso de reduccin sigue la regla de evaluacin de derecha a izquierda, y de arriba hacia abajo
(top-down).
En este caso, se observa que el subgrafo (3 + 3) se evala en primera instancia, mientras que la evaluacin
de los trminos repetidos se va retardando hasta el final.
ensamblador VAX.
103
Ejercicios
3.1. Escribir la siguiente definicin para una AND lgica en trminos de foldr:
and :: [Bool] -> Bool
and [ ]
= True
3.2. Se puede notar que el segundo elemento de la lista infinita enumFrom 0 consiste en la expresin no
evaluada (0+1) y no la expresin 1. Se puede pensar en una mejor implementacin de enumFrom usando la
funcin seq siguiente:
Estos objetos pueden representarse como un tipo algebraico: data Sign = Rock | Paper | Scissors, descritos
como sigue:
beat :: Sign -> Sign
beat Rock = Paper
beat Paper = Scissors
beat Scissors = Rock
Escriba el programa completo y documntelo.
3.4. Este ejercicio tiene como finalidad integrar funciones de orden alto y la evaluacin perezosa, se trata de
implementar el conocido Juego del Gato. En este juego se intenta saber que tan buena es la posicin que
escogi un jugador (X, 0). Se permite que las posiciones en el tablero sean representadas por objetos de tipo
Posicion. El tipo vara de juego a juego. Se considera que existe la manera de saber que movimientos
pueden ser realizados desde una posicin: asuma que existe una funcin descrita por: moves: position ->
listof position, la cual toma la posicin del juego como argumento y retorna la lista de todas las posiciones que
pueden ser alcanzadas. Ver este ejercicio en [Hughes, 1989].
104
Captulo 4
FUNDAMENTOS DE LA
PROGRAMACIN LGICA
Introduccin
La programacin lgica es un enfoque
imperativa y la funcional. Aunque la diferencia entre estos paradigmas no siempre resulta clara un programa
funcional puede compartir algunos aspectos con los lenguajes imperativos- las diferencias entre ellos, son las
que determinan el diseo y esquemas de razonamiento en los programas. Con el fin de comprender la
programacin lgica, se debe analizar la diferencia entre computacin y deduccin. Para computar se parte de
una expresin, y de acuerdo a un conjunto fijo de reglas (el programa) se genera un resultado. Por ejemplo,
15 + 26 = 41. Para deducir, se empieza con una conjetura y, de acuerdo con un conjunto fijo de reglas
(axiomas y reglas de inferencia), tratamos de obtener la prueba de la conjetura. De esta forma, mientras la
computacin consiste en un proceso mecnico y no requiere de ingenuidad, la deduccin es un proceso
creativo [Frank, 2006].
Por siglos, filsofos, matemticos y cientficos de la computacin, han tratado de unificar estos enfoques, o al
menos, entender la relacin entre ellos. Por ejemplo, George Boole, tuvo xito al reducir cierta clase de
razonamiento lgico a travs de cmputo mediante el lgebra booleana. En el siglo veinte, los fundamentos
de la indecibilidad indicaron que, no cualquier cosa con la que se pueda razonar resulta ser mecnicamente
computable, an si se sigue en un conjunto bien definido de reglas formales. Sin embargo, se puede
considerar que la computacin puede ser vista como una forma limitada de deduccin, debido a que puede
establecer teoremas. Por ejemplo, 15 + 26 = 41, es tanto, el resultado de una computacin como un teorema
aritmtico. Asimismo, una deduccin puede considerarse una forma de computacin si se fija una estrategia
para la bsqueda de pruebas. Esta idea establece el fundamento de la programacin lgica, es decir, un
programa lgico realiza una bsqueda de pruebas en base a una estrategia.
La ventaja de la programacin lgica es que sus programas poseen una doble semntica: lgica y
operacional, las cuales pueden ser analizadas de manera separada. Si la lgica subyacente se escoge
105
lgica, es la parte ms pequea e indivisible que forma parte de una frmula lgica. La lgica de
proposiciones permite expresar varias leyes simples, tales como la ley de De Morgan
y la ley de contraposicin
se tiene que asignar un valor de verdad a cada uno de sus tomos. Posteriormente, se evala la frmula
aplicando las reglas usuales para
y, para la negacin
Tabla 4.1. Valores de verdad para diferentes conectores y dos valores atmicos.
falso
falso
falso
falso
verdadero
verdadero
verdadero
falso
verdadero
falso
verdadero
falso
verdadero
verdadero
verdadero
falso
falso
verdadero
falso
falso
falso
verdadero
verdadero
verdadero
verdadero
verdadero
vedadero
falso
106
lgica de primer orden, es ms adecuada para este fin, ya que generaliza a la lgica proposicional,
agregndole variables, trminos, frmulas y cuantificadores. Un predicado es una afirmacin que expresa una
propiedad de un objeto, o una relacin entre objetos. Estas afirmaciones se hacen verdaderas o falsas cuando
se reemplazan las variables (objetos) por valores especficos. Por ejemplo, la afirmacin p(x) : x es alta es
un predicado que expresa la propiedad del objeto x de ser alta. Si sustituimos la variable x por un valor
determinado, por ejemplo Laura, entonces el predicado se transforma en la proposicin Laura es alta que
podr ser verdadera o falsa. En este sentido, el predicado q(x, y) : x es primo y, expresa una relacin entre
los objetos x e y. Si sustituimos x por Pedro e y por Juan, obtendremos la proposicin Pedro es primo de
Juan.
En los lenguajes de programacin, aparecen estructuras de decisin del tipo Si p Entonces q. Bajo el
contexto de un lenguaje de programacin lgico, esta
645
Por ejemplo, para el segmento de programa que aparece en las lneas siguientes, determine el nmero de
a b a : b ab ab
false false true
true
false true false
true
false true false false
true true true
true
a
b
false false
false true
y > 0 Entonces
true false
true true
a
true
true
false
false
de lo contrario
If the formula is true for all possible assignment s of its atoms, then it is called
a tautology. Both t he contrapositive law and De Morgans law are examples of
La expresin
por los
predicados
,y
tautologies.
Theycondicional
are trueest
forformada
all four
possible
truth assignment
s of , pcomo
andambos
q. predicados
se convierten en proposiciones verdaderas al reemplazar el valor de la variable y, la sentencia
, solo
se der
ejecutapr
una
vez. e calculus
Fir st -or
edicat
Propositional
logic
is rather
weakde
aspredicados
a base fortiene
logic
programming,
be- En esta
Una frmula
lgica
en el clculo
la gramtica
mostrada principally
en la Figura 4.1.
cause itgramtica,
does not
allow expressing
data structures.
First-order
predicat
calculus
(a) representa
un tomo, mientras
que (f) indica una
frmula. Un tomo
en el e
clculo
de predicados
is muches better-suited
for
this.
The
predicate
calculus
generalizes
propositional
ms general que un tomo en el clculo proposicional, ya que puede tener argumentos.
logic with variables, terms, and quantiers. A logical formula in the predicate
calculus has t he following grammar, where a is an atom and f is a formula:
a
f
::=
::=
|
|
|
|
p( x 1, ..., x n )
a
x = f (l 1 : x 1, ..., l n : x n )
x1= x2
f 1
f2| f 1
f 2| f 1 f
x.f |x.f
| f
| f
At oms in Figura
predicate
calculus
arefrmula
moreexpresada
general en
than
propositional
atoms
since
t hey
4.1. Gramtica
de una
la lgica
de primer orden,
(a) es un
tomo,
y (f) una
frmula. p is a predicate symbol, f is a
can have arguments. Here x is a variable symbol,
term label, and the l i are t erm feat ures. The symbols ( for all ) and ( there
exist s ) are called quantiers. In like manner as for program stat ement s, we 107
can dene the free identier occurrences of a logical formula. Sometimes these
are called free variables, although strict ly speaking they are not variables. A
De esta forma, (x) representa el smbolo para las variables, p es un smbolo de predicado, f es la etiqueta de
un trmino, y las li indican las caractersticas de un trmino. Los smbolos (existe) y (para todo) son
llamados cuantificadores. De forma similar a las sentencias de los programas en otros modelos de
programacin, se permite la definicin de identificadores de libre ocurrencia en una frmula lgica. Algunas
veces a estos identificadores se les conoce como variables libres, aunque estrictamente hablando, no son
variables. Una frmula lgica que no tiene identificadores libres se le denomina sentencia lgica. Por ejemplo,
la expresin
libres:
no es una sentencia lgica, ya que cuenta entre sus trminos con dos variables
por ejemplo,
Existen varios modelos posibles para estos axiomas, por ejemplo el siguiente:
108
Las relaciones solo indican las tuplas que son verdaderas, las dems tuplas se asumen falsas. Basndonos
en este modelo, se pueden asignar valores de verdad a las sentencias del clculo de predicados. Por ejemplo,
la sentencia
, puede ahora ser evaluada como una sentencia falsa. Hay que
tomar en cuenta que la relacin de igualdad forma parte de este modelo, an cuando los axiomas no la
definan de forma explcita.
Los
cuanificadores
se
evalan
de
izquierda
derecha,
por
ejemplo
la
expresin
], indica lo siguiente: Para cada persona que elijamos en el universo de discurso, existe
otra que est casada con ella. Otra expresin ms
nmero entero y tal que su suma con cualquier otro nmero entero es cero. Se demuestra que esta
proposicin es falsa.
Considerando lo descrito, se puede definir con ms precisin un programa lgico o un programa basado en
lgica, en este caso: lgica de predicados. Como mencion anteriormente, un programa lgico consiste en un
conjunto de axiomas expresados en lgica de primer orden (LPO), una sentencia de interrogacin (query), y
un resolvedor de teoremas, es decir, un sistema capaz de realizar deducciones utilizando los axiomas en un
intento de probar la veracidad/falsedad de la interrogante. Con la finalidad de desarrollar un programa que
realice deducciones lgicas se tienen que considerar los siguientes aspectos:
o
Tericamente, un resolvedor de teoremas est limitado a lo que puede hacer. El resolvedor solo
garantiza encontrar, o no, una prueba para preguntas que resultan verdaderas en todos los modelos.
Si solo nos interesa algn modelo especfico, entonces, pudiera o no existir una prueba, an cuando
la pregunta resulte verdadera. Se dice que el resolvedor de teoremas es incompleto.
An cuando el resolvedor de teoremas pueda tericamente encontrar un resultado, ste podra ser
ineficiente. La bsqueda de una prueba podra tomar un tiempo exponencial. Un resolvedor de
teoremas podra tener una semntica operacional simple y predecible, as que el programador puede
definir sus algoritmos y razonar acerca de su complejidad.
La deduccin obtenida por el resolvedor puede ser constructiva. Es decir, si la pregunta indica que
existe un x que satisface alguna propiedad, entonces, el sistema debe construir una seal de su
existencia. En otras palabras, debera construir una estructura de datos como una salida de un
programa lgico.
Con el fin de enfrentar los aspectos anteriores, se proponen los siguientes enfoques:
o
Se asignan restricciones a los axiomas, de tal suerte que, es posible construir un resolvedor de
teoremas constructivo. En este sentido, el lenguaje Prolog, se basa en las clusulas de Horn, las
cuales son axiomas de la forma:
109
donde
son escogidas de tal forma que el axioma no tenga variables libres. Las clusulas
de Horn son interesantes ya que utilizan una regla de inferencia llamada resolucin [Lloyd, 1987] la
cual genera un resolvedor de teoremas constructivo eficiente.
o
Un gran logro en las ciencias computacionales, es que los sistemas prcticos de programacin lgica han
sido creados combinando los dos enfoques mencionados. El primer lenguaje en alcanzar este objetivo fue
Prolog [Roy, 1994], el cual puede competir en velocidad de ejecucin con los lenguajes imperativos.
Prolog. Los programas prolog son generalmente declarativos, aunque de manera inevitable integran
elementos procedurales. Los programas escritos en este lenguaje se basan en tcnicas desarrolladas por los
lgicos formando conclusiones vlidas a partir de la evidencia disponible. Se distinguen dos componentes en
estos lenguajes: hechos y reglas [Bramer, 2005]. El programador, almacena en la memoria de la
computadora, generalmente a travs de un programa,
programa, a travs una sesin tpica consistente en una serie de preguntas (queries), el sistema Prolog
responde usando las reglas y los hechos disponibles en el programa. En las siguientes 5 lneas se muestran,
4 hechos y una regla:
perro (sultn).
perro (pinto).
gato (felix).
gato (henry).
animal (X) :- perro (X).
No resulta difcil saber cuales son los hechos y cual la regla. Con los datos anteriores se pueden hacer las
siguientes consultas:
?- perro (pinto).
110
Yes
?- gato(X)
X = felix
X = henry
Se observa que un lenguaje lgico, como Prolog, recibe interrogantes (en lenguaje natural) como informacin
de entrada, y mediante un proceso deductivo que involucra hechos y reglas, busca responder a ellas.
Determinar que felix es un gato involucra el siguiente razonamiento lgico:
DADO QUE
cualquier X es un animal si este es un gato
Y
felix es un gato
DEDUCCIN
felix es un animal
El lenguaje Prolog tiene una amplia variedad de aplicaciones, entre las cuales figuran las siguientes:
1.
2.
3.
111
4.
Utilice los predicados Gobierna, EsGuatemalteco, TieneAnimal, TienePerro, TieneGato, EsPerro, EsVacuna,
EsEnfermedad, DebeVacunar
En la Figura 4.2 se ilustra un conjunto de 8 lneas donde las 7 primeras corresponden a los hechos que
determinan los cuatro puntos anteriores, mientras que la ltima lnea expresa la regla.
En la Figura 4.3, se muestra la implementacin en lenguaje Prolog13 del ejemplo 1. Se puede observar (Figura
4.2) que se declararon siete hechos, en letras maysculas, as como una regla relacionando dos variables (X,
Y). Posteriormente, se observan 4 lneas, las cuales representan, en lenguaje de lgica de predicados las
lneas que inician el ejemplo.
Figura 4.2. Conjunto de reglas y hechos relacionados con el enunciado del ejemplo 1.
13
En estos ejercicios se est aplicando la versin de Prolog SWI-prolog, por Sterling & Shapiro, cuya informacin:
Software, manual de referencia, etc., se encuentra en la siguiente direccin: http://www.swiprolog.org/pldoc/doc_for?object=manual
112
Figura 4.3. Implementacin en Prolog de los hechos y reglas mostrados en la Figura 4.2.
Ejemplo 2. En este ejemplo, se muestra la versatilidad del lenguaje lgico para representar el comportamiento
de circuitos electrnicos, as como la relacin entre sus diferentes componentes. Se trata en este caso de
realizar los hechos y las reglas en Prolog, que tengan como finalidad representar las entradas para las
compuertas AND y NOT correspondientes a las redes mostradas en la Figura 4.4. Se observa que la salida
(Z) del primer circuito, va a ser parte de las entradas del segundo circuito.
Figura 4.4. Ilustracin de dos redes basadas en una combinacin de compuestras AND y NOT.
113
Se observa en las lneas anteriores que, las primeras 2 lneas representan las posibles entradas para la
compuerta tipo inversor (NOT), en este caso, el primer valor indica el dato de entrada, y como segundo valor
la salida correspondiente. En las siguientes 4 lneas se describen las posibles combinaciones, 4 en total, para
la compuerta AND, el tercer valor indica la salida correspondiente a las combinaciones de las entradas.
En la Figura 4.5, se observa la ejecucin de las reglas donde se proporcionan las entradas travs de las
reglas descritas, y se obtiene como salida el resultado lgico de las compuertas.
Ejemplo 3. Se muestra en este ejemplo, una de las aplicaciones ms empleadas de los programas lgicos, y
consiste en su facilidad para el manejo de relaciones. En este caso, se trata de relaciones familiares.
Figura 4.5. Ilustracin del resultado, salida lgica, de los circuitos mostrados en la Figura 4.4.
114
Definir en Prolog los predicados que definen por extensin todas las relaciones familiares directas,
padre(Padre, Hijo) y madre(Madre, Hijo) del rbol genealgico de tu familia. Definir la relacin progenitor,
utilizando las relaciones de padre y madre. Definir recursivamente la relacin antepasado. Probar definiciones
alternativas de esta relacin cambiando el orden de los predicados. Comprobar cmo afecta al
comportamiento del programa el orden usado en las distintas definiciones de antepasado. Definir nuevas
relaciones (como hermano, hermana, abuelo, abuela) aadiendo los predicados (por ejemplo mujer,
hombre) y reglas necesarios.
En la Figura 4.6, se muestra un ejemplo de rbol genealgico familiar que se va a representar en cdigo
Prolog (ver Figura 4.7), las consultas a la base de datos almacenada se pueden observar en la Figura 4.8.
Figura 4.6. Diagarama del rbol genealgico corresondiente a: Jonathan Mariche Catana,
alumno de
hombre (jonathan).
hombre (silvestre).
hombre (vicente).
hombre (leovigildo).
mujer (katia).
mujer (patricia).
mujer (margarita).
mujer (flavia).
progenitor (jonathan, silvestre).
progenitor (katia, silvestre).
progenitor (silvestre, leovigildo).
progenitor (patricia, vicente).
progenitor (jonathan, patricia).
progenitor (katia, patricia).
115
Figura 4.7. Cdigo en Prolog (clusulas: hechos y reglas) que representa el rbol genealgico mostrado en la
Figura 4.6.
Figura 4.8. Declaracin de predicados y variables (a), as como interrogantes (b) realizadas en Prolog, y
relacionadas con el rbol genealgico de la Figura 4.6.
116
Los resultados observados en los ejemplos anteriores fueron obtenidos a travs de un proceso deductivo. Las
tcnicas utilizadas por los lenguajes lgicos para realizar un razonamiento y llegar a conclusiones son muy
diversas. Sin embargo, una de las ms utilizadas se basa en la tcnica de Resolucin introducida por
Robinson en 1930. A continuacin voy a describir la operacin de esta tcnica conjuntamente con el proceso
de buscar la correspondencia entre clusulas en una base de datos, tarea conocida como Unificacin.
117
persona(Juan, Gmez, 23), se realiza de forma exitosa al hacer corresponder las variables X, Y, Z con los
argumentos Juan, Gmez, 23. Con la finalidad de realizar la unificacin de dos trminos de manera exitosa,
se debe cumplir lo siguiente:
Dos tomos se unifican si y slo si son los mismos: sultn con sultn.
Dos trminos compuestos se unifican si y solo si tienen el mismo predicado y la misma aridad, y sus
argumentos pueden unificarse uno a uno de izquierda a derecha.
Dos nmeros se unifican si son iguales, de esta forma 7 slo se unifica con 7.
Dos variables, por decir X y Y, siempre se unifican con las variables asignadas a cada una de ellas.
Una variable no asignada y un trmino que no es una variable siempre se unifican con la variable
asignada que se encuentra en el trmino:
X y sultn se unifican, con la variable X asignada al tomo sultn.
X y [a, b, c] se unifican, con X asignada a la lista [a, b, c].
X y mipred(a, b, P, Q) se unifican, con X asignada a mipred(a, b, P, Q).
Dos listas se unifican, si y slo si tienen el mismo nmero de elementos, y sus elementos pueden ser
unificados uno a uno, iniciando de izquierda a derecha.
[a, b, c] puede unificarse con [X, Y, c], asignando X con a, y Y con b.
[a, b, c] no puede unificarse con [a, b, d].
La expresin [a, mipred(X, Y), K] puede ser unificada con [P, Z, tercero] con la variable P asignada
al tomo a, la variable Z asignada al trmino compuesto mipred[X, Y], y la variable K al tomo
tercero respectivamente.
Evaluacin de metas por unificacin. Supongamos que se realiz un programa Prolog para el manejo de
las capitales de Amrica, as como frases asociadas, y que en la base de datos se tienen las siguientes dos
reglas y un hecho:
R1:pred (X, capitales de Amrica) :- capital (X, Y), america(Y), write(X), nl.
R2: america (mexico):- write (Dios cuide a los Mexicanos!), nl.
capital (mexico, mexico).
Si la meta consiste en evaluar: ?-pred(mexico, A). El proceso de unificacin causa que, al revisar la base de
datos, el tomo mexico se asigne a la variable X y que la variable A se asocie al tomo capitales de
Amrica. Enseguida, las asignaciones realizadas con el encabezado del predicado (R1) afectan al cuerpo de
la regla, reemplazando las ocurrencias de las variables, en este ejemplo X por mexico. La regla 1 (R1 por
referencia), aparecer entonces como:
R1: pred(mexico, capitales de Amrica):- capital (mexico, Y), america(Y), write(mexico), nl.
118
A continuacin, se examina el cuerpo de la regla 1, checando los trminos uno a uno de izquierda a derecha,
todos los trminos deben estar relacionados para que la meta original se considere exitosa. La evaluacin de
las reglas se realiza de forma parecida a la del trmino de entrada, es decir, si el encabezado de un predicado
se unifica con algn otro predicado de la base, se realiza el reemplazo de variables , y as sucesivamente. En
este caso, al analizar el predicado capital(mexico, Y), ste coresponde con el hecho capital(mexico, mexico),
por lo que el primer trmino de la regla 1 es satisfecho, con la variable Y asignada al tomo mexico. Esta
asignacin afecta los trminos restantes de la regla 1, por lo que, ahora esta regla se reescribe como:
R1: pred(mexico, capitales de Amrica):- capital (mexico, mexico), america(mexico), write(mexico), nl.
En este momento, asumiremos que la primera clusula en la base de datos que tiene un encabezado que
unifica con el objetivo del segundo predicado, es la regla 2 (R2), en este caso se realiza el llamado a esta
regla..
Ahora, el sistema prolog trata de satisfacer los objetivos indicados en el cuerpo de la regla 2, el cual consiste
en la visualizacin de un mensaje, as como la instruccin de salto de lnea (nl.). En este proceso, se muestra
el siguiente mensaje:
Dios cuide a los mexicanos!
Con esto, las metas de la regla 1 han sido cumplidas, por lo tanto, los objetivos que se establecen en su
encabezado pred(mexico, capitales de america) son satisfechos.
Esto a su vez, ocasiona que la entrada del usuario ?-pred(mexico, A), resulte tambin satisfecha. Por lo
tanto, la respuesta generada por el sistema Prolog sera la siguiente:
?- pred(mexico, A).
Dios cuide a los mexicanos!
mexico
A
El principal objetivo de un sistema Prolog consiste en la evaluacin de objetivos, ya sean resultado de una
entrada del usuario o establecidos en el cuerpo de las reglas, a travs del proceso de unificacin de los
encabezados de las clusulas. Como se observ en el ejemplo mostrado, este proceso crea enlaces entre
objetivos, los encabezados de las clusulas y los cuerpos de las reglas almacenadas en la base de datos (ver
Figura 4. 9).
119
Figura 4.9. Visualizacin grfica del proceso de Unificacin que inicia con la entrada del usuario: ?pred(mexico, A).
En el ejemplo anterior se trat de dar una idea intuitiva del proceso de unificacin, una descripcin, un poco
ms formal de este algoritmo, se ofrece enseguida. Unificacin es el proceso de encontrar un unificador para
dos tomos. Para que puedan ser unificados, dos tomos deben tener la misma estructura (pensar en
constantes y variables). Ya que los tomos pueden contener expresiones anidadas como argumentos, se
requiere un algoritmo recursivo, es decir, basado en el uso de pilas. Antes de describir el algoritmo, se van a
definir un par de elementos con el fin de clarificar la explicacin que sigue:
Definicin 1
Un trmino se define inductivamente de la siguiente forma:
1.
2.
Si f es una funcin de aridad n y t1,,tn son trminos, entonces f(t1,,tn) es tambin un trmino.
3.
Definicin 2
Sea p un predicado n-ari y t1,,tn trminos. Entonces p(t1,,tn) es un tomo.
120
Intuitivamente, el algoritmo de unificacin primero verifica que los dos tomos especificados inicien con el
mismo smbolo de predicado, de otra forma los tomos no se podrn unificar. Si los smbolos de predicado
son los mismos entonces el algoritmo checa que los trminos que forman los parmetros tengan la misma
estructura. Se crea una pila para almacenar parejas de trminos correspondientes (a partir de los dos
tomos). En este punto, el algoritmo de manera repetida, saca un par de trminos del topo de la pila y
compara sus estructuras. En tanto se siga manteniendo una correspondencia en tre los trminos comparados,
este paso contina. Si se encuentra alguna diferencia, en la comparacin, los tomos no pueden ser
unificados y el algoritmo finaliza. En otro caso, el proceso contina hasta que ya no existan trminos que
comparar en la pila (pila vaca), y se determina que existe un unificador.
Definicin 3
Algoritmo de Unificacin
Sean A1(t1
n)
y A2(s1
m)
dos tomos.
Algoritmo:
Si A1 y A2 son predicados diferentes o n <= m
Entonces resultado fallido.
De otra forma
Inicializar la sustitucin a vaco,
La pila para contener las parejas (t-i, s-i),
y Falla a falso.
Mientras la pila no est vaca y no haya falla
Hacer pop (X, Y) de la pila
caso
X es una variable que no ocurre en Y:
sustituir Y por X en la pila y en la sustitucin
agregar X=Y en la sustitucin
121
yY
f y
Hay un par de cosas que hay que mencionar en relacin al algoritmo descrito con anterioridad:
1.
Si dos tomos se unifican, el algoritmo siempre retorna el unificador ms general para los dos
tomos.
2.
En los primeros dos componentes de la sentencia case la frase no ocurre en se relaciona con un
chequeo de ocurrencia. Esta condicin protege de una sustitucin como
, la cul no puede
ser unificada.
En el algoritmo de unificacin se hace referencia al concepto de sustitucin. Una sustitucin consiste de un
conjunto finito de igualdades, tales como:
Donde, cada
cada
es una variable y
es un trmino, asimismo
Generalmente, las sustituciones son representadas por las letras griegas , , y . Por ejemplo, el conjunto
representa una sustitucin, sin mebargo, los conjuntos
, no lo son, ya que el conjunto la misma variable (X) ocurre dos veces en la unin, mientras que en
Y = Y viola la condicin de que cada Xi debe ser distinta a ti.
Bajo estas definiciones voy a mostrar un ejemplo del proceso de unificacin.
Ejemplo. Considere los dos tomos
)] as como =.
122
2.
de la pila, dejando [(
Se lee
Se lee
Leer
)].
) de la pila, quedando la pila vaca [ ]. Para el cuarto case, se ajusta la pila para
)], sin ajustes a .
almacenar [(
4.
5.
La unificacin puede verse como un mtodo o estrategia que realiza el reemplazo de tomos buscando
coincidencias y a la vez satisfacer ciertas metas. La idea de hacer coincidir tomos, busca encontrar de forma
rpida contradicciones en la correspondencia. Su empleo es una parte bsica de algunos algoritmos aplicados
en la demostracin automtica de teoremas, as como en aplicaciones de inferencia en la lgica de
predicados, tal como sucede con la tcnica de Resolucin [Robinson, 1965].
123
resolucin, el cul veremos a continuacin. El mecanismo de resolucin determina qu variables deben de ser
sustitudas por cules valores con el fin de satisfacer un query. En este proceso hay que notar dos aspectos:
1) La manera de usar el unificador retornado por el respectivo algoritmo para determinar la sustitucin de
valores, y 2) como asegurar que se retornan todos los posibles valores de sustitucin?. El segundo aspecto
involucra un proceso de revisin hacia atrs o de retroceso (backtracking), y ocurre cuando el usuario
responde con un punto y coma (;) a una sustitucin de retorno.
?-
j h Ch
wr
Th Ch
wr
Ch
Ante esta consulta, Prolog intenta satisfacer todos los objetivos (predicados, instrucciones, tomos, clusulas)
en la secuencia (simultneamente), y en el camino, encontrar uno o ms valores posibles para la variable
Child. El proceso inicia con el primer objetivo: parent (john, Child), el cual intenta unificar con el encabezado
(argumentos) de cada una de las clusulas relacionadas con el predicado parent, el trabajo de revisin inicia
de arriba hacia abajo (top-bottom)14. Las primeras clusulas en ser revisadas, con fines de obtener alguna
correspondencia, son las etiquetadas como [P1] y [P2] (ver Figura 4.10) pero se genera una falla al tratar de
unificarlas con el objetivo. La falla se ocasiona debido a que estas clusulas tienen valores constantes como
argumentos. Enseguida inicia la revisin de la clusula etiquetada como [P3] y en este momento, el objetivo
se hace corresponder de manera exitosa con el encabezado de la clusula [P3]: parent(X, Y):write(mother?), nl, mother(X, Y), write(mother!), nl, realizando la asignacin de X = john, y de la variable Y
con Child (otra variable), quedando esta regla como se muestra en la siguiente lnea:
[P3] parent(john, Y):- wr
h r?
h r j h Y wr
h r!
En este punto, el sistema ahora trabaja a travs de las metas con el cuerpo de la regla [P3] intentando
hacerlas corresponder.
Se realiza la evaluacin exitosa de write(mother?) y de nl, mostrando, como un efecto lateral, la siguiente
lnea de texto:
14
Este mecanismo de bsqueda tambin se le conoce como dirigido por objetivos (goal-directed), a diferencia del procedimiento de
razonamiento hacia delante (forward reasoning).
124
mother?
Ahora se maneja el tercer objetivo, mother(John, Y). Este, no unifica con los encabezados de ninguna de las
clusulas [M1] a [M10], que definen al predicado mother. As que se produce una falla.
En este punto el sistema realiza un retroceso. Y se dirige al objetivo satisfecho de manera ms reciente en el
cuerpo de la regla [P3], desplazndose ahora de derecha a izquierda, encuentra a nl, e intenta resatisfacerlo,
es decir, encontrar otra forma de satisfacerlo. Sin embargo, y al igual que muchos predicados integrados, nl
no se puede volver a satisfacer cuando es evaluado durante el retroceso, as que este objetivo falla. El
proceso determina un nuevo desplazamiento, otra posicin a la izquierda, en el cuerpo de [P3], al objetivo
write(mother?). Este predicado resulta tambin no satisfacible por lo que este objetivo tambin falla. Ya que
no existen ms objetivos que evaluar, de derecha a izquierda, en el cuerpo de la regla [P3], el sistema Prolog
rechaza esta regla. Hasta ahora se tiene solamente:
?- parent(john, Child), write(The child is ), write(Child), nl.
Donde la variable Child contina sin asignrsele un valor.
Prolog ahora retrocede al objetivo evaluado ms recientemente: parent(John, Child), y trata de resatisfacerlo.
La bsqueda de clusulas contina en la base de datos a partir del punto previamente alcanzado (clusula
[P3]). De esta manera, se examina la clusula [P4] la cual unifica de manera exitosa el objetivo inicial con el
encabezado de [P3], asignando a la variable A con john y a la variable B con Child, quedando la clusula
[P4]: parent(John, B):- write(father?), nl, father(john, B), write(father!), nl.
El sistema ahora trabaja a travs del objetivo en el cuerpo de la regla [P4]. Los primeros dos objetivos resultan
exitosos, y se muestra como salida la lnea de texto:
father?
Prolog ahora trata de satisfacer el tercer objetivo: father(john, B). Se busca en la base de datos, de arriba
hacia abajo, la primera clusula en corresponder es [F2] la cual es un hecho, esto causa que la variable B sea
asignada al tomo mary. Esta asignacin a la vez causa que la variable Child (previamente asignada a B) sea
reasignada al tomo mary. Despus de estos cambios, las clusulas [P4] y [F2] aparecen como:
f h r?
f h r j h
ry wr
f h r!
125
40
mother(ann,henry).
[M2]
mother(ann,mary).
[M3]
mother(jane,mark).
[M4]
mother(jane,francis).
[M5]
mother(annette,jonathan).
[M6]
mother(mary,bill).
[M7]
mother(janice,louise).
[M8]
mother(lucy,janet).
[M9]
mother(louise,caroline).
[M10]
mother(louise,martin).
[F1]
father(henry,jonathan).
[F2]
father(john,mary).
[F3]
father(francis,william).
[F4]
father(francis,louise).
[F5]
father(john,mark).
[F6]
father(gavin,lucy).
[F7]
father(john,francis).
[F8]
father(martin,david).
[F9]
father(martin,janet).
[P1]
parent(victoria,george).
[P2]
parent(victoria,edward).
[P3]
parent(X,Y):-write('mother?'),nl,mother(X,Y),
write('mother!'),nl.
[P4]
parent(A,B):-write('father?'),nl,father(A,B),
write('father!'),nl..
[P5]
parent(elizabeth,charles).
[P6]
parent(elizabeth,andrew).
Figura 4.10. Ilustracin de relaciones familiares [Bremer, 2005]. Las etiquetas, solamente son agregadas
como referencia, no forman parte del programa.
Todos los objetivos en el cuerpo de la regla [P4] han resultado exitosos, de tal forma que el encabezado de la
clusula reescrita ahora como parent(john, mary), es exitosa. Por lo tanto, el primer objetivo parent(john,
Child) en la consulta del usuario es satisfecho. Asimismo, los siguientes tres objetivos en la consulta resultan
satisfechos, por lo que, como resultado se muestra la lnea de texto:
126
resolucin-SLD,
inferencia.
127
Una clusula de programa, consiste en una clusula de Horn con una literal positiva y, una o ms,
literales negativas.
Asimismo, es necesario tener en cuenta las siguientes definiciones:
Un conjunto de clusulas de Horn no objetivo cuyos encabezados tienen la misma letra de predicado
es un procedimiento.
Ejemplo 4.3.1. En las siguientes lneas de un programa lgico se muestran dos procedimientos: p y q,
adems p resulta ser tambin una base de datos.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
15
En muchos libros de programacin lgica se prefiere el uso del smbolo al mostrado, ya que, segn los autores,
representa una forma natural para indicar: para probar A, probar B1, B2,
128
, donde el cuantificador
. Para dar un ejemplo de esto,
la sustitucin
, por lo tanto:
Ya que
.
, es una sustitucin correcta para G.
y = , la sustitucin vaca:
Ya que
, por lo tanto:
De tal forma que, para cualquier sustitucin que convierte la conjuncin en una frmula cerrada,
resulta verdadera para cualquier modelo de P. Esto explica la terminologa y porque la
sustitucin ofrece una respuesta a la consulta expresada en la clusula objetivo.
129
Seleccionar
2.
d}.
3.
4.
Solamente queda una literal, la cul ser resuelta con la clusula 3 dando como resultado la clusula
vaca [], a travs de la sustitucin {z
a}.
cabeza de
unifica con
y resolviendo:
.
.
.
16
SLD significa Selection-rule driven (seleccin manejada por reglas), Linear resolution (resolucin lineal), Definite
clauses (clusulas definidas).
130
i
Una refutacin-SLD es una derivacin-SLD para la clusula vaca []. La regla para seleccionar una literal
a partir de una clusula objetivo es la regla computacional. Por otro lado, la regla para seleccionar una
clusula
17
es la regla de bsqueda .
r b
Prolog es un lenguaje de programacin lgico escrito en formato de las clusulas de Horn . El cmputo en
este lenguaje aplica la resolucin-SLD con una regla computacional especfica: seleccionar la literal ms a la
izquierda de una clusula objetivo, y una regla de bsqueda especfica: escoger las clusulas de arriba hacia
abajo en la lista de clusulas. La tcnica utilizada por la resolucin-SLD para realizar bsqueda y clculos se
basa en los rboles-SLD. Es decir, el conjunto de derivaciones-SLD para un programa lgico puede
representarse a travs de un rbol. Enseguida se va a mostrar la definicin de este rbol y posteriormente
mostrar un ejemplo.
17
18
En este caso, y a diferencia de la notacin matemtica, las variables se escriben en letras maysculas, los predicados,
constantes y funciones se representan en letras minsculas, y el smbolo :- reemplaza a .
131
rbol-SLD. Sea P un conjunto de clusulas de programa, R una regla computacional y G una clusula
objetivo. Un rbol-SLD (ver Figura 4.11) se genera de la siguiente manera: La raz se etiqueta con la clusula
objetivo G. Dado un nodo n etiquetado como
, se crea un hijo
, la
cul puede obtenerse resolviendo el literal escogido por la regla R, y con el encabezado de una clusula en P.
Figura 4.11. Ilustracin de un rbol-SLD aplicado para seleccionar la literal ms a la izquierda [Ben-Ari, 2012].
Este rbol se basa en los datos (clusulas y hechos) del Ejemplo 4.3.1. Recordar que en este ejemplo la
clusula objetivo es q(y, b).
La regla computacional siempre selecciona la literal ms a la izquierda de la clusula objetivo, esto se ilustra
en la Figura 4.11 mediante el subrayado de la literal. El nmero que aparece en los vrtices de la Figura 4.11,
se refiere al nmero de la clusula de programa reemplazado en la clusula objetivo. En esta representacin,
una rama finalizando con una refutacin se considera una rama exitosa. A su vez, una rama que se dirige a
una clusula objetivo cuya literal elegida no unifica con alguna clusula es una rama fallida. La tcnica de
bsqueda que se aplica en la representacin tipo rbol, puede ser cualquiera de las dos siguientes: 1)
Bsqueda primero en amplitud, donde los nodos de cada nivel se checan antes de pasar al siguiente, este
mtodo garantiza encontrar una rama exitosa si es que existe, 2) Bsqueda primero en profundidad, realiza
una bsqueda hacia abajo en cada rama. En la prctica se prefiere la segunda opcin de bsqueda, ya que
requiere mucho menos memoria.
El problema que se puede encontrar en la tcnica de bsqueda primero en profundidad, es que puede
conducir a un nodo no terminal an si existe un nodo de terminacin. Esto se puede evitar a travs de un
ordenamiento cuidadoso de las clusulas y de las literales en ellas.
132
Ya que una falla puede ocurrir en cualquier paso, Prolog debe almacenar una lista con puntos de retroceso
(backtrack points) los cuales representan los nodos previos del rbol donde hay ramas adicionales.
Para dar claridad a este proceso, considere un programa lgico formado por los siguientes cuatro hechos:
p (a).
p (b).
P (c).
P (d).
Y la clusula objetivo:
:- p(X), q(X).
En la Figura 4.12, se muestra el rbol-SLD que representa al programa mencionado y descrito en las lneas
anteriores.
Figura 4.12. rbol-SLD que representa los hechos p(a), p(b), p(c), p(d) y la clusula objetivo :- p(X), q(X) [BenAri, 2012].
La bsqueda primero en profundidad, intenta en primera instancia, reemplazar la primer literal p(a), en la
clusula objetivo p(X). Mientras este reemplazo resulta exitoso, la clusula q(a) puede no resultar. La
bsqueda debe entonces retroceder y tratar con el siguiente reemplazo para p, a saber, p(b). En este caso, el
cmputo tambin resulta fallido, y se debe retroceder con el fin de encontrar una refutacin exitosa.
Debido a que Prolog no cuenta con estructuras para ciclos iterativos, implementa este aspecto a travs del
predicado fail el cual fuerza una falla y no requiere clusulas de programa. Su aplicacin se mostr en el
programa de relaciones familiares (ver Figura 4.10).
133
En la Secciones 4.1, 4.2 y 4.3 , se mostraron ejemplos de programas Prolog, los cuales como ye mencion
en esta seccin se basan en clusulas de Horn, algunos ejemplos ms se mostrarn ms adelante.
Interpretacin
I1
I2
Dominio
Nmeros naturales
Personas
Constantes
Funciones
Predicados
Se muestra una expresin bajo dos interpretaciones diferentes. Bajo la interpretacin I 1, la frmula equivale a
El cuadrado de todo nmero impar mayor que cero es mltiplo de 9, y el 2 es mayor que cero.
Mientras que bajo la interpretacin I 2, la frmula expresa que Todas las madres de los estudiantes de
informtica que juegan al pker son tercas y juan estudia informtica.
Como se puede observar en el ejemplo anterior, la semntica da significado a los programas y permite
describir formalmente lo que calculan. Hay tres maneras bien conocidas de dar significado o semntica a los
134
formarse a partir de las constantes y los smbolos de funcin que ocurren en . Por ejemplo,
sea
= {0, suc, nat} donde 0 es una constante, suc es un smbolo de funcin de aridad 1 y nat es un
predicado de aridad 1. El universo de Herbrand de
(
=
2.
es:
y los trminos en
es:
{
3.
Una estructura
funcin de
en ,
para
}.
y elementos
de
4.
es la siguiente:
para todo
Una vez establecidos los puntos anteriores concernientes a una definicin formal de semntica, se ofrecer a
continuacin un ejemplo basndonos en esta definicin. Consideremos un programa lgico P. En este caso, P
induce a un lenguaje de primer orden, donde las constantes, los smbolos de funcin y los predicados ocurren
en P. Entonces, podemos hablar del universo de Herbrand de P, denotado como
podemos referirnos a la base de Herbrand de P, denotada por
. De igual forma,
135
(
{
) (
) (
( (
) (
))
) (
Semntica declarativa. Desde el punto de vista lgico, un programa P puede verse como una teora lgica
formada por las clusulas del programa. Los modelos de Herbrand de esta teora son considerados los
modelos del programa P . Por ejemplo, la base de Herbrand del programa P ,
, es un modelo de P.
Entre las estructuras de Herbrand que son modelos de P, se destaca el que contiene exactamente los tomos
que son consecuencia lgica de P. Este modelo corresponde al significado entendido o estndar del
programa y es llamado el modelo mnimo de P, MP. El modelo MP se define como sigue:
por ejemplo, y tomando como referencia la expresin anterior, el modelo mnimo del programa P es:
(
Semntica operacional. La semntica operacional queda determinada por el proceso de inferencia utilizado
para probar que un objetivo puede ser derivado del programa. Esta semntica para los programas lgicos est
dada por un procedimiento se basa por un procedimiento de refutacin (Resolucin-SLD), el cul se mostr en
la seccin anterior.
Semntica denotacional. Esta semntica realiza la interpretacin de un programa lgico a travs de la
asociacin de una funcin sobre el dominio del programa. El significado o interpretacin bajo esta semntica,
viene dado por el punto fijo de la funcin, si es que existe.
136
rodea. En este sentido, en el lenguaje lgico, en este caso: lgica de predicados, el conocimiento se
representa a travs de los formalismos ya vistos con anterioridad (sentencias, clusulas, tomos, hechos).
Este conocimiento debe expresar informacin, datos y relaciones de un dominio especfico, tal informacin
generalmente se almacena en el programa como una base de reglas y clusulas (base de conocimientos).
Esta base de conocimientos debe contener informacin suficiente relacionada con un rea determinada, a fin
de resolver problemas. Por ejemplo, si se tiene en la base los datos azul, rojo y amarillo, as como las
constantes 10, 20 y 30, se pueden hacer varias asociaciones al respecto (cantidad de lpices y colores,
cantidad y color de focos, ). Sin embargo, si en esta base se encuentra el predicado: flores(color, cantidad),
entonces ya se tiene representado un conocimiento.
Con el fin de ilustrar la metodologa bsica utilizada para representar el conocimiento en un lenguaje de
programacin lgica, tal como Prolog, voy a presentar el siguiente ejemplo, obtenido de Gelfond y Leone
(2002).
Ejemplo 4.6.1. Sea cs un pequeo departamento en ciencias computacionales, localizado en colegio de
ciencias, cos, de la universidad, u. El departamento, descrito por la lista de sus miembros y el catlogo de sus
cursos, se encuentra en las ltimas etapas para la planificacin de sus cursos de verano. En este ejemplo se
va a mostrar la construccin de una sencilla base de conocimientos
departamento. Por simplicidad se van a considerar asignaturas ilimitadas, conteniendo nombres, cursos,
departamentos, etc. En este contexto, tanto la lista como el catlogo corresponden a las siguientes
colecciones atmicas:
miembro(jorge, cs).
miembro(jose, cs).
miembro(tom, cs).
curso(java, cs).
curso(c, cs).
curso(ai, cs).
curso(lgica, cs).
junto a las asumciones del escenario descrito expresadas por las siguientes reglas:
.
.
Las asumciones se justifican por la completez de la informacin correspondiente. La planificacin preliminar
se describe por la siguiente lista:
137
ensea(jorge, java).
ensea(jose, ai).
Ya que la planificacin est incompleta, la asumcin de mundo cerrado para el predicado ensea no resulta
apropiada. Por lo tanto, se procede a ampliar la base de conocimientos,
los cursos de ciencias computacionales son enseados solo por profesores de ciencias computacionales. El
curso de lgica es una excepcin a esta regla. Este puede ser enseado por profesores del departamento de
matemticas. Lo anterior es un caso tpico de omisin con una excepcin dbil, lo cual puede representarse
en Prolog por las siguientes reglas:
,
,
,
,
.
En este caso,
se aplica a
La segunda regla detiene la aplicacin de la omisin a cualquier P que pueda ser un profesor de matemticas.
Asumiendo que el predicado:
, la respuesta a la
consulta ensea(Jaime, c)? Ser: no, mientras la respuesta a consulta ensea(Jaime, lgica)? Ser:
desconocido. Vale la pena notar que, ya que la informacin de personas que pertenecen a los departamentos
involucrados est completa, la segunda de las reglas descritas puede reemplazarse por la siguiente:
.
Con el fin de completar nuestra definicin de ensea se va a expandir nuevamente la base de datos
para la
regla que indica Generalmente una clase es enseada por una persona. Esto se realiza por la siguiente
regla:
,
,
,
,
138
Ahora, si sabemos que lgica es enseada por Jorge seremos capaces de concluir que no es enseada por
Jaime. La base de conocimientos desarrollada hasta ahora es tolerante a actualizaciones sencillas. Se puede
fcilmente realizar algunas modificaciones en la lista de miembros del departamento y en el catlogo de
cursos. La representacin tambin permite el manejo de excepciones a las reglas de default, por ejemplo,
sentencias como ensea(juan, ai) anula las conclusiones por omisin. Por lo tanto, se permite el manejo de
excepciones en la base de conocimientos sin causar contradicciones.
Continuando con el tema, vamos a fijar ahora nuestra atencin en definir el lugar que ocupa el departamento
dentro de la universidad. Esto se puede realizar agregando a la base de datos las siguientes reglas:
.
.
.
.
.
Los primeros dos hechos, indican que el departamento forma parte de la estructura jerrquica mostrada en la
grfica de la universidad. La primera regla expresa la transitividad en la relacin parte de. La segunda regla
establece la asumcin de mundo cerrado para la relacin parte de, la cual se justifica, solo si
contiene el
esquema organizacional completo de la universidad. Si este es el caso, la asumcin de mundo cerrado para
miembro puede ser ampliada a
Dando un vistazo ms de cerca al programa generado, podemos descubrir algunos datos interesantes.
Primero, observamos que
Leone, 2002]). Para verificar que Jos es miembro de la universidad, se realiza la consulta: miembro(Jos, u)?.
En este caso, el programa despliega todos los trminos que satisfacen la relacin definida por el programa, en
cuyo caso, podemos utilizarlos para listarlos, por decir, despliega todos los miembros de CS.
Este proceso se realiza a travs de eliminacin recursiva por la izquierda aplicada a la base de datos,
realizando lo siguiente a las reglas anteriores:
.
.
.
20
Cabe mencionar que los autores estn utilizando una versin ampliada de Prolog, conocida como AProlog.
139
Con el fin de que la base de datos contemple un abanico ms amplio en sus respuestas se va agregar una
nueva relacin: ofrecido(C, D), la cul indica los cursos que son ofrecidos. Para esto, las siguientes reglas se
agregan a
:
.
.
Suponga que tambin Jorge y Rafa se han programado para ensear la clase de lgica. Una representacin
para
ello
requiere
de
una
disyuncin,
puede
expresarse
como:
140
En este caso, r1 es una constante utilizada para identificar la regla 1, el tomo ab(r1, X), se refiere a las aves
cuya habilidad de volar no est confirmada (es decir, cuando la regla 1 no se aplica). La primera regla expresa
una frase declarativa relacionada con
como asumciones de default). La regla 3, se utiliza, en este caso, para bloquear la aplicacin del default 1
(regla de cancelacin). La regla de cancelacin (4), puede verse como una instancia particular de un principio
de razonamiento general conocido como Principio de Herencia, de acuerdo con este principio, la informacin
ms especfica es preferible a la ms general.
Resulta fcil observar que este programa lgico, formado por 4 reglas y 2 hechos, nos da un modelo estable,
y a partir del mismo, podemos iniciar con la consulta vuela(pioln). Si
. Por el
se deriva de forma inmediata del hecho 1 (h1) y del lema. Asimismo, para probar
141
Para hablar al respecto, vamos a partir de la siguiente base de datos deductiva que almacena relaciones
familiares (ver Figura 4.13) la cual contiene definiciones para hombre, mujer, padre y madre, y cuya parte
intencional la forma padres y abuelo:
padre(adn, guillermo).
madre(ana, guillermo).
padre(adn, bety).
padre(guillermo, caty).
madre(ana, bety).
madre(caty, donaldo).
padre(donaldo, erik).
madre(diana, erik).
mujer(ana).
hombre(adn).
mujer(bety).
hombre(guillermo).
mujer(caty).
hombre(erik).
mujer(diana).
hombre(donaldo).
Figura 4.13. Ilustracin de una base de datos deductiva mostrando relaciones familiares.
En la mayora de los casos es posible organizar la base de datos de varias maneras. El tipo de organizacin
se determina en base a la informacin que se necesita accesar. Aunque en general, este aspecto lo determina
el tamao de la base de datos. En el aspecto de actualizar la informacin contenida en las bases de datos, la
organizacin es un punto muy importante para evitar inconsistencias, por ejemplo, cmo podra manejarse la
tupla padres(adn, guillermo) con la informacin mostrada en la Figura 4.13?.
Otro punto que vale resaltar con esta informacin es que las definiciones unarias hombre y mujer pueden
verse como declaraciones tipo. Resulta fcil agregar otras declaraciones de este tipo para el dominio de
personas, por ejemplo:
persona(X) mujer(X).
persona(X) hombre(X).
Asimismo, ahora resulta posible agregarle al cuerpo de cualquier clusula de la base de datos mostrada en la
Figura 4.13, argumentos tales como:
142
padres(adn, X).
Ante esta consulta, el sistema Prolog tendra que responder: X = guillermo y X = bety, o de forma alterna
desplegar la relacin unaria {guillermo, bety}. De igual manera, ante la consulta:
madre(X, Y).
Prolog genera las siguientes respuestas:
X = ana, Y = guillermo
X = ana, Y = bety
X = caty,
Y = donaldo
X = diana,
Y = erik
gusta(X, Y) beb(Y).
beb(mary).
Informalmente las dos clusulas dicen que A todos les gustan los bebs y Mary es una beb. Considere el
resultado de la consulta Hay alguno que es gustado por alguien?. En otras palabras ante la clusula
objetivo:
gusta(X, Y).
143
Prolog responder con Y=mary y X sin una correspondencia. Esto se puede interpretar como A todos les
gusta Mary, pero, qu significa esto en trminos de una base de datos?. Una solucin a este problema
consiste en declarar un predicado de tipo y ampliar el objetivo integrando llamadas a este nuevo predicado,
como se muestra:
mundo cerrado o CWA . Otra suposicin, generalmente adoptada en bases de datos deductivas es la
22
conocida como suposicin de dominio cerrado (DCA) , la cul declara que Los nicos individuos existentes
son los indicados en la base de datos. En trminos de lenguaje lgico esto puede ser expresado mediante el
siguiente axioma adicional:
donde
son todas las constantes que se encuentran en la base de datos. Con este axioma, la
supone que la base de datos no contiene funciones, solamente un nmero finito de constantes.
22
144
bloque se puede mover a la vez. Asimismo, las acciones a realizar por el brazo robtico son del tipo: pon el
bloque A en la mesa, coloca el bloque A encima de B, coloca el bloque C en la mesa, etc. [Guerra, 2009].
Las diferentes configuraciones que pueden formarse con los tres bloques determinan el espacio de estados
(ver Figura 4.15). El problema de encontrar un plan para acomodar los cubos de un estado inicial a un estado
final (ver Figura 4.14), equivale a encontrar un camino en el grafo descrito en la Figura 4.15.
Como el problema se va a resolver bajo el paradigma de programacin declarativa, en este caso, el lenguaje a
utilizar es Prolog, entonces el asunto consiste en cmo representar este grafo en un lenguaje lgico como
Prolog?.
145
El espacio de estados se representar por la relacin s(X, Y) la cul ser verdadera si existe un movimiento
vlido del nodo X al nodo Y (sucesor de X). Asimsimo, si existe un costo asociado a las acciones, ste puede
representarse por un tercer argumento de s, s(X, Y, Costo). Esta relacin puede ser especificada por un
conjunto de hechos. La relacin s es normalmente definida intencionalmente mediante un conjunto de reglas
que obtienen el sucesor de un nodo dado. Otro aspecto a considerar tiene que ver con la representacin de
los estados del problema, los nodos. La representacin debe de ser compacta y permitir el clculo la eficiente
de los nodos sucesores y posiblemente del costo asociado a las acciones. Tomemos como ejemplo el mundo
de los bloques (ver Figura 4.14). Cada estado del problema puede ser representado por una lista de pilas.
Cada pila a su vez puede ser representada por una lista de bloques. El tope de cada pila es el primer
elemento de cada lista de bloques. La pila vaca est representada por la lista vaca. En ese sentido, el
estado inicial mostrado en la Figura 4.14 es la lista: [[c, b, a], [ ], [ ]] (suponemos, que en la mesa slo hay
espacio para 3 pilas de bloques). Una meta es cualquier arreglo con los bloques en el orden deseado. Existen
tres soluciones posibles en este caso:
1.
2.
[[ ], [a, b, c]]
3.
[[ ], [ ], [a, b, c]]
La relacin sucesor puede programarse de acuerdo a la siguiente regla: el Estado2 es sucesor de Estado1 si
hay dos pilas: Pila1 y Pila2 en Estado1, y el tope de la pila Pila1 puede moverse a Pila2. Esto en Prolog se
representa como:
S(Pilas, [Pila1, [Tope1 | Pila2] | OtrasPilas])
:- quitar ([Tope1 | Pila1], Pilas, Pilas1), quitar(Pila2, Pilas1, OtrasPilas).
quitar(X, [X | Ys], Ys).
quitar (X, [Y | Ys], [Y | Ys1])
:-quitar (X, Ys, Ys1).
En este segmento de programa, la relacin s nos permite verificar si un nodo es sucesor de otro, por ejemplo:
?- s ([ [b], [a, c], [ ]], [ [ ], [b, a, c], [ ]).
Yes
Asimismo, para representar los estados objetivo utilizamos:
Meta (Estado) :- member ([a, b, c], Estado).
146
Un predicado solucin se aplica para plantear las metas, por ejemplo: solucin([[c, a, b], [ ], [ ]], Sol). En este
caso, la solucin se encontrar realizando una bsqueda en el espacio de estados.
Existen varias tcnicas aplicadas para encontrar un camino (bsqueda de soluciones) en el espacio de
estados representado por un grafo. En este ejemplo se va a mostrar una de ellas: bsqueda primero en
profundidad. Para encontrar un camino solucin Sol, de un nodo N a un nodo meta, hacer lo siguiente:
Si existe un nodo sucesor N1, tal que existe un camino Sol1 de N1 al nodo objetivo, entonces Sol =
[N|Sol1].
147
En este mismo sentido, con el fin de evitar caer en bsquedas infinitas sobre ramas no cclicas, es posible
establecer un limite a la profundidad de la bsqueda. Para ello definiremos primeroProfundidad2, donde el
tercer argumento es la profundidad mxima considerada en la bsqueda.
solucion3 (Nodo, Sol, MaxProf) :- primeroProfundidad2 (Nodo, Sol, MaxProf).
primeroProfundidad2 (Nodo, [Nodo1], _) :- meta (Nodo).
primeroProfundidad2 (Nodo, [Nodo | Sol], MaxProf) :- MaxProf > 0,
s (Nodo, Nodo1),
Max1 is MaxProf 1,
primeroProfundidad2 (Nodo1, Sol, Max1).
?-
148
Con la finalidad de mostrar el manejo de operaciones aritmticas en Prolog, se van a proporcionar los
siguientes ejemplos:
Ejemplo 4.9.1. Desarrolle un programa para que realice la multiplicacin de matrices de enteros. Obviamente
el programa debe verificar que el nmero de columnas de la primer matriz sea igual a las filas de la segunda.
Se van a utilizar las libreras de SWI-Prolog. El cdigo del programa se muestra en la Figura 4.16 (a) y en la
Figura 4.16 (b) se observa un ejemplo de su ejecucin.
% SWI-Prolog has transpose/2 in its clpfd library
:- use_module(library(clpfd)).
(b)
Figura 4.16. Programa para multiplicar matrices: (a) cdigo, M1 y M2 representan las matrices a multiplicar,
M3 es la matriz resultante, (b) ejemplo de aplicacin.
Listas
En Prolog, la estructura de una lista est predefinida como una estructura re- cursiva lineal, y a diferencia de
149
otros lenguajes de programacin, sus elementos pueden ser heterogneos, ya que en Prolog no existe una
comprobacin de tipos. Bsicamente, una lista es una estructura con dos argumentos: la cabeza de la lista,
que es un componente de la misma, y la cola, que es otra lista con el resto de los elementos. Normalmente,
existen dos notaciones para listas: 1) una notacin prefija: .(Cabeza, Cola) y 2) una notacin infija [Cabeza |
Cola], la cul resulta ms cmoda de utilizar. La representacin de la lista vaca es a travs de [ ].
De esta manera, la lista (a, b, c, d) se puede escribir en Prolog de las dos formas siguientes:
.(a, .(b, .(c, .(d .[ ] ) ) ) )
y [a | [b | [c | [d | [ ] ] ] ] ].
Sin embargo, la segunda representacin se puede escribir de forma simplificada como [a, b, c, d]. De esta
manera, en la lista [a, b, c, d], la cabeza es el elemento [a] mientras que la cola est formada por los elementos
[b, c, d]. Asimismo, es posible escribir varios elementos a la izquierda del smbolo |, separados por comas, as
como cualquier combinacin de estas dos formas. Por lo tanto, la lista (a, b, c, d) se puede escribir de
cualquiera de las formas siguientes:
[a | [b, c, d]]
Es importante observar, que en las notaciones para listas, el segundo argumento debe ser siempre una lista,
pues aunque Prolog no comprueba tipos, s es capaz de distinguir las listas de otra clase de trminos. Por
ejemplo, notaciones como las siguientes: .(a, b)
como una constante (tomo), no como una lista. Asimismo, hay que tener en cuenta que los elementos de
una lista pueden ser al mismo tiempo otras listas, as por ejemplo, [a, b, [c, d, e] ] es una lista con tres
componentes, el ltimo de los cuales, es a su vez una lista, mientras que [a, b | [c, d, e] ] es una lista con cinco
componentes, todos constantes. Otros ejemplos de listas son:
i) [[juan, luis, alfredo], [juana, luisa, alfreda]],
ii) [bici, moto, auto, helicptero].
Algunos predicados que se utilizan en el manejo de las listas son los siguientes:
Member. Este predicado nos permite saber si un elemento forma parte de la lista, por ejemplo:
?- member (1, [3, 2, 8, 7, 1, 9])
yes
Si el primer elemento de la lista es una variable, entonces se pueden listar los elemntos de la lista uno a uno,
por ejemplo:
?- member(X, [1, 2, 3]).
150
Yes, X=1;
Yes, X=2;
Yes, X=3;
No
Append. Con este predicado se permiten las siguientes operaciones: i) la unificacin de dos listas en una, ii)
verificar si una lista se forma por la unin de otras dos, iii) verificar que elementos no estn instanciados en
una lista, y iv) obtener las posibles combinaciones de una lista, por ejemplo:
?- append ([1, 2, 3], [4, 5], X).
X = [1, 2, 3, 4, 5]
?- append ([a, b], [c], [a, b, c].
Yes
? append (X, [3, 4], [1, 2, 3, 4]).
Yes, X= [1, 2]
? append [X, Y [1, 2, 3, 4]].
X= [ ], Y=[1, 2, 3, 4]
X=[1], Y=[2, 3, 4]
X=[1, 2, 3, 4], Y=[ ]
Unificacin. Es posible realizar la unificacin entre dos listas a travs del operador de iguladad (=). Por
ejemplo, si [
Listar los elementos de una lista. En ocasiones se requiere trabajar con los elementos de la lista de una
forma separada, uno a uno, y de izquierda a derecha. Esto puede realizarse al separar la lista en dos partes:
Cabeza (head) y Cola (tail) y procesar cada una de forma separada de manera recursiva. Sin emabrgo, el
predicado writeall definido como:
writeall ([ ]).
writeall ([H | T]) :- write(H), nl, writeall (T).
Permite escribir los elemntos de una lista, uno por lnea. En este proceso, la segunda clusula separa la lista
en Cabeza (H) y Cola (T), escribe H, nueva lnea y despus el cuerpo de la lista, de forma recursiva. La
primera clusula se asegura que la evaluacin termine cuando ya no haya ms elementos en la lista. Por
ejemplo:
?- wr
[ f E
]]
Alfa
Esta es una cadena
25
151
[1, 2, 3]
yes
Longitud de una lista. Para determinar la longitud de una lista, Prolog utiliza el predicado length. Este
predicado requiere dos argumentos, el primero debes ser una lista. Si el segundo elemento es una variable,
entonces, sta contiene el nmero de elementos de la lista. Por ejemplo:
?- length ([a, b, c, d], X).
X=4
?- length ([[a, b, c], [d, e, f], g, h, i]], Y).
Y=3
Si el segundo elemento en vez de ser una variable es un nmero, su valor se compara con la longitud de la
lista:
?- length ([a, b, c], 3).
Yes
Lista en orden inverso. El predicado reverse hace que los elementos de una lista sean mostrados en orden
inverso. Este predicado toma dos argumentos. Si el primer argumento es una lista y el segundo una variable
(o viceversa), la variable ser asignada con los elementos de la lista escitos en orden inverso. Por ejemplo:
?- reverse([1, 2, 3, 4], X).
X = [4, 3, 2, 1]
?- reverse ([1, 2, 3], [3, 2, 1]).
Yes
Al igual que los predicados descritos con anterioridad, existen ms relacionados con el procesamiento de
listas, pero una seccin es insuficiente para mostrarlos todos. A continuacin se mostrarn algunos programas
realizados en Prolog-SWI y relacionados con el manejo de listas.
Ejemplo 4.9.2. Escriba un programa que obtenga el mayor elemento de una lista de enteros. En la Figura
4.17 se muestra el cdigo escrito en Prolog y una muestra de la ejecucin de este programa.
152
(a)
(b)
Figura 4.17. En (a) se muestra el cdigo en Prolog para obtener el nmero mayor (predicados maximo y
maxim) de una lista de enteros, (b) muestra de su ejecucin.
Ejemplo 4.9.3. Realizar un programa en Prolog que cuente el nmero de veces que un nmero dado se
encuentra en una lista. En la Figura 4.18 se muestra tanto el cdigo como una muestra de la ejecucin de este
153
programa.
(a)
(b)
Figura 4.18. En la imagen (a) se muestra el cdigo en lenguaje Prolog que realice el conteo de las
repeticiones de un nmero en una lista, en (b) se ilustran tres ejemplos de la ejecucin de este programa, el
nmero que inicia la lista es el que se va a biscar y la variable A es donde se almacena el nmero de
ocurrencias de este nmero en la lista.
Ejemplo 4.9.4. Cree un programa en Prolog para quitar los elementos repetidos de una lista, y genere una
nueva sin elementos repetidos. Por ejemplo, la lista [a,b,h,j] se obtendra como resultado de eliminar los
154
(a)
(b)
Figura 4.19. En (a) se muestra el cdigo para eliminar elementos repetidos en una lista, en (b) se ilustran dos
ejemplos de su aplicacin, M es la variable que almacena la nueva lista.
rboles
Un rbol es una estructura con una definicin puramente recursiva, ya que se puede considerar como el
elemento raz cuyos hijos son, a su vez, rboles. Si el rbol tiene nicamente dos hijos se denomina rbol
binario. Este modelo especfico de rbol se utiliza mucho para resolver gran cantidad de problemas en
aspectos de programacin.
Un rbol se puede considerar, a su vez, un caso particular de grafo, donde todos los caminos son acclicos.
155
Una estructura de datos en PROLOG se puede visualizar mediante un rbol. Por ejemplo el predicado
fecha(17, enero, 1960), se puede visualizar a travs del rbol mostrado en la Figura 4.20 (a). Asimismo, la
expresin aritmtica a + b * c se representa a travs de un rbol como se muestra en la Figura 4.20 (b).
Figura 4.20. En (a) se muestra la representacin de la funcin fecha(17, enero, 1960), mientras que en (b) se
ilustra la representacin de la expresin a + b * c.
tree(LT, Root, RT) donde LT y RT son las ramas izquierda y derecha de la raz (Root).
En este caso, la funcin nil consiste en un valor constante (funcin con aridad 0), mientras que tree es una
funcin con aridad 3. En las siguientes lneas se muestran dos formas para definir una estructura tipo rbol en
Prolog:
tree(nil, 2, nil)
Para determinar la altura de un rbol binario se uede utilizar un predicado como el descrito en las siguientes
lneas:
156
un rbol vaco, o
un rbol cuyas ramas tengan la misma altura y estn balanceadas, es decir, si cumplen con lo
siguiente:
balanced(nil).
balanced(tree(LT, _, RT)) :height(LT, H), height(RT, H),
balanced(LT), balanced(RT).
en diversos
anteriores, la tcnica de resolucin que utiliza Prolog se basa en la construccin de un rbol de bsqueda de
soluciones. Por lo tanto, podemos concluir que el conocimiento y manejo de esta estructura es fundamental
para este tipo de metodologa declarativa. Cabe mencionar que las listas son un caso particular de una
estructura tipo rbol. Sin embargo, un rbol tambin se puede representar a travs de listas. Aunque la forma
ms empleada para construir un rbol es el uso de medios recursivos.
Ejemplo 4.9.5. En este ejemplo se va a mostrar el uso de una estructura tipo rbol con el fin de realizar el
anlisis de una oracin en espaol. Una oracin puede representarse por las siguientes partes: Sujeto
(Artculo, Sustantivo) y predicado (Verbo, Complemento). Por ejemplo, la oracin: El tigre como carne, se
describe como:
oracin (sujeto (artculo (El), sustantivo (tigre)), predicado (verbo (come), complemento (carne)))
La representacin a travs de un rbol de esta oracin se muestra en la Figura 4.21. Como se observa, el uso
de un predicado con dos argumentos en el caso de rbol binario o N argumentos en el caso de rbol N-ario es
una forma sencilla de representar un rbol.
157
Mediante el uso de funciones recursivas del tipo: arbol(nodo, hijoizq, hijoder), donde hijoizq e hijoder son
tambin rboles, podemos representar un rbol binario. El rbol vaco se representa a travs del hecho: vacio.
La estructura del rbol se describe en la lnea siguiente:
arbol(1,arbol(2,arbol(4,vacio,vacio),arbol(5,vacio,vacio)),arbol(3,vacio,vacio))
En la Figura 4.22 se muestra el programa realizado en Prolog para representar el rbol descrito en la Figura
4.21. Se puede observar el uso de secciones donde se definen los componentes: dominio, predicados,
clusulas y objetivos.
domains
arbol= nodo(integer, arbol, arbol); vacio
lista= integer*
predicates
concatenar(lista, lista, lista)
preorden(arbol, lista)
inorden(arbol, lista)
postorden(arbol, lista)
158
clauses
concatenar([],[],[]):-!.
concatenar([],L2,L2):-!.
concatenar(L1,[],L1):-!.
concatenar([X|Y],L2,[X|Aux]):-concatenar(Y,L2,Aux).
preorden(vacio,[]):-!.
preorden(nodo(X,Izq,Der),[X|L]):-preorden(Izq,L1),
preorden(Der,L2),
concatenar(L1,L2,L).
inorden(vacio,[]):-!.
inorden(nodo(X,Izq,Der),L):-inorden(Izq,L1),
inorden(Der,L2),
concatenar(L1,[X|L2],L).
postorden(vacio,[]):-!.
postorden(nodo(X,Izq,Der),L):-postorden(Izq,L1),
postorden(Der,L2),
concatenar(L1,L2,L3),
concatenar(L3,[X],L).
goal
inorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),
nodo(3,vacio,vacio)), L1),
preorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),
nodo(3,vacio,vacio)), L2),
postorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),
nodo(3,vacio,vacio)), L3).
Figura 4.22. Programa escrito en Prolog que representa la estructura tipo rbol de la oracin: El tigre come
carne.
159
utilizando el conjunto de reglas. Usualmente el proceso de bsqueda sigue una secuencia establecida, sin
embargo, en ocasiones esta secuencia puede ser controlada. La explicacin de este punto se dar
continuacin. La explicacin se basa un problema de bsqueda recursiva en el espacio de estados [Luger,
2009]. Se trata de realizar movimientos en un tablero similares a los del
direccin (horizontal o vertical). De esta forma, el jugador puede realizar un movimiento del cuadro 1 al cuadro
6 o al cuadro 8 (ver Figura 4.23).
Figura 4.23. Tablero de ajedrez de 3x3 y los movimientos vlidos expresados como hechos Prolog.
En este caso, la consulta es con respecto a obtener una secuencia de movimientos vlidos para ir de un
cuadro inicial a uno final. Los movimientos vlidos se muestran en la Figura 4.23 con el conjunto de hechos
mover. El predicado path, mostrado abajo, define un algoritmo para obtener una trayectoria entre dos
argumentos: el estado actual X, y el estado que queremos alcanzar Y. Para hacer esto primero se verifica si
estamos donde queremos estar, path(Z, Z), si no es as, buscar un nuevo estado, W, donde movernos.
path (Z, Z).
path (X, Y) :- mover(X, W), not( been(W), assert(been(W)), path(W, Y).
La bsqueda establecida por path es recursiva, primero en profundidad, y recorre el grafo de izquierda a
derecha. El predicado assert coloca sus argumnetos en la seccin de especificaciones de la base de datos.
Por su parte, el predicado been se utiliza para grabar cada estado visitado, y not(been(X)) determina para
160
cada nuevo estado encontrado, si ya ha sido visitado, si es as, evita el ciclo en la bsqueda. De hecho, el uso
de been consiste en modificar la ejecucin del programa y controlar la bsqueda.
Una manera ms sofisticada para controlar la bsqueda, consiste en crear una lista que lleve un registro de
los estados visitados. Al crear esta lista, se agrega como el tercer argumento del predicado path, ver abajo su
descripcin. En este caso, cuando se encuentra un nuevo estado, el predicado member verifica si este estado
ya ha sido visitado. Si el estado no forma parte de la lista (no es miembro), se agrega en el orden en que fue
encontrado, de tal forma, que, los estados ms recientes se encuentran en el tope de la lista. En caso
contrario, el predicado path retrocede, buscando estados no visitados. La siguiente clusula implementa la
bsqueda primero en profundidad y de izquierda a derecha, con retroceso, en un grafo.
path (Z, Z, L).
path (X, Y, L) :- mover(X, Z), not( member(Z, L)), path(Z, Y, [Z | L]).
A continuacin, en la Figura 4.24, se muestra una solucin al problema del ajedrez en un tablero de 3x3.
Con referencia al programa, la llamada recursiva a path regresa un yes cada vez que es llamada. Esta
llamada consiste en un shell o en una estructura general para el control de la bsqueda en un grafo: en path
(X, Y, L), X indica el estado actual, Y el estado objetivo.
1.
2.
is path(Z, Z,
is path(X, Y,
move(X,
path(Z,
L).
L) :Z), not(member(Z, L)),
Y, [Z | L]).
?-
path(1, 3, [1]).
path(1, 3, [1]) attempts to match 1. fail 1<>3.
path(1, 3, [1]) matches 2. X=1, Y=3, L=[1]
move(1, Z) matches, Z=6,
not(member(6,[1]))=true,
call path(6, 3, [6,1]
path(6, 3, [6,1]) trys to match 1. fail 6<>3.
path(6, 3, [6,1]) calls 2. X=6, Y=3, L=[6, 1].
move(6, Z) matches, Z=7,
not(member(7, [6,1]))=true,
call path(7, 3, [7,6,1])
path(7, 3, [7,6,1]) trys to match 1. fail 7<>3.
path(7, 3, [7,6,1]) in 2: X=7, Y=3, L=[7,6,1].
move(7, Z) matches Z=6,
not(member(6, [7,6,1])) fails, backtrack!
move(7, Z) matches, Z = 2,
not(member(2, [7,6,1])) is true
call path(2, 3, [2,7,6,1])
path(2, 3, [2,7,6,1]) attempts 1, fail, 2 <> 3.
path matches 2, X in 2: Y is 3, L is [2, 7, 6, 1]
move(2, Z) matches, Z=7,
not(member(...)) fails, backtrack!
move(2, Z) matches Z=9,
not(member(...)) is true, call path(9, 3, [9,2,7,6,1])
path(9, 3, [9,2,7,6,1]) fails 1, 9<>3.36
path matches 2, X=9, Y=3, L=[9,2,7,6,1]
move(9, Z) matches Z = 4,
161
not(member(...)) is true,
call path(4, 3, [4,9,2,7,6,1])
path(4, 3, [4,9,2,7,6,1])fails 1, 4<>3.
path matches 2, X=4, Y=3, L is [4,9,2,7,6,1]
move(4, Z) matches Z = 3,
not(member(...)) true,
call path(3, 3, [3,4,9,2,7,6,1])
path(3, 3, [3,4,9,2,7,6,1]) attempts 1, true, 3=3
Figura 4.24. Programa Prolog que resuelve el problema de encontrar trayectorias vlidas en un tablero de 3x3.
Cuando X y Y son iguales, la recursividad finaliza. L es la lista de estados en la trayectoria actual, y conforme
cada nuevo estado Z es encontrado, con una llamada a move(X, Z), ste es colocado en la lista [Z | L]. El
estado de la lista se checa usando not(member(Z, L)).
Uso de cut para controlar la bsqueda en Prolog. El predicado cut se representa por un signo de
exclamacin !. La sintxis para este predicado es equivalente a la de un objetivo sin argumentos. Cut tiene
varios efectos colaterales: primero, cuando se encuentra de manera inicial siempre tiene xito, y segundo, si
falla en el retroceso, causa que la clusula en la que se encuentra falle. Para mostrar un ejemplo de su
operacin considere el predicado path2, descrito a continuacin:
path2(X, Y) :- mover(X, Z), mover(Z, Y).
Como se observa, hay dos predicados mover para determinar la trayectoria entre X y Y a travs de un estado
intermedio Z. Para este caso, supongamos los siguientes hechos en la base de datos:
move(1, 8).
move(6, 7).
move(6, 1).
move(8, 3).
move(8, 1).
Ante la consulta:
?- path2(1, W).
El intrprete encuentra encuentra las siguientes cuatro trayectorias:
W=7
W=1
W=3
W=1
No
162
Sin emabrgo, cuando el predicado path2 es alterado al agregar cut, solamente se muestran dos resultados:
path2(X, Y) :- move(X, Z), !, move(Z, Y)
?- path2(1, W).
W=7
W=1
No
La respuesta No, ocurre porque la variable Z toma solamente un valor (el primer valor es asignado), por decir
6. Una vez que el primer objetivo resulta exitoso, Z es asignada a 6 y se encuentra el cut. Este evento inhibe
el retroceso usando el primer sobobjetivo y evitando cualquier asignacin adicional para la variable Z. Existen
varias justificaciones para el uso de cut en programacin lgica. Primero, como lo mostr el simple ejemplo
anterior, permite al programador controlar el proceso de bsqueda en un grafo.
exhaustiva no es requerida, el rbol puede ser podado de forma explcita en un punto. Esto permite al cdigo
en Prolog manejar la llamada a funciones, cuando un conjunto de valores es retornado por un predicado (o
conjunto de predicados) y el cut es encontrado, el intrprete ya no busca ms unificaciones. De esta forma, si
este conjunto de valores no conducen a una solucin, no se buscan valores adicionales.
Un segundo uso del predicado cut consiste en controlar las llamadas recursivas. Por ejemplo, en la llamada a
path:
path(Z, Z, L).
path(X, Z, L) :- move(X, Y), not(member (Y, L)), path(Y, Z, [Y | L], !.
La adicin de cut significa que (a lo ms) una solucin se genera en el proceso de bsqueda en un grafo. Esta
solucin se produce debido a que ocurren soluciones adicionales despus que la clusula path(Z, Z, L) es
satisfecha. Si el usuario pide por ms soluciones, path(Z, Z, L) falla, y la segunda llamada a path es reinvocada
para continuar la bsqueda en el grafo. Cuando el cut se coloca despus de una llamada recursiva a path, la
llamada no puede ser reingresada (se regresa, mueve hacia atrs) para continuar con una bsqueda
adicional.
Importantes efectos colaterales se realizan al cut para hacer que el programa se ejecute ms rpido y
conserve posiciones de memoria. Asimismo, cuando el cut se usa entre un predicado, los punteros en
memoria necesarios para mantener el retroceso a predicados a la izquierda del cut no son creados. Esto
ocurre, porque estos punteros nunca se van a necesitar. De esta forma, el cut genera nicamente la solucin
deseada, haciendo un uso ms eficiente de la memoria.
Adems, el cut tambin puede ser usado en la recursin para reinicializar la llamada a path en bsquedas
subsecuentes en el grafo.
163
Adems del uso del predicado cut, en Prolog se aplican otras estrategias para controlar el proceso de
bsqueda, tales como: i) Ordenar las clusulas y ii) Ordenar los trminos.
nonvar(Term)
ground(Term)
atom(Term)
atomic(Term)
nmero.
number(Term)
integer(Term)
float(Term)
compound(Term)
Es cierto si Term est instanciado a una estructura (es decir, a un trmino compuesto).
is_list(Term) Es cierto si Term est instanciado a una lista (es decir, a la lista vaca [ ] a un trmino con
fu
r y aridad 2, donde el segundo argumento es una lista. Este predicado acta como
164
Yes
?-var(X).
Yes
?- compound([a,b|Xs]).
Yes
Asimismo, la utilizacin de los predicados anteriores permite al programador chequear si una variable est
instanciada o no con la finalidad de desarrollar programas ms flexibles y eficientes. Por ejemplo, los
predicados descritos en las siguientes lneas:
abuelo(X, Y) :- nonvar(X), hombre(X), progenitor(X, Z), progenitor(Z, Y).
abuelo(X, Y) :- nonvar(Y), progenitor(Z, Y), progenitor(X, Z), hombre(X).
Pueden dar lugar al uso de expresiones como: abuelo(X, Y) :- X es abuelo de Y.
T1 \= T2
T1 == T2
ligaduras de
variables.
T1 \== T2
165
o(X, Y) :- call(Y).
4. Gestin en
PROLOG para gestionar la Base de Clusulas (BC). Es decir, meta-predicados de consulta, insercin,
eliminacin, ...,
23
dynamic +Name/+Arity, ... informa al intrprete que la definicin del(os) predicado(s) puede cambiar
durante la ejecucin (usando assert y/o retract). En otro caso se consideran estticos, sin permitir cambios
durante la ejecucin.
166
3.1. assert(Clausula) o assertz(Clausula). Inserta Clausula (que debe estar instanciada) en la BC al final de
la lista de su procedimiento. Por ejemplo, la expresin:
? assert((hijo_a(X,Y) :- madre(Y,X))).
= maria
167
fact(ave, pato).
fact(vehiculo, auto).
fact(color, blanco).
?- findall(Nombre:Valor, fact(Nombre, Valor), Lista).
Lista = [ave:pato, vehiculo:auto, color:blanco]
3.6. bagof(Instance, Goal, List). Este predicado es parecido al anterior, excepto en cmo trata las variables que
aparecen en Goal y no en Instance (conocidas como variables libres). Bagof
retroceso (backtracking) y produce una lista List para cada posible ligadura de las variables libres. Se puede
convertir una variable libre a no-libre usando ^ . Si Goal no es cierto para ningn valor de Instance, entonces
List se unifica con la lista vaca [ ]. Un ejemplo de este predicado se presenta en las lneas siguientes:
pide(alfredo, cerveza).
pide(tomas, vino).
pide(janeth, cerveza).
pide(janeth, cola).
?- bagof(P, pide(X, P), L).
X = alfredo L = [cerveza] ;
X = tomas
L = [vino];
X = janeth
L = [cerveza, cola]
%%
X es no-libre.
En este mismo sentido se consideran predicados utilizados para: i) el manejo de la E/S estndar, E/S de
caracteres, modificacin de dispositivo estndar de E/S, ii) analizar tomos, iii) manipulacin de archivos. Con
el fin de mostrar el uso de estos predicados, principalmente los de E/S, se ilustra en la Figura 4.25 un
programa escrito en Prolog y que realiza la lectura de frases y el conteo de palabras introducidas por el
usuario.
168
%,
caracter_unico(59).
%;
caracter_unico(58).
%:
caracter_unico(63).
%?
caracter_unico(33).
%!
caracter_unico(46).
%.
%
%
%
%
%
ab...z
AB...Z
012...9
'
-
Ejemplo de ejecucin:
?- leer(X), length(X,N).
|: Esta es una prueba, contaremos
|: las palabras luego.
X = [esta, es, una, prueba, (','), contaremos, las, palabras, luego, '.']
N = 10
Figura 4.25. Programa que lee frases introducidas por el usuario [Navarro, 2008].
169
Ejercicios.
4.1. Consideremos como universo del discurso el conjunto de los nmeros enteros y sean los siguientes
predicados:
p(x) : x es no negativo.
q(x) : x es par.
r(x) : x es impar.
s(x) : x es primo.
Expresar en notacin lgica las siguientes afirmaciones:
a) Existe un entero par.
b) Todo nmero entero es par o impar.
c) Todos los nmeros primos son no negativos.
d) El nico nmero primo par es el 2.
e) No todos los enteros son pares.
f) No todos los primos son impares.
g)
4.2. Para el ejemplo de aplicacin del backtracking (unificacin y resolucin), (consulte la Seccin 4.2), realice
de forma manual, y posteriormente comprube con un programa lo siguiente: 1) muestre el proceso de
retroceso y lo mostrado en pantalla, si a las relaciones familiares indicadas en la Figura 4.10, se agregan las
siguientes clusulas:
[R1]
rich (jane).
[R2]
rich (john).
[R3]
rich (gavin).
[RF1]
4.3. Dibuje un rbol similar al de la Figura 4.11, excepto que la regla computacional seleccione la literal ms a
la derecha en una clusula.
4.4. Sea P el programa formado por p(a) , y G la clusula objetivo p(x). Explique si la sustitucin vaca
resulta ser un reemplazo adecuado.
4.4. Dado el siguiente programa lgico:
p(a, b)
170
24
de los objetos
p(c,en
b) un dominio de discurso dado. El conjunto de todos estos objetos se conoce como univer so de discur so (U ). Los miembros del universo de
p(x, y) p(x, y), p(y, z)
discurso pueden
ser objetos concretos, ej., un libro, un robot, etc; abstractos, ej.,
numeros; e incluso,
cticios, ej., unicornios, etc. Un objeto es algo sobre lo cual
p(x, y) p(y, x),
queremos expresarnos. Como ejemplo, consideren el multi citado mundo de los
Y la clusula
p(a, c),se
muestre
que sien
cualquiera
de las
clusulas
es omitidade
del discurso
programa, entonces
hay
bloques
[5] que
muestra
la gura
2.2.
El universo
para talno escerefutacin.
nario
es el conjunto que incluye los cinco bloques, la el brazo robotico y la mesa:
{ a, b, c, d, e, brazo, mesa} .
4.5. En base a la Figura 4.26, indique el dominio y una posible interpretacin.
Brazo robtico
E
A
C
Mesa
Una funci o n es un tipo especial de relaci o n entre los objetos del dominio de
4.6. Con referencia
en elde
Ejemplo
4.6.2, describa
procedimiento
dar respuesta
a la consulta
discurso.
Este tipo
relaciones
mapeaelun
conjunto para
de objetos
de entrada
a un
vuela(sam)?.
objeto
u nico de salida. Por ejemplo, es posible denir la funci o n parcial sombrero
que mapea un bloque al bloque que se encuentra encima de e l, si tal bloque existe.
4.7. El principio de resolucin es la tcnica generalmente utilizada en Prolog para realizar el proceso de
Las
parejas correspondientes aesta funci o n parcial, dado el escenario mostrado en la
consulta en una base de clusulas o de datos. Sin embargo, esta tcnica tiene varias debilidades. Busque en
gura2.2
son: { (b, a), (c, d), (d, e)} . El conjunto detodas lasfunciones consideradas
la bibliografa especializada e indique las debilidades. Adems describa otra tcnica ms robusta para realizar
en
la conceptualizaci
o nadel
mundo
se conoce
la consultas
en bases de datos
travs
de un enfoque
lgico. como base funcional .
Un segundo tipo de relaci o n sobre los objetos del dominio de discurso son los
4.8.
Resolver el Diferentes
problema mostrado
en la Seccin
4.8 denirse
aplicando laen
estrategia
de bsqueda:
primero en
predicados.
predicados
pueden
el mundo
de los bloques,
ej.,
amplitud.
el predicado sobre que se cumple para dos bloques, si y solo si el primero esta inmediatamente
encima del segundo. Para la escena mostrada en la gura 2.2, sobre/ 2 se
4.9. Muestre cmo la estrategia del ordenamiento de clusulas puede servir como un mecanismo para
dene
los pares
{ (a, b), (d, c), (e, d)} . Otro predicado puede ser l ibre/ 1, que se
controlarpor
la bsqueda
en Prolog.
cumple para un bloque si y solo si e ste no tiene ningun bloque encima. Este predica4.10.
Resuelva
problema deelementos
buscar las trayectorias
tablero del de
ajedrez
de 8x8,
Seccin 4.10.usados
do
tiene
los elsiguientes
{ a, e} . en
Elunconjunto
todos
losver
predicados
en la conceptuaci o n se conoce como base relacional .
Para universos de discurso nitos, existe un lmite superior en el numero posible
de predicados n-arios que pueden ser denidos. Para un universo de discurso de
cardinalidad b (car dinalidad es el numero de elementos de un conjunto), existen bn
171
distintas n-tuplas. Cualquier predicado n-ario es un subconjunto de estas bn tuplas.
n)
(b
Por lo tanto, un predicado n-ario debecorresponder auno demaximo 2 conjuntos
posibles.
Referencias
[Apfelmus, 2016]. Heinrich Apfelmus. The Incomplete Guide of Lazy Evaluation in Haskell. Consultada en
marzo del 2016 en, https://hackhands.com/lazy-evaluation-works-haskell/
[Baral and Gelfond, 1994]. Chitta Baral and Michael Gelfond. Logic Programming and Knowledge
Representation. Journal of Logic Programming, pp. 1-93, 1994.
[Barnette and McQuain, 2000]. N. D. Barnette and W. D. McQuain. Intro Data Structures & SE. Computer
Science Dept Va Tech, Jan 2000.
[Ben-Ari, 2012]. Mordechai Ben-Ari. Mathematical Logic for Computer Science. Springer Verla London, Third
Edition, ISBN 978-1-4471-4128-0. DOI 10.1007/978-1-4471-4129-7, 364 pages, 2012.
[Bird and Wadler, 1988]. Richard Bird and Philip Wadler. Introduction to functional programming. Prentice Hall
International (UK) Ltd, Simon & Schuster International Group, UK, 1988.
[Bloss et al, 1988]. Adrienne Bloss, Paul Hudak and J. Young. Code Optimizations for Lazy Evaluation. This is
a preprint of a paper that appeared in Lisp and Symbolic Computation, Vol. 1(2), September 1988, pp. 147164. The research was supported in part by the National Science Foundation under grant DCR-8451415.
[Bramer, 2005]. Max Bramer. Logic Programming with Prolog. Department of Computer Science and Software
Engineering University of Portsmouth United Kingdom, British Library, Verlag, ISBN-10: 1-85233-938-1 ISBN13: 978-1852-33938-8, pp. 228, 2005.
[Bubenick, 1989]. Bubenik and Zwaenepoel. Performance of Optimistic Make. Performance Evaluation Review
Vol. 17. pp. 3948, 1989.
[Cuningham, 2010]. H. Conrad Cuningham. Notes on Functional Programming with Haskell. Text for
Educational Purposes, Department of Computer and Information Science, University of Mississippi, 2010.
[Daniel, 1989]. William Daniel P. Graph Reduction Without Pointers. PhD Thesis, University Of North Carolina,
Chapel Hill, December 1989.
[Davies, 2016]. Rowan Davies. Functional Programming: Graph Reduction. School of Computer Science &
Sftware Engineering, sitio web: http://teaching.csse.uwa.edu.au/units/CITS3211/, visitado en Marzo, 2016.
[Diel, 2015]. Stephen Diel. Write You a Haskell. This written work is licensed under a Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International License, MIT, 2015.
[Doland and Valet, 1994]. Jerry Doland and Jon Valet. C Style Guide. Technical Report SEL-94-003, National
Aeronautics and Space Administration, Goddard Space Fligth Center, Maryland, USA, 1994.
[Fokker, 1995]. Jeroen Fokker. Functional programming. Book provided by The Deparment of Computer
Science, Utrecht University, English edition, translated by Dennis Gruijs and Arjan van IJzendoorn, September,
1995.
172
[Friedman and Wise, 1976]. D. P. Friedman and David S. Wise. Cons should not evaluate its arguments.
Automata Languages and Programming Third International Colloquium (Edinburgh University Press), 1976.
[Garca y otros, 2002]. A. Garca-Beltrn, R. Martnez y J.A. Jan. Fundamentos de Programacin. Ed.
Bellisco, 2 Edicin, Madrid, 2002.
[Gelfond and Leone], 2002]. Michael Gelfond and Nicole Leone. Logic Programming and knowledge
representation The A-Prolog perspective. Edited by Elsevier Science, Artificial Intelligence, Vol. 38, pp. 3-38,
2002.
[Goldberg, 1996]. Benjamn Goldberg. Functional programming languages. ACM Computing Surveys, Vol. 28,
No. 1, March 1996.
[Guerra, 2009]. Alejandro Guerra Hernndez. Metodologas de Programacin I: Programacin Lgica.
Departaamneto de Inteligencia Artificial, Universidad Veracruzana, 5 Nov, 2009.
[Handerson and Morris, 1976]. Peter Henderson and James Morris. A Lazy Evaluator. Conference Record of
the Third ACM symposium on Principles of Programming Languages. January 1976.
[Honda et al, 1997]. Kohei Honda, Vasco T. Vasconcelos, and Makoto Kubo. Languaje Primitives and Type
Discipline for Structured Communication-Based Programming. Technical Report, Dept. of Computer Science,
University of Edinburgh, U.K. 1997.
[Hughes, 1989]. Jhon Hughes. Why Functional Programming Matters. In D. Turner, Editor, Research Topics in
Functional Programming, Adisson Wesleey, 1989.
[Jones, 2003]. S. Peyton Jones et al. The Haskell 98 language and libraries: The revised report. Journal of
Functional Programming, 13(1):0255, 2003.
[Kernighan and Plauger, 1974]. Brian W. Kernighan and P. J. Plauger. Programming Style: Examples and
Counterexamples. Computing Surveys, Vol. 6, No. 4, pp. 303-319, December 1974.
[Kernighan and Ritchie, 2016]. Brian W. Kernighan and Dennis M. Ritchie. The C Programming Languaje.
Libro
digital
editado
por
la
Ed.
Prentice
Hall
Software
Series,
obtenido
en
la
direccin:
173
[Lloyd, 1987]. John Lloyd. Fundations of Logic Programming, Second Edition. Springer-Verlag, 1987.
[Luger and Stubblefield, 2009]. George Luger and William Stubblefield. AI Algorithms, Data Srtuctures, and
Idioms in Prolog, Lisp, and Java. Pearson Education Inc., ISBN: 10: 0-13-607047-7, pp. 463, Boston, MA,
2009.
[Navarro, 2008]. Marissa Navarro. Apuntes de programacin Lgica. 2008.
[Nilson, 2000]. Ulf Nilsson and Jan Maluszynski. Logic, Programming and Prolog (2ED). John and Wiley Sons,
Ltd. Second Edition, 2000.
[Pasarella, 2016]. Edelmira Pasarella. Notas sobre programacin lgica. Universidad Politcnica de Catalua,
Informacin obtenida de Internet: www.cs.upc.edu/~edelmira/ApuntesLP.pdf, en Marzo del 2016.
[Pfenning,
2006].
Frank
Pfenning.
Logic
Programming.
Lecture
Notes,
obtained
in:
174
Anexo A
ARCHIVO STANDARD.PRELUDE
Informacin almacenada en el archivo standard.prelude contenido en Gofer versin 2.30a por Mark P. Jones.
9
9
8
7
7
6
5
5
4
4
3
2
0
!!
.
^
*
/, `div`, `quot`, `rem`, `mod`
+, \\
++, :
==, /=, <, <=, >=, >
`elem`, `notElem`
&&
||
$
not
not True
not False
and, or
and
or
any, all
any p
all p
otherwise
otherwise
:: Bool
= True
175
isControl c
||
c == '\DEL'
isPrint c
&&
c <= '~'
isSpace c
c == ' '
|| c == '\t'
c == '\f'
isUpper c
isLower c
=
=
c >= 'A'
c >= 'a'
&&
&&
c <= 'Z'
c <= 'z'
isAlpha c
isDigit c
isAlphanum c
=
=
=
isUpper c
c >= '0'
isAlpha c
||
&&
||
isLower c
c <= '9'
isDigit c
toUpper, toLower
|| c == '\n'
|| c == '\v'
||
toUpper c | isLower c
| otherwise
toLower c | isUpper c
| otherwise
minChar, maxChar
minChar
maxChar
|| c == '\r'
:: Char
= chr 0
= chr 255
:: [a] -> a
= x
last
last [x]
last (_:xs)
:: [a] -> a
= x
= last xs
tail
tail (_:xs)
init
init [x]
init (x:xs)
(++)
[]
++ ys
(x:xs) ++ ys
genericLength
genericLength
length
length
(!!)
(x:_) !! 0
(_:xs) !! (n+1)
176
iterate
iterate f x
repeat
repeat x
:: a -> [a]
= xs where xs = x:xs
cycle
cycle xs
copy
copy n x
nub
nub []
nub (x:xs)
reverse
reverse
elem, notElem
elem
notElem
concat
concat
transpose
transpose
-- (\\) is used to remove the first occurrence of each element in the second
-- list from the first list. It is a kind of inverse of (++) in the sense
-- that (xs ++ ys) \\ xs = ys for any finite list xs of proper values xs.
(\\)
(\\)
[a]
= []
= xs
= x : xs `del` y
177
map f []
map f (x:xs)
filter
filter _ []
filter p (x:xs)
| p x
| otherwise
------------
= []
= f x : map f xs
:: (a -> Bool) -> [a] -> [a]
= []
= x : xs'
= xs'
where xs' = filter p xs
Fold primitives: The foldl and scanl functions, variants foldl1 and
scanl1 for non-empty lists, and strict variants foldl' scanl' describe
common patterns of recursion over lists. Informally:
foldl f a [x1, x2, ..., xn]
etc...
The functions foldr, scanr and variants foldr1, scanr1 are duals of these
functions:
e.g. foldr f a xs = foldl (flip f) a (reverse xs) for finite lists xs.
foldl
:: (a -> b -> a) -> a -> [b] -> a
foldl f z []
= z
foldl f z (x:xs) = foldl f (f z x) xs
foldl1
foldl1 f (x:xs)
foldl'
:: (a -> b -> a) -> a -> [b] -> a
foldl' f a []
= a
foldl' f a (x:xs) = strict (foldl' f) (f a x) xs
scanl
scanl f q xs
scanl1
scanl1 f (x:xs)
scanl'
scanl' f q xs
foldr
:: (a -> b -> b) -> b -> [a] -> b
foldr f z []
= z
foldr f z (x:xs) = f x (foldr f z xs)
foldr1
foldr1 f [x]
foldr1 f (x:xs)
scanr
:: (a -> b -> b) -> b -> [a] -> [b]
scanr f q0 []
= [q0]
scanr f q0 (x:xs) = f x q : qs
where qs@(q:_) = scanr f q0 xs
scanr1
178
scanr1 f [x]
scanr1 f (x:xs)
= [x]
= f x q : qs
where qs@(q:_) = scanr1 f xs
::
=
=
=
drop
drop 0
xs
drop _
[]
drop (n+1) (_:xs)
::
=
=
=
splitAt
::
splitAt 0
xs
=
splitAt _
[]
=
splitAt (n+1) (x:xs) =
takeWhile
::
takeWhile p []
=
takeWhile p (x:xs)
| p x
=
| otherwise =
takeUntil
takeUntil p []
takeUntil p (x:xs)
| p x
| otherwise
x : takeWhile p xs
[]
dropWhile
::
dropWhile p []
=
dropWhile p xs@(x:xs')
| p x
=
| otherwise =
span, break
::
span p []
=
span p xs@(x:xs')
| p x
=
| otherwise =
break p
=
dropWhile p xs'
xs
-- Text processing:
179
-------
lines s
words s
unlines ls
unwords ws
lines
lines ""
lines s
words
words s
unlines
unlines
unwords
:: [String] -> String
unwords [] = []
unwords ws = foldr1 (\w s -> w ++ ' ':s) ws
-- Merging and sorting lists:
merge
:: Ord a => [a] -> [a] -> [a]
merge []
ys
= ys
merge xs
[]
= xs
merge (x:xs) (y:ys)
| x <= y
= x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys
sort
sort
insert
:: Ord a => a -> [a] -> [a]
insert x []
= [x]
insert x (y:ys)
| x <= y
= x:y:ys
| otherwise = y:insert x ys
qsort
qsort []
qsort (x:xs)
180
ANEXO B
ESTRUCTURA DE UN GRAFO Y TERMINOLOGA
La implicacin bsica en un proceso de reduccin de grafo es que los trminos de un programa, expresiones,
son representados a travs de grafos (acclicos, dirigidos), no rboles.
181
grafo que representa una expresin sustituda. Asimismo, se tiene un indicador (bandera visited) que se
activa, por el interpretador, cuando un nodo es visitado por primera vez. Se tiene un contador de referencias
(nmero de punteros a un nodo), as como un par de coordenadas (x, y) utilizados con fines de depuracin. El
nodo AppG puede, de manera temporal, ser convertido en un nodo de indireccin a travs de la bandera indir.
LamG. Representa una abstraccin, y tiene un apuntador al cuerpo de la abstraccin. Este nodo cuenta con un nico
identificador de tipo entero (binder-ID). Las variables pueden hacerse corresponder con este entero para verificar si
estn sujetas a la abstracin.
VarG. Representa una variable.
Es necesario manejar alguna terminologa relacionada con grafos, especialmente para aspectos de comparaciones. Un
nodo tiene un tipo de tres posibles: aplicaciones, abstracciones, y variables sujetas a abstracciones.
La Figura B.2 muestra una tpica representacin de un nodo. Se observa que el primer elemento consiste en
una etiqueta que contiene los valores de la aplicacin del nodo.
El valor de esta etiqueta se selecciona de tal forma que corresponda a un valor de ndice de una tabla
conteniendo rutinas de acciones. Bajo este esquema el acceso a un nodo requiere una doble operacin de
indireccin a travs de la etiqueta y una tabla de entrada
El mecanismo
182
eliminacin de etiquetas, eliminando de esta forma su interpretacin. Como un primer paso en la eliminacin
de etiquetas reemplazan las celdas que contienen valores constantes por apuntadores a nodos de indireccin.
La Figura B.3 muestra el resultado de esta accin.
Figura B.3. Ilustracin de un ejemplo donde se utilizan nodos de indireccin para constantes.
De forma similar, cualquier grafo puede ser reescrito con valores cosntantes colocados en el lado derecho de
los nodos de indireccin. Esto puede parecer demasiado trabajo, pero es de hecho, la forma en que los grafos
existen durante la ejecucin del programa. Por ejemplo, el combinador +, cuando se ejecuta, crea un nodo de
indireccin con la suma. Cabe notar que las constantes solamente se encuentran como argumentos en
combinadores de indireccin. Si estos combinadores del lado izquierdo en nodos constantes son renombrados
como combinadores LIT (valor literal), tal como se muestra en la Figura B.4, la etiqueta constante ya no se
requiere, ya que este combinador de forma implcita identifica al argumento como un valor constante. En este
sentido, todas las etiquetas pueden ser eliminadas definiendo nuevos combinadores de forma similar al LIT.
de esta forma, el grafo ilustrado en la Figura B.4 solo contempla dos tipos de etiquetas: combinadores y
apuntadores. De esta manera, resulta posible reducir de forma sustancial el costo de checar etiquetas
utilizando diferentes trucos ya estandarizados. Por ejemplo, todos los nodos y valores de punteros pueden ser
asignados a lmites de 4 bytes, esto mejora la velocidad sin importar la arquitectura del equipo.
Figura B.4. Utilizacin de nodos LIT en lugar de nodos de indireccin para constantes.
183
Bajo este esquema, el bit menos significativo de una celda puede operar como un indicador de etiqueta, la
Figura B.5 muestra el grafo reescrito de esta manera.
columna que se
Figura B.6. Ejemplo de grafo en el programa TIGRE, con nfasis en la columna izquierda.
184
Los nodos tipo combinador, tal como el nodo 3 de la Figura B.6, contienen un tipo de token que invoca a un
combinador. En algn punto durante la ejecucin del programa, el valor de este token se covierte en una
direccin y un segmento de programa es ejecutado, as que, el ensamblador utilizado por TIGRE,
simplemente almacena la direccin actual del cdigo correspondiente a la rutina del combinador en lugar de
los valores del token. De hecho, se almacena una llamada a subrutina relacionada con el cdigo del
combinador, as que, la direccin del lado derecho del nodo 3 (ver Figura B.7) ser almacenada en la pila, y el
combinador tendr todos sus argumentos apuntados a esta direcin (instruccin de retorno de la pila).
Un efecto de este esquema es que ahora solo existe un tipo de dato en el grafo: apuntadores. As que, solo
hay un tipo de nodo, y no se requieren ramas condicionales o anlisis de casos, durante la ejecucin. Todos
los nodos contienen, ya sea, punteros a otros nodos, o punteros al cdigo del combinador. La Figura B.7
ilustra la ejecucin de la expresin (( + 11) 22) aplicando este esquema. Ya que todos los valores de los
nodos (a excepcin del lado derecho de las celdas LIT) son instrucciones de llamadas a subrutinas, es posible
simplificar, simplemente diciendo que cada celda contiene un apuntador que es interpretado como una
llamada a subrutina por el motor de ejecucin de TIGRE. A un nivel de implementacin ms detallado, los
nodos del grafo pueden implementarse como una tripleta de celdas de 32 bits (ver Figura B.8). La primera
celda contiene una instruccin de llamada subrutina, la segunda y tercera celda contienen datos del lado
izquierdo y derecho del nodo.
Mientras el grafo mostrado en la Figura B.8 es simple, su operacin no es tan obvia. La evaluacin de un
programa relacionado con grafos inicia al realizar una llamada (jsb) a la subrutina del nodo raz del subgrafo.
El contador de programa de la mquina, apunta a la columna izquierda de la estructura del grafo a travs de
instrucciones jsb a los nodos siguientes .
185
Figura B.8. Implementacin de una expresin en el interpretador de grafos TIGRE, jsb es la instruccin para
llamar a subrutina utilizada por el lenguaje ensamblador VAX.
186
ANEXO C
LOCALIZACIN Y REDUCCIN DE REDEX.
Buscar un redex en un grafo (en pre-orden), consiste en, bsicamente el mismo proceso de bsqueda en un
rbol, con la excepcin de que los subgrafos previamente visitados no se vuelven a visitar. La funcin
onestepG : Gnode ref -> bool (ver Figura C.1), encuentra el siguiente redex y modifica el grafo de forma
apropiada, retornando un indicador booleano para dar a conocer que una reduccin fue realizada.
Las funciones lazy-copy y substG en onestepG son las encargadas de llevar a cabo una sustitucin, es decir,
todas las variables que tengan una correspondencia con binder-ID son reemplazadas por un puntero a rand N.
La variables SubstG indica el nmero de sustituciones realizadas.
Si rand consiste en una sola variable, es decir, el redex es (x.{B} y), la sustitucin de y es realizada y la
bandera de sustitucin subbed no es activada. Bajo esta circunstancia, compartir una variable resulta trivial y
no tienen ningn sentido. Esta situacin se maneja por substG.
La funcin onestepS consigue el efecto de reemplazar la raz del redex, convirtindolo en un nodo de
indireccin, una tcnica comn. Esto resulta ms simple que buscar en todo el grafo apuntadores al redex y
volver a apuntarlos.
187
188
189