You are on page 1of 189

Jos Antonio Montero Valverde

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.

Dr. Jos Antonio Montero Valverde

Tabla de contenido

Unidad 1 Conceptos Fundamentales

1.1 Estilos de Programacin

1.2 Evaluacin de expresiones

10

1.3 Definicin de funciones

14

1.4 Disciplina de tipos

19

1.5 Tipos de datos

20

1.6 Ejercicios

25

Unidad 2 Programacin Funcional

2.1 El tipo de datos

29

2.2 Funciones

34

2.3 Operadores

48

2.4 Aplicaciones de las listas

52

2.5 rboles

61

2.6 Ejercicios

67

Unidad 3 Evaluacin Perezosa

3.1 La estrategia de evaluacin perezosa

69

3.2 Tcnicas de programacin funcional perezosa

84

3.3 Ejercicios

100

Unidad 4 Fundamentos de la Programacin Lgica

4.1 Repaso de la lgica de primer orden

102

4.2 Unificacin y resolucin

113

4.3 Clusulas de Horn. Resolucin SLD

124

4.4 Programacin lgica con clusulas de Horn

127

4.5 Semntica de los programas lgicos

130

4.6 Representacin clausada del conocimiento

132

4.7 Consulta de una base de clusulas

137

4.8 Espacios de bsqueda

140

4.9 Programacin lgica con nmeros, listas y rboles

144

4.10 Control de bsqueda en programas lgicos

156

4.11 Manipulacin de trminos. Predicados metalgicos

160

4.12 Ejercicios

166

Referencias

168

ANEXO A

Archivo STANDARD.PRELUDE

171

ANEXO B

ESTRUCTURA DE UN GRAFO Y TERMINOLOGA

177

ANEXO C

LOCALIZACIN Y REDUCCIN DE REDEX

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.

1.1 Estilos de programacin


Un buen estilo de programacin consiste en hacer programas comprensibles, pequeos y eficientes. Aunque
hace algn tiempo, tener un buen estilo para programar se asociaba con ahorro en memoria, tiempo de
ejecucin, nmero de comentarios por lnea de cdigo, a lo que posteriormente se le agreg, usar lo menos
posible algunas estructuras de control, tal como el GOTO [Kernighan, 1974]. A decir de Kernighan y Plauger

[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)

Figura 1.3. Ilustracin de indentacin y espacios en un programa.

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.

enum DiaSemanaTipo {Lun, Mar, Mie, Jue, Vie, Sab, Dom}


enum MesTipo
{Ene, Feb, Mzo, Abr, May, Jun, Jul,
Ago, Sep, Oct, Nov, Dic}
typedef struct {
MesTipo mes;
int
dia;
DiaSemanaTipo diasemana;
int
anno;
} Fecha;
Figura 1.4. Segmento de programa mostrando la definicin de registros y tipos enumerados.

Sentencias en Bloques. Las sentencias en un bloque (grupo de instrucciones o sentencias entre { } en C)


deben tener un comentario. Este comentario debe indicar bsicamente lo que pasa dentro del bloque, tal
como la accin realizada en cada paso de un ciclo, o en la parte else de una sentencia if. En las siguientes
lneas se muestra un ejemplo en C de un bloque de instrucciones.

while (fahr <= 300)


{
/* Muestra el valor de la conversin de grados Farenheit a Centgrados mientras
farenheit sea menor que 300
*/
printf("%d\t%d\n\a", fahr, convierte (fahr));
fahr=fahr + 20;

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:

Extensibilidad: La facilidad con que se adapta el software a cambios de especificacin. Un buen


estilo de cdigo fomenta programas que no slo resuelven el problema, sino que tambin reflejan
claramente la relacin problema/solucin. Esto tiene como efecto que muchos cambios simples en el
problema reflejen de forma obvia los cambios a hacer en el programa.

Verificabilidad: la facilidad con que pueden comprobarse propiedades de un sistema. Si el estilo de


cdigo hace obvia la estructura del programa, eso ayuda a verificar que el comportamiento sea el
esperado.

Reparabilidad: la posibilidad de corregir errores sin demasiado esfuerzo.

Capacidad de evolucin: la capacidad de adaptarse a nuevas necesidades.

Comprensibilidad: la facilidad con que el programa puede ser comprendido.

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".

1.2 Evaluacin de expresiones


Las expresiones son otro de los conceptos bsicos que surgen en los primeros lenguajes de programacin. El
objetivo principal es poder expresar con facilidad clculos complejos, con una sintaxis inspirada en las
matemticas. Por ejemplo, la realizacin de un clculo sin el uso de expresiones, en este caso se aplican
solamente variables y registros, se muestra en las siguientes lneas [Urea, 2011]:
[
[

]
]

[
[

]
]

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

representa las diferentes

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

solo tienen un operando, tales como operadores de incremento,

decremento, y negacin, entre otros.


Binarios (ver Tabla 1.1). Son la clase ms comn de operadores, con ellos se realizan la mayora de las
operaciones aritmticas, lgicas y relacionales entre otras.
Ternarios (ver Tabla 1.1). En algunos lenguajes es posible combinar tres operandos a travs de operadores.
Generalmente aplica en expresiones condicionales.

Evaluacin de expresiones. En la evaluacin de una expresin, el compilador del lenguaje respectivo


generalmente se basa en la jerarqua de evaluacin de los operadores, aunque el uso de parntesis y reglas
(asociatividad, precedencia) permite alterar este orden previamente asignado. Por ejemplo, en la mayora de
los lenguajes de programacin se aplica como regla general que el operador de multiplicacin (*) tiene
prioridad sobre el operador de suma (+). Asimismo, las expresiones se evalan generalmente de izquierda a
derecha. De esta forma en la expresin

, primero se evala la operacin

resultado se suma a la variable . Si embargo, en la expresin

y posteriormente el

, el uso del parntesis altera el orden

de evaluacin, en este caso primero se evala la operacin de suma, es decir,

, y el resultado se

multiplica por c.
La jerarqua entre operadores

generalmente coincide en la mayora de los lenguajes de programacin,

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:

. Estas reglas pueden ser de dos tipos: 1)

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

Primero se evala la operacin de suma y posteriormente la multiplicacin. En la Tabla 1.1 se muestra la


jerarqua entre operadores utilizados por la mayora de los lenguajes de programacin.
Sobrecarga de operadores. En muchos lenguajes el operador + se usa para sumar enteros, sumar nmeros
reales, concatenar cadenas etc., se dice entonces que el operador + est sobrecargado (realiza mltiples
tareas). Muchos lenguajes de programacin, tales como Ada y C, permiten el uso de estos operadores. Ada
permite definir al usuario operadores sobrecargados (C++ tambin).

#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);
}

Figura 1. 6. Expresiones aritmticas expresadas en el lenguaje de programacin C.

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

forma el primer operando de la operacin OR lgica. En la segunda expresin, el operador OR

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

indicadas en la Tabla 1.1.

1.3 Definicin de funciones


Como se ha mencionado con anterioridad un programa escrito en lenguaje imperativo consiste en una
secuencia de instrucciones que le indican al procesador qu hacer. Asimismo, bajo este paradigma las
instrucciones se pueden agrupar para realizar una tarea especfica. A este agrupamiento o bloque de
instrucciones se les conoce comnmente con el nombre de funcin, subrutina o procedimiento. Para proceder
a su definicin voy a basar la explicacin de esta seccin en el lenguaje C1. El modelo de programacin en
que se basa el diseo de C es el empleo de funciones. Por esta razn, un programa en C contiene al menos
una funcin, la funcin main (ver Figuras 1.1 y 1.6). Esta funcin es particular dado que la ejecucin del
programa se inicia con las instrucciones contenidas en su interior. Una vez iniciada la ejecucin del programa,
desde la funcin main se puede llamar a otras funciones y, posiblemente, desde estas funciones a otras. Otra
particularidad de la funcin main es que se llama directamente desde el sistema operativo y no desde ninguna
otra funcin. De esta manera, un programa en C slo puede contener una funcin main [Knuth, 2004].
Bajo este enfoque, en lenguaje C se define a una funcin como un fragmento de cdigo que realiza una tarea
especfica. Las funciones pueden estar previamente declaradas en el lenguaje y formar parte de la librera del
mismo, tal como la funcin printf que imprime por la salida estndar los argumentos indicados, o bien pueden
ser definidas por el usuario [Kernighan y Ritchie, 2016].
Se puede realizar un programa sin las funciones definidas por el usuario, pero su empleo hace que el
programa sea ms eficiente, fcil de leer, modificar, verificar y mantener (ver Seccin 1.1). Las ventajas de
usar funciones en un programa son las siguientes:

Facilita el diseo descendente (top-down).

Los procedimientos dentro de ellas se pueden ejecutar varias veces.

Facilita la divisin de tareas.

Se pueden probar individualmente

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:

tipo nombre_funcion(lista de parmetros)


{cuerpo de la funcin
return valor
}
Figura 1.7 Estructura de una funcin en lenguaje C.

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.

Las funciones se pueden utilizar de los siguientes modos:

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 reciben argumentos, realizan un proceso y devuelven un valor.

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.

/* En este programa se ilustra el uso de las variables locales*/


#include <stdio.h>
void imprimeValor(); /* Se define la funcin imprimeValor() */
main() {
int contador = 0;
contador++;
printf("El valor de contador es: %d\n", contador);
imprimeValor();
printf("Ahora el valor de contador es: %d\n", contador);
return 0;
}

21

/* Declaracin de la funcin imprimeValor, la variable contador es de mbito


local */
void imprimeValor()
{
int contador = 5;
printf("El valor de contador es: %d\n", contador);
}
Salida del programa:
El valor de contador es: 1
El valor de contador es: 5
Ahora el valor de contador es: 1
Figura 1.10. Programa escrito en C que muestra el alcance de variables locales. Se agreg el resultado de la
variable contador.

/* Este programa se ilustra el uso de las variables globales como un mecanismo de


intercambio de informacin entre funciones */
#include <stdio.h>
void primeraFuncion();
void segundaFuncion();
int contador; /* Variable global */
main()
{
contador = 9;
printf ("El valor de variable contador es: %d\n", contador);
primeraFuncion();
segundaFuncion();
printf("Ahora el valor de la variable contador es: %d\n", contador);
return 0;
}
void primeraFuncion() /* Declaracin de funcin */
{
printf("En primeraFuncion, el valor de la variable contador es: %d\n", contador);
}
void otraFuncion() /* Declaracin de funcin */
{
variable++;
printf ("En la funcin segundaFuncin, el valor de la variable contador es: %d\n",
contador);
}
Los resultados mostrados al ejecutar el programa son los siguientes:
El valor de variable contador es: 9
En primeraFuncion, el valor de la variable contador es: 9
En segundaFuncionFuncion, el valor de la variable contador es: 10
Ahora el valor de la variable contador es: 10

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:

, en este caso los argumentos son datos de

tipo numrico real, a su vez

los ngulos deben ser proporcionados en radianes, b) logartmicas

, 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.

1.4 Disciplina de tipos.


Cuando se habla de la disciplina de tipos en lenguajes de programacin, se refiere a la caracterstica que
poseen los diferentes compiladores o intrpretes para verificar los diferentes tipos de datos que manejan. Los
tipos se infieren, es decir se comprueban, de forma esttica, en tiempo de compilacin. En los lenguajes de
programacin con disciplina de tipos, cada tipo representa una coleccin de valores o datos similares. El
conocer los tipos de las funciones, por ejemplo, ayuda a documentar los programas y evitar errores en tiempo
de ejecucin.
Un lenguaje tiene disciplina de tipos si los errores de tipos se detectan siempre, es necesario determinar los
tipos de todos los operandos, ya sea en tiempo de compilacin o de ejecucin. Con respecto a la disciplina de
tipos, se puede indicar lo siguiente en relacin a algunos lenguajes de programacin:

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.

1.5 Tipos de datos.


Una variable es un espacio de memoria reservado en la computadora para almacenar valores que pueden
cambiar durante la ejecucin de un programa. Los tipos que se le asignen a estas determinan cmo se
manipular la informacin contenida en ellas. Cada variable necesita un identificador que la distingue de las
dems. Un identificador vlido es una secuencia de una o ms letras, dgitos o guiones bajos, recordando que
no deben coincidir con palabras reservadas del lenguaje, deben comenzar por una letra y adems tomar en
cuenta que el lenguaje C hace diferencia entre maysculas y minsculas. Como se mencion en la Seccin
1.3 las variables presentes en un programa pueden ser de los siguientes tipos: local, global y esttica (se
tienen que inicializar en el momento en que se declaran, de manera obligatoria). Una vez recordado lo
anterior, se describirn los tipos de datos manejados en C. Los datos definen el modo en que se usa el
espacio (memoria) en los programas. Al especificar un tipo de datos, estamos indicando al compilador como
crear un espacio de almacenamiento en particular, y tambin como manipular este espacio. Un tipo de dato
define el posible rango de valores que una variable puede tomar durante la ejecucin del programa y a lo
largo de toda la vida til del propio 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].

Tabla 1.3. Tipos de datos bsicos en C.


Tipo de dato
unsigned char
char
signed char
unsigned char
short int
unsigned int
int
unsigned long
enum
long
float
double
long double
punteros
void

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

Rango de valores utilizado


0 a 255
-128 a 127
-128 a 127
0 a 255
-32768 a 32767
0 a 4,294,967,295
-2,147,483,648 a 2,147,483,647
0 a 4,294,967,295
-32,768 a 32,767
-2,147,483,648 a 2,147,483,647
3.4X10-38 a 3.4 X 10+38
308
+308
1.7 X 10- a 1.7 X10
-4932
3.4 X 10
a 3.4 X 10+4932
Direcciones de memoria
Sin valor

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

El tamao en bits asignado depende de la capacidad de la computadora y del compilador utilizado.

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

0x, por ejemplo: 0x452, 0x12FA, 0xADD, etc.

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)

Cadenas de dgitos con punto decimal, ejemplos: 123.54, 0.001, .98.

b)

En notacin cientfica, donde se incluye la letra

o E para indicar exponente, por ejemplo:

Literal es la especificacin de un valor especifico de un tipo de dato.

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

NULL (entero 0) puede ser asignado a cualquier puntero.

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

corresponde a la media y xi a cada uno de los elementos. Con la informacin anterior,

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

), y son expresadas de acuerdo a notacin que obedece


computadora acta de forma parecida a una simple

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.

En particular, soportan la definicin de tipos de datos algebraicos

mediante la enumeracin de sus constructores.


Por lo anteriormente descrito, se dice que los lenguajes funcionales, son lenguajes de programacin ms
cercanos al mundo humano (no al mundo de las computadoras) al estar basados en notaciones matemticas,
las cuales no estn influenciadas por arquitecturas concretas de computadoras.

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

puede evaluarse en cualquier momento. Esto libera al programador la

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

2.1 El tipo de datos

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).

Algunos ejemplos de operaciones realizadas con datos numricos se ilustran a continuacin:


? 12+4 -> Operacin

33

16

-> Resultado

? 5*4

-> Operacin

20

-> Resultado

? 5/2

-> Operacin

-> Resultado

Tabla 2.1. Operaciones aritmticas bsicas reconocidas por los lenguajes funcionales.
Operador

Operacin que realiza


Suma
Incremento
Resta
Decremento
Multiplicacin
Divisin
Exponenciacin
Cociente de una divisin
Residuo de una divisin

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

1 + 3), su precedencia es menor a los operadores aritmticos. Los operadores de comparacin no

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
->

? False < True

->

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

American Standard Code for Interchange of Information.

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

Cuando se manejan comparaciones entre pares de valores estos siguen la regla


cumple si

, 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

de un conjunto de objetos A conocidos como el dominio hacia un conjunto de

objetos B conocidos como el rango de tal forma que, para cualquier elemento
aplicacin de
ecuacin

a ), hace corresponder un nico elemento


, donde T es un trmino en el cual

de A, el trmino

de B. Generalmente,

(la

es definida por una

ocurre como una variable libre. El resultado de

determina al evaluar T, una vez que el parmetro formal

se

se reemplaza por .

Por ejemplo:

Define a la funcin cuadrado en el conjunto Z de nmeros enteros tal que la aplicacin


resultado

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:

Enteros, tales como 3, -7, 15 y 17345, y

Nmeros de punto flotante, tales como 1.72, 3.876e3, y -0.643.

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

la funcin raz cuadrada


la funcin seno
la funcin logaritmo base 10
la funcin exponente (e elevado a).

Asimismo, existen dos funciones primitivas que relacionan nmeros enteros y reales.

fromInteger
round

convierte un nmero entero a nmero real


redondea un nmero real a entero.

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

para nmero enteros y 10

38

para reales, y para equipos actuales a 2

31

para nmeros enteros y 10

308

para

mquinas actuales. Estas cantidades pueden variar dependiendo del equipo.


Cuando se realizan operaciones donde el resultado rebasa el mximo permitido se obtienen resultados sin
sentido.

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

<=

menor o igual que

>=

mayor o igual que

==

igual que

/=

diferente que

Algunos ejemplos del uso de estos operadores se muestran a contnuacin:


? 1+2 > 4
False
?sqrt 5.0 <= 1.3
False

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:

length [ ]indica la longitud (nmero de elementos) de la lista.

sort [ ]

ordena los elementos contenidos en la lista.

reverse [ ]

invierte el orden de la lista.

sum [ ] suma los elementos de la lista.

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:

fac n = product [1..n]

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

formulaABC a b c = [ (-b + sqrt


,(

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

se encuentran descritas en dos

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

i) ? formula2ABC 1.0 1.0 0.0


ii) [0.0, -1.0]
iii) (18 reductions, 66 cells)
a) ? formulaABC 1.0 1.0 0.0
b) [0.0, -1.0]
c) (24 reductions, 84 cells)
Las funciones sin parmetros se conocen generalmente como constantes, por ejemplo:

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)

Se indica el nombre de la funcin,

b)

Se indican los nombres de los parmetros, si es que lleva,

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

fecha d a = mes(numes d) ++ diaSemana(numeroDia d a) ++ show (numdia d)

diaSemana 0 = "dia: domingo "


diaSemana 1 = "dia: lunes "
diaSemana 2 = "dia: martes "
diaSemana 3 = "dia: miercoles "
diaSemana 4 = "dia: jueves "
diaSemana 5 = "dia: viernes "
diaSemana 6 = "dia: sabado "

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

bisiesto a = divisible a 4 && (not(divisible a 100) || divisible a 400)

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"

mes 10 = "mes: octubre\n"


mes 11 = "mes: noviembre\n"
mes 12 = "mes: diciembre\n"
numes d
| d>=1

&& d<=31

= 1

| d>=32

&& d<=59

= 2

| d>=60

&& d<=90

= 3

| d>=91

&& d<=120 = 4

| d>=121 && d<=151 = 5


| d>=152 && d<=181 = 6
| d>=182 && d<=212 = 7
| d>=213 && d<=243 = 8
| d>=244 && d<=273 = 9
| d>=274 && d<=304 = 10
| d>=305 && d<=334 = 11
| d>=335 && d<=365 = 12

numdia d

| d>=1

&& d<=31 = d - 0

| d>=32

&& d<=59 = d - 31

| d>=60

&& d<=90

| d>=91

&& d<=120 = d -90

= d -59

| d>=121 && d<=151 = d -120


| d>=152 && d<=181 = d -151

46

| d>=182 && d<=212 = d -181


| d>=213 && d<=243 = d -212
| d>=244 && d<=273 = d -243
| d>=274 && d<=304 = d -273
| d>=305 && d<=334 = d -304
| d>=335 && d<=365 = d -334
Figura 2. 1 Programa fecha.hs que recibe dos argumentos: dia (d) y ao (a) y devuelve el da de la semana y
mes en que ocurri.
Descripcin. En esta programa escrito en Gofer (ver Figura 2.1) se observa la definicin de la funcin
divisible, la cual recibe dos parmetros de tipo entero (t, n) y retorna un valor tipo booleano. Esta funcin
determina si el mes de febrero se encuentra en un ao bisiesto, con el fin de asignarle 28 o 29 das.
Asimismo, la funcin fecha, recibe dos parmetros enteros (dia y ao) y manda a llamar a las siguientes
funciones:
numdia d: esta funcin recibe como argumento el nmero de da, 1 de 365 valores, y determina el da y mes
correspondientes.
diaSemana (numeroDia d a): numeroDia devuelve un numero correspondiente al da de la semana. Esta
funcin evala y tambin determina si el mes de febrero cuenta con 29 o 28 dias, para saber si el ao es
bisiesto o no.
mes(numes d): numes devuelve un nmero el cual corresponde al mes en el que se encuentra el dia
asignado. La funcin mes recibe el nmero indicado por la funcin numes, y dependiendo del valor obtenido
asigna el nombre del mes.
En la Figura 2.2 se observa una prueba de la ejecucin del programa anterior.

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

lst = let line x (h:t) | x == h = x:'\n':h:t


| otherwise = x:h:t
f (h:t) = foldr line [last (h:t)] $ init (h:t)
f []=[]
in unlines [f lst]

Figura 2.3. Ilustracin del programa dividir.hs.

En Gofer, los elementos de una lista se identifican por un elemento colocado en la cabeza (header, h) de la

lista y el elemento en la cola (tail, t) de la misma.

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).

Figura 2.4. Muestra de la ejecucin de la funcin dividir.hs.

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.

nded :: (Eq a)=>[a]->Int


nded = length.nub

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.

diag list = putStrLn $ reduce 0 list


where reduce n [ ] =""
reduce n (h:t) = replicate n ' '
++ [h] ++ "\n" ++ reduce (n+1) t

(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

Operadores permitidos son, por ejemplo:


+

++
/\

&&
...

||
<+>

<=
?

==
:->

/=

//

@@

-*- \/

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,
(

. Sin embargo, algunos operadores

pueden ser utilizados como un operador unario de prefijo (antes del argumento), esto es, estos

operadores pueden anteceder a un argumento, por ejemplo,

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, (

, produce un resultado diferente al

obtenido en la lnea ii).

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

En el archivo prelude.standard de Gofer, se encuentra la informacin mostrada en la Tabla 2.4 en la cual se


indica el orden de precedencia (nmeros del 0 al 9, donde 9 es el de mayor precedencia) de los operadores
reconocidos por este lenguaje.
Como se puede observar en la Tabla 2.4, entre los operadores utilizados para realizar operaciones
aritmticas, el que tiene mayor precedencia es el de exponenciacin (^, con precedencia=8) siguindole los
operadores de multiplicacin ( , divisin entre reales (/), divisin entre enteros (div), remanente de una
divisn (mod), los cuales tienen un orden de precedencia igual a 7. Finalmente, se ubican los operadores
utilizados para realizar las operaciones de sumar (+) y restar (

que tienen un orden equivalente a 7. El

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

&&
||
$

la primera operacin realizada es la exponencial (

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

= 9), posteriormente se realiza la multiplicacin (7

14) , finalmente se realizan las operaciones de sumar (3+14) y restar (

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:

, da True como resultado. Los

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.

Los operadores booleanos pueden ser combinados aplicando los siguientes

operadores lgicos:

&&
||
not

Y lgico
O lgico
negacin lgica

En las siguientes lneas se muestran algunos ejemplos:


? 1>2 || 3<5

->

Instruccin

True

->

Resultado

? 1<2 && 5<2

->

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)

otra forma es el uso de casos, por ejemplo:


bisiesto a = (a mod 4 == 0), iff (a mod 100 == 0)
= (a mod 4 == 0), otherwise

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.

2.4 Aplicaciones de las listas


Las listas son tan importantes en la programacin funcional como los conjuntos lo son en varias ramas de las
matemticas. Estos conceptos guardan una gran relacin ya que la enumeracin de elementos en un conjunto
equivale a formar una lista en cierto orden de tales elementos. Sin embargo, a diferencia de los conjuntos, los
elementos de una lista pueden estar repetidos, asimismo el orden de sus elementos es importante. Con las
listas se pueden realizar mltiples operaciones, tales como: ordenar sus elementos, combinar los elementos
de dos listas, realizar operaciones aritmticas con los elementos de una o ms listas y formar una resultante,
concatenar dos o ms listas, entre otras. Una lista se puede definir como una coleccin linealmente ordenada
de valores y, al igual que las secuencias en matemticas, una lista puede contener un nmero infinito de
elementos. Una propiedad importante de las listas consiste en que todos sus elementos son del mismo tipo
(nmeros, caracteres, listas). Una lista se denota a travs de corchetes ([ ]) y comas (,), por ejemplo: [ 1, 2, 3]
indica una lista de nmeros, [Carlos, Luisa, Pedro] describe a una lista de cadenas de caracateres. La
lista vaca se indica por [ ]. Dos listas son iguales si contienen los mismos elementos y en el mismo orden, de
esta manera:

?[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]

[[1, 2], [0, 3, 4]]

::

[[Int]]

[False, True]

::

[Bool]

[3>4, a==3, p && q]

::

[Bool]

[sin, cos, tan]

::

[Float -> Float]

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

[2.5, 3.5, 4.5, 5.5]

->

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

? map cuadrado xs = [1, 2, 3, 4, 5, 6, 7, 8, 9]

->

Instruccin

[1, 4, 9, 16, 25, 36, 49, 64, 81]

->

Resultado

? filter odd xs = [1, 2, 3, 4, 5, 6, 7, 8, 9]

->

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.

Figura 2.20. Ilustracin de ejecucin de la funcin listaNum.

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

rbol son Nodo y Hoja. Los nombres de las funciones

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

(2) Usando la constante Hoja.


Los rboles pueden crearse al aplicar

las funciones constructoras en una expresin. Por ejemplo, el rbol

mostrado en la Figura 2.16 se representa con la siguiente expresin:


Nodo 4 (Nodo 2 (Nodo 1 Hoja Hoja)
(Nodo 3 Hoja Hoja)
)
(Nodo 6 (Nodo 5 Hoja Hoja)
(Nodo 7 Hoja Hoja)
)
Aplicaciones de los rboles binarios.

Un rbol binario es una estructura de datos til cuando deben

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)

Si coincide, se tiene un duplicado.

b) Si es menor, se examina el subrbol izquierdo.


c)

Si es mayor, se examina el subrbol derecho.

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

| e < x = elemArbol e izq


| e > x = elemArbol e der

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

en una lista ordenada,

la funcin insertArbol inserta un elemento en el lugar que le

corresponde en un rbol de bsqueda. Aplicando la funcin insertArbol de manera repetida, se pueden


colocar todos los elementos de una lista en un rbol, tal como se describe en las siguientes lneas:
listaArbol

::

Ord a => [a] -> Arbol a

listaArbol

foldr insertArbol Hoja

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

[]

labels (Nodo x izq der)

labels izq ++ [x]

[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

rbol. Como el valor x se coloca en su lugar

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.

A continuacin se van a mostrar la descripcin de un ejercicio relacionado con el manejo de rboles.


Ejercicio 11. En el texto se mostr la funcin buscar, la cual busca un elemento en una lista en base a un
ndice. Escriba una funcin buscarArbol que busque en un rbol con nodos que contengan parejas (ndice y
valor) un valor en base a un elemento. Elija por s mismo una estructura de datos para el rbol. En la Figura
2.17 (a) se muestra la funcin que realiza la tarea descrita y en la Figura 2.17 (b) se ilustra la ejecucin de la
funcin en Gofer.

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:

sintetise una definicin constructiva para initree.


2.5. Demuestre que cualquier funcin delete que satisface la ecuacin:
[ ]
satisfar la especificacin original de delete.
2.6. Realizar los ejercicios que vienen al final de los Captulos 3, 4 y 5 del libro, de Jeroen Fokker:
Programacin Funcional.

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.

3.1 La estrategia de evaluacin perezosa

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:

El incremento en el rendimiento al evitar clculos innecesarios, y en tratar condiciones de error al evaluar


expresiones compuestas.
Permite la manipulacin de grandes estructuras de datos, potencialmente infinitas.
La capacidad de definir estructuras de control como abstracciones, en lugar de operaciones primitivas.
La semntica de implementacin no es estricta.
Permite el manejo de modularidad as como la implementacin de paralelismo.

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

programa indica claramente que el valor de

sea almacenado en la variable , sin embargo, lo que

tenga actualmente almacenada la variable

es irrelevante, hasta que exista la necesidad de ese valor a

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:

podemos evaluar la siguiente expresin:

reemplazando el lado izquierdo de la definicin de la funcin


variable

con el lado derecho sustituyendo la

por el valor actual, se tiene que:

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.

Figura 3.1. Representacin en grafo de la expresin cuadrado (1+2).

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

corresponde a la regla mostrada en la Figura 3.2.

Figura 3.2. Regla de reduccin correspondiente a la expresin

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.3. Ilustracin de la secuencia de redex de la expresin

Figura 3.4. ilustracin de la representacin en Forma Normal para la expresin tipo lista 1: 2: 3: [ ].

Figura 3.5. Grafo que representa la expresin unos : 1 unos.

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.

Figura 3.6. Representacin en grafo de la expresin (3 + 5) : [ ].

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.7. Reglas de reduccin aplicadas a la definicin de la funcin (&&).


Ahora considere la expresin: ('H' == 'i') && ('a' == 'm'), cuya representacin se muestra en la Figura 3.8 (a).

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.

Representacin textual. Las representaciones grficas de expresiones a travs de grafos ayudan a


comprender las bases de la evaluacin perezosa. Sin embargo, para aplicaciones prcticas, el manejo de
grficas es algo incmodo, por lo que generalmente, para mostrar las reducciones se emplea una
representacin textual aplicando la sintxis de Haskell. En la representacin a travs de grafos resulta fcil
visualizar los subgrafos compartidos, en la representacin textual se tienen que indicar las expresiones
compartidas a travs de un nombre usando let.

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:

('H' == 'i') && ('a' == 'm')


=> False && ('a' == 'm')
=> False
Como se puede observar en las lneas anteriores, no se comparten subexpresiones, por lo tanto, no se

79

requiere el uso de let.


En los ejemplos siguientes la aplicacin de la estrategia de evaluacin perezosa ser mostrada con
representacin textual.
Eficiencia en tiempo y espacio. En este prrafo se mostrarn aspectos de eficiencia que la estrategia de
evaluacin perezosa emplea en relacin a recursos computacionales como el tiempo de ejecucin y espacio
utilizado en memoria.

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:

foldl (+) 0 [1..100]

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

foldl f a (x : xs) = foldl f (f a x) xs


El proceso de evaluacin para la sumatoria se muestra a continuacin:
foldl (+) 0 [1..100]
=> foldl (+) 0 (1:[2..100])
=> foldl (+) (0 + 1) [2..100]
=> foldl (+) (0 + 1) (2:[3..100])
=> foldl (+) ((0 + 1) + 2) [3..100]
=> foldl (+) ((0 + 1) + 2) (3:[4..100])
=> foldl (+) (((0 + 1) + 2) + 3) [4..100]
=>
Como se puede observar, el parmetro de acumulacin muestra un comportamiento creciente, dando lugar a
un problema de almacenamiento en memoria. La solucin a este problema consiste en asegurarnos de que el
parmetro de acumulacin est siempre en la representacin WHNF. La modificacin siguiente a la funcin
foldl realizar lo que se pretende:
foldl' :: (a -> b -> a) -> a -> [b] -> a
foldl' f a [ ]

=a

foldl' f a (x:xs) = let a' = f a x


in seq a' (foldl' f a' xs)
Hecho lo anterior, la evaluacin proceder como se muestra en las siguientes lneas:
foldl' (+) 0 [1..100]
=> foldl' (+) 0 (1:[2..100])
=> let a' = 0 + 1 in seq a' (foldl' (+) a' [2..100])
=> let a' = 1 in seq a' (foldl' (+) a' [2..100])
=> foldl' (+) 1 [2..100]
=> foldl' (+) 1 (2:[3..100])
=> let a' = 1 + 2 in seq a' (foldl' (+) a' [3..100])
=> let a' = 3 in seq a' (foldl' (+) a' [3..100])
=> foldl' (+) 3 [3..100]
De esta manera, durante el curso de la evaluacin, la expresin se mantendr con un tamao constante. Este
es un ejemplo de cmo se puede obtener un cdigo modular en Haskell al aplicar la evaluacin perezosa.

81

Cdigo Modular y Evaluacin Perezosa en Haskell. La evaluacin Perezosa es el mtodo ms


ampliamente utilizado durante la ejecucin de cdigo en un programa Haskell. Este mtodo ayuda en
desarrollar un cdigo ms simple y modular, pero, tambin puede resultar ms complicado comprender en
detale como el programa es evaluado. Uno de los aspectos principales de la evaluacin perezosa para
ayudar a mejorar el cdigo, consiste en permitir el reuso del cdigo, y al mismo tiempo mantener un buen
nivel de eficiencia. Con la finalidad de comprender como este mtodo de evaluacin puede manejar la
modularidad en el cdigo, vamos a considerar la funcin (&&) que implementa la lgica AND, cuya definicin
es:
(&&) :: Bool -> Bool -> Bool
True && x = x
False && x = False
Ya he mostrado con anterioridad, que gracias a la evaluacin perezosa, esta funcin retorna de manera
anticipada un resultado cuando el valor del primer argumeto es False. Por ejemplo, la expresin: False &&
((4*2 + 34) == 42), de forma inmediata se reduce a False y el segundo argumento no se evala. Este
comportamiento se conoce como evaluacin en corto-circuito, y tambin se encuentra en otros lenguajes de
programacin. Consideremos ahora una funcin que implementa la lgica AND y que opera no con dos
valores, sino, con una lista de valores. Una definicin recursiva de esta funcin podra ser la siguiente:
and :: [Bool] -> Bool
and [ ]

= True

and (b:bs) = b && and bs


Si aplicamos esta funcin a una lista cuyo primer elemento es False, entonces la evaluacin perezosa
proceder como se muestra en las lneas siguientes:

and [False, True, True, True]


=> False && and [True, True, True]
=> False
En otras palabras, despus de saber que el primer elemento es False, la funcin regresa un valor de False e
ignora el resto de la lista. En general, la funcin inspeccionar los primeros elementos de la lista buscando un
False y retornar este mismo valor si lo encuentra, finalizando con esto la bsqueda y trayendo por
consiguiente un ahorro sustancial en el tiempo de procesamiento.

Veamos ahora la siguiente funcin que compara dos listas:

prefix :: Eq a => [a] -> [a] -> Bool


prefix xs ys = and (zipWith (==) xs ys)
Esta regresa el valor True, siempre que alguno de los elementos de una lista resulta ser un prefijo de la otra
lista. Por cuestin de clarificar el ejemplo, no se emplea el operador de igualdad en listas (==), ya que este no

82

puede implementarse con la funcin zipWith. Cabe mencionar que la funcin zipWith compara dos listas
empleando una funcin binaria:

zipWith :: (a -> b -> c) -> [a] -> [b] -> [ c ]


zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys zipWith
f__=[]
A continuacin se muestra lo que hace la evaluacin perezosa cuando compara las cadenas Haskell y
eager:
prefix "Haskell" "eager"
=> and (zipWith (==) "Haskell " eager")
=> and ('H' == 'e' : zipWith (==) "askell" "ager")
=> and ( False

: zipWith (==) "askell" "ager")

=> False && and (zipWith (==) "askell" "ager")


=> False
Como se observa, el proceso de comparacin retorna un valor de False tan pronto comprueba que las
primeras letras son diferentes.

El punto que hay que tener presente aqu,

es que la funcin de biblioteca zipWith, es completamente

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:

enumFrom :: Integer -> [Integer]


enumFrom n = n : enumFrom (n+1)

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

=> (0,'O') : ((0+1), 'h') : [ ]


En este ejemplo la letra O se hace corresponder con la posicin 0 y la letra h con la posicin 0+1. En el
ltimo paso, zip se reduce a una lista vaca ya que el segundo argumento es la lista vaca.

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 [

aplicando de manera repetida la funcin f descrita a continuacin:

iterate :: ( -> ) ->

-> [ ]

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

= within (1e-14) $ iterate (better a) ( / 2)

de este enfoque, es que el proceso de generar y seleccionar las aproximaciones son

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:

relative eps (x:y:ys) =


if abs (x-y) <= eps * abs x then y else relative eps (
De una u otra forma, la evaluacin perezosa nos permite trabajar con listas potencialmente infinitas. Sin
embargo, muy seguido resulta conveniente pretender que las listas, son de hecho, infinitas. Despus de todo,
puede resultar muy tedioso rastrear en detalle los pasos de este mtodo, el aspecto de manejar (0 + 1) contra

85

1 en la funcin enumFrom es una pista de esta situacin.


9

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:

filenames <- getDirectoryContents "."


forM filenames (\filename -> )
En conclusin, la evaluacin perezosa es un ingrediente que permite al programador implementrar flujos de
control personalizados. Se pueden tomar estas ideas y construir un mini-lenguaje embebido en Haskell, de tal
manera que se pueda utilizar como un macro lenguaje, donde el mecanismo de la evaluacin perezosa se

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

asegure que las macros sean extendidas cuando se requieran.


Algo ms de la reusabilidad de cdigo. Suponga que se desea encontrar al elemnto ms pequeo en una
lista. Una forma consiste en ordenar primero la lista y enseguida tomar el primer elemento de la lista, con la
expresin:
minimum = head . sort
Desde luego, este proceso no es muy eficiente. Si la lista contiene n elementos, un buen algoritmo de
ordenamiento tomar al menos un timpo proporcional a O( n log n) para realizar el ordenamiento. Para
encontrar el elemento ms pequeo de una lista, con observar cada elemento dba ser suficiente, y esto solo
debera tomar un tiempo equivalente a O(n). Sin embargo, el clculo mostrado fue sin considerar la evaluacin
perezosa, sabemos que head no evala la cola de la lista, asimismo, la mayora de los algoritmos de
ordenamiento solo requieren de un tiempo lineal para encontrar al elemento ms pequeo en una lista, as
que, gracias a la evaluacin perezosa, esta implementacin estara en el mismo nivel de eficiencia que la
versin directa.

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:

sort :: Ord a => [a] -> [a]


sort [ ]

= []

sort (x:xs) = sort ls ++ [x] ++ sort rs


where
ls = filter ( < x) xs
rs = filter (>= x) xs

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

head (sort xs)


~> head (sort ls ++ [x] ++ sort rs)
~> head ((sort lls ++ [lx] ++ sort lrs) ++ [x] ++ sort rs)
~> head (((sort llls ++ [llx] ++ sort llrs) ++ [lx] ++ sort lrs) ++ )
Para realizar los pasos de reduccin anteriores, se tienen que evaluar las listas de la izquierda ls, lls y
sucesivamente en su forma normal, esto toma cerca de: O(n) + O(n/2) + O(n/4) + = O(n) pasos de
reduccin, tomando en cuenta que la evaluacin se realiza en un tiempo lineal y que cada lista tiene la mitad
de longitud que la lista previa. En este punto, la lista del lado izquierdo estar vaca:

head ((([ ] ++ [y] ++ sort ) ++ [] ++ sort ) ++ )

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.

3.2 Tcnicas de programacin funcional perezosa

El mtodo de evaluacin perezosa es la estrategia ms utilizada en la mayora de los lenguajes funcionales


con el fin de realizar la ejecucin de su cdigo. La base de los lenguajes funcionales es la funcin. Asimismo,
como ya mencion en la seccin anterior, un programa escrito en lenguaje funcional est formado por un
conjunto de expresiones, las cuales pueden a su vez formada por ms expresiones, anidamiento de
expresiones (ver Figura 3.10 (a)). El conjunto de expresiones que forman el cuerpo de un programa escrito en
un lenguaje funcional, tal como Haskell, pueden representarse en una estructura tipo rbol (ver Figuras 3.10 y
3.11).
De esta forma, una de las tcnicas utilizadas por la estrategia de evaluacin perezosa para reducir
expresiones, consiste en representarlas de manera inicial en una estructura tipo rbol, y posteriormente iniciar
el proceso de reduccin hasta alcanzar la forma normal o la forma normal reducida (WHNF) (ver Figura 3.11).
Este proceso ya se mostr en la seccin anterior.
En la Figura 3.10 (b) se ilustra en 4 pasos como se realiza la reduccin de expresiones aplicando la
evaluacin perezosa. En el paso 1, se muestra que un programa es visto como una coleccin de expresiones,
las cuales al ser representadas a travs de un rbol se pueden ver como una sola expresin, donde la
expresin ms externa es la raz del rbol, y las ms internas forman las hojas del mismo (paso 2). Las
expresiones son a su vez, reducidas de la ms interna a la ms externa y de izquierda a derecha (paso 3).

88

1. Introduction

One of the mental models for Haskell program

Haskell program

A program is a collection of expressions.

main = expaa (expab expac expad )


main

expac = expaca expacb

expaa

expad = expada expadb expadc

expaca

expab

expac

expacb

expad

expada

expadb

expadc

main = expaa (expab (expaca expacb ) (expada expadb expadc) )

1. Introduction

One of the mental models


for Haskell program
A entire
(a)program is regarded as a single expression.
The subexpression is evaluated (reduced) in some order.

(1) A program is a collection of expressions.

The evaluation is performed by replacement.

(2) A entire program is regarded as a single expression.


main = e (e (e (e e) e (e e e) ) )

(3) The subexpressions are evaluated (reduced) in some order.


f = e (e (e (e e) e (e e e) ) )

(4) The evaluation is performed by replacement.


3+2
+

5
2

This is an example of an expression reduction model for Haskell.


(b)

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.

Figura 3.11. Tcnicas de evaluacin utilizadas en Haskell [Takenobu, 2004].

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].

(\x. \y. y x) (2 + 2) (\x. x + 1)


=> (\x. \y. y x) 4 (\x. x + 1)
=> (\y. y 4) (\x. x + 1)
=> (\x. x + 1) 4
=> 4 + 1 => 5

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

let VClosure c env = eval env a in


let v = eval env b in
eval (v : env) c
Lit n -> VInt n
Prim p a b -> (evalPrim p) (eval env a) (eval env b)
evalPrim :: PrimOp -> Value -> Value -> Value
evalPrim Add (VInt a) (VInt b) = VInt (a + b)
evalPrim Mul (VInt a) (VInt b) = VInt (a * b)
En este mtodo de evaluacin, una funcin evala sus argumentos antes de que sean requeridos, es decir,
antes de que el programa inicie su ejecucin. En este caso, cada argumento se evala exactamente una vez.

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:

(\x. \y. y x) (2 + 2) (\x. x + 1)


=> (\y. y (2 + 2)) (\x. x + 1)
=> (\x. x + 1) (2 + 2)
=> (2 + 2) + 1
=> 4 + 1
=> 5
Aunque este mecanismo de evaluacin se considera no-estricto, muy pocos lenguajes de programacin lo
utilizan.

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

Cuando una expresin no es evaluada se representa por trunks.

92

necesita

recalcular el valor de una expresin. Este mecanismo de evaluacin garantiza los siguientes

aspectos:

La funcin se evala si el resultado es requerido por alguna funcin,

La funcin nunca se evala ms de una vez, evaluacin de orden aplicativa.

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.

fib m n = m : (fib n (m + n))


getIt [ ] _ = 0
getIt (x:xs) 1 = x
getIt (x:xs) n = getIt xs (n-1)
getN n = getIt (fib 0 1) n
la funcin getit expande la lista producida por fib solo las veces que sean necesarias para leer el n-simo
elemento. Por ejemplo, en las lneas siguientes se muestra una secuencia de llamadas que calculan el 4 o
elemento de la secuencia de Fibonacci:
getIt (fib 0 1) 4 = getIt (0 : fib 1 1) 4
getIt (fib 1 1) 3 = getIt (1 : fib 1 2) 3
getIt (fib 1 2) 2 = getIt (1 : fib 2 3) 2
getIt (fib 2 3) 1 = getIt (2 : fib 3 5) 1
=2

Correspondencia de patrones (pattern matching). Otra estrategia de evaluacin generalmente utilizada en


programacin funcional, consiste en evaluar solo lo necesario y cuando sea necesario (ver Figura 3.11). Un
mtodo que realiza lo anterior es el conocido como: Correspondencia de patrones. En base a lo descrito surge
la siguiente pregunta: cundo es necesario evaluar una expresin?. Para responder esta pregunta considere
las lneas de programa mostradas a continuacin:

f1 :: Maybe a -> [Maybe a]


f1 m = [m, m]
f2 :: Maybe a -> [a]
f2 Nothing = [ ]
f2 (Just x) = [x]
En este segmento de programa, tanto f1 como f2 utilizan (cada una) su propio argumento. Sin embargo, existe
una gran diferencia entre ellas. Aunque f1 tiene como argumento a m, no necesita saber nada acerca del
mismo. El argumento m puede permanecer sin ser evaluado, y la expresin sin evaluar, simplemente es
colocada en una lista. Dicho de otra forma, el resultado, e, de f1 no depende de e como condicin. Por otro
lado, f2, necesita saber algo acerca de su argumento para poder continuar, fue utilizado Nothing o Just? (ver

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

(1) Evaluation by pattern-matching

resultado de f2 depende de e como condicin.


pattern-matching in case expression

forcing
(drive the evaluation of the thunk)

case ds of
x:xs -> f x xs
[]

-> False

pattern-matching in function definition


f Just _

forcing
(drive the evaluation of the thunk)

= True

f Nothing = False

4. Evaluation

(a)

(1) Evaluation by pattern-matching

Strict patterns drive the evaluation

Referencesthe
: [H1]
Ch.3, [D2], [D1], [H5], [W1]
Lazy patterns postpone
evaluation.

case expression

let binding pattern

case ds of

let (x:xs) = fun args

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

Otro aspecto a considerar, es que los thunks

References : [H1] Ch.3, [D2], [D1], [H5], [W1], [H8] Ch.4


son evaluados
solo lo suficiente para permitir la continuacin

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:

Las expresiones se evalan cuando haya una correspondencia entre patrones.

solamente lo necesario para proceder a la correspondencia, no ms!!.

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 _ [ ]

= []

take n (x:xs) = x : take (n-1) xs


La evaluacin paso a paso de la expresin take 3 (repeat 7) se muestra a continuacin:
take 3 (repeat 7)
{ 3 <= 0 es False, por lo tanto procedemos con la segunda clusula la cual necesita corresponder con el
segundo argumento. Asi que debemos expandir
repeat 7. }
= take 3 (7 : repeat 7)
{ la segunda clusula no corresponde pero la tercera clusula s lo hace. Note que (3-1) an no se
evala}
= 7 : take (3-1) (repeat 7)
{para decidir que hacer con la primera clausula, se debe probar (3-1) <= 0, lo cual

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)

evaluacin de expresiones es un aspecto relevante en la semntica de los lenguajes funcionales, la creacin y


mantenimiento de thunks contribuye de forma sustancial en el costo de ejecucin de los programas
funcionales.

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].

En el Anexo B se describe la terminologa en lenguaje ML aplicada a grafos.

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.

El algoritmo mostrado en la Figura 3.15 realiza la evaluacin (reduccin)

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

Graph can share subexpressions to evaluate at once.


main = print (head [1..])

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

print

head [1..]
function

thunk

code

code
build

head

[1..]
function

thunk

code

code

(b)

References : [H5], [H10]

Figura 3.16. Ilustracin de grafos. (a) Grafo mostrando expresiones compartidas, (b) Representacin de un
cdigo Haskell en grafo.

100

5. Implementation of evaluator

Graph can be reduced in some order


top-level redex
square (1+2)
square

inner level redex

which first ?

1+2
+

reduce inner level first

reduce top-level (outermost) first

for call-by-value

for call-by-need

square (1+2)

(1+2) * (1+2)
3
3

square
+

1+2

*
1

To select top-level redex first, the evaluation of arguments can be postponed.


References : [D3], [W5], [H4] Ch.11, 12, [B8] Ch.3
Figura 3.17. Proceso de reduccin de un grafo. Para seleccionar
el elemento del nivel superior, la evaluacin

de argumentos puede ser pospuesta.

5. Implementation of evaluator

Normal order reduction is implemented by lazy graph reduction


Normal order reduction

Lazy graph reduction


find top-level redex
and reduce it
implemented by

exp

an expression
exp

exp

exp

...

exp

Normal order (leftmost outermost) first

exp

exp

exp

exp
exp

exp
exp

Normal order (leftmost outermost) reduction is implemented by lazy graph reduction


Figura 3.18. La reduccin de la forma normal se implementa en la reduccin de grafos para seleccionar el
to select top-level redex first.
redex que se encuentra en el tope.
Given an application of a function, the outermost redex is the function application itself.

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

Evaluator (reducer, executer)


(abstract 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.

Ejemplo de aplicacin de la tcnica: reduccin de grafo. Enseguida se va a mostrar la evaluacin de una


expresin aritmtica aplicando la tcnica de reduccin de grafos. La expresin a evaluar es: ((2 + 2) + (2 + 2)
+ (3 +3)). La representacin de esta expresin como un grafo se muestra en la Figura 3.20. En la expresin
aritmtica se puede observar la repeticin de algunos trminos. Estos elementos repetidos, se observan en el
grafo de la Figura 3.20, sin embargo, bajo esta representacin no se repiten, sino que se declaran como
subexpresiones compartidas (ver crculos azules en la Figura 3.20).

102

Figura 3.20. Representacin como un grafo de la expresin aritmtica ((2+2)+(2+2)+(3+3)).

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.

Figura 3.21. Reduccin del grafo mostrado en la Figura 3.20.

Nota. En el Anexo B, se muestra la arquitectura de un interpretador de grafos conocido como


TIGRE(Threaded Interpretative Graph Reduction Engine), el cul es una aportacin de Koopman y Lee
(1989). Este es un programa implementado en lenguaje C, integrando

rutinas escritas en el lenguaje

ensamblador VAX.

103

Ejercicios

3.1. Escribir la siguiente definicin para una AND lgica en trminos de foldr:
and :: [Bool] -> Bool
and [ ]

= True

and (b:bs) = b && and bs

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:

enumFrom n = n `seq` (n : enumFrom (n+1))


Pruebe con este cambio!!
3.3. Para tener ms prctica con la programacin de listas infinitas, realice un programa que simule el juego
Piedra, Papel o Tijera. Ejemplo clsico que se encuentra en el libro An Introduction to Functional
Programming por R. Bird y P. Wadler. Se deben conocer las reglas del juego. En toda ronda, cada jugador
realiza un signo con la mano, relacionado con los objetos: piedra, papel, o tijera. Cada uno de los signos
puede ser anulado por otro.

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

particular de programacin. Otros enfoques son la programacin

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

apropiadamente, entonces, la lgica semntica es ms simple que la operacional. Sin embargo, la


programacin lgica no puede aplicarse en todos los modelos de computacin.
Un programa lgico consiste en sentencias lgicas basadas en una semntica operacional, es decir, pueden
ser ejecutadas en una computadora. Si la semntica operacional est bien diseada, la ejecucin de un
programa cumple con dos propiedades: 1) es correcto, es decir, respeta la semntica lgica (todas las
consecuencias de la ejecucin son consecuencias lgicas vlidas del programa, formado por un conjunto de
axiomas), y es 2) eficiente, es decir, permite la escritura de programas que se ejecutan con la complejidad
espacial y temporal, esperadas [Roy y Seif, 2003].
Antes de pasar a la lgica de primer orden o lgica de predicados, voy a mostrar de manera breve,
informacin relacionada con la lgica de proposiciones, la cul es la lgica ms simple en la que se puede
basar un programa.
Lgica proposicional. Esta lgica consiste en frmulas proposicionales, las cuales son expresiones que
resultan de la combinacin de smbolos, tales como: p, q, r, juntos a conectores como:
y

. Los smbolos p, q y r son llamados tomos. Un tomo en

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

Para asignar un valor de verdad a una frmula proposicional,

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

, de acuerdo a las leyes del lgebra

booleana, como se muestra en la Tabla 4.1:


Si una frmula resulta verdadera para todas las combinaciones de sus tomos, se le conoce como tautologa.
Tanto las leyes de De morgan como la de contraposicin dan lugar a resultados tautolgicos. Es decir, estas
frmulas resultan verdaderas para todas las posibles combinaciones de p y q.

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

4.1 Repaso de la lgica de primer orden (LPO)


La lgica proposicional no resulta apropiada para la programacin basada en lgica, ya que su principal
debilidad es que no permite el manejo de expresiones con estructuras de datos. La lgica de predicados o

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

condicional se representar en LPO como:

, donde p y q representan predicados. La ejecucin del condicional es como sigue: la ejecucin de q se


realizar solamente cuando p sea verdadera, si p es falsa, el control del programa pasa a la siguiente
instruccin.
9.3 R elat
ion t o logic pr ogr amming

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

veces que se ejecuta la sentencia


y := 1
Si y < 2

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

y . La expresin anterior puede convertirse en una sentencia lgica agregndole cuantificadores,

por ejemplo,

. En este caso, las variables x y y, anteriormente libres, ahora son

capturadas por los cuantificadores.


Con la finalidad de asignar un valor de verdad a una sentencia en el clculo de predicados, se tiene que
definir un modelo. En este caso la palabra modelo se refiere a un modelo lgico, el cual es un poco diferente a
un modelo computacional. Un modelo lgico se forma de dos partes: 1) un dominio de discurso (todos los
valores posibles que pueden tomar las variables), y 2) un conjunto de relaciones (donde una relacin es un
conjunto de tuplas). Cada predicado tiene una relacin, la cual indica las tuplas para las cuales el predicado
es verdadero. Entre todos los predicados, el smbolo de igualdad (=), resulta particularmente importante. La
relacin de igualdad casi siempre ser parte del modelo. Los cuantificadores x (existe un x) y x (para
todo x), cubren el rango en todo el dominio de discurso. Generalmente, el modelo lgico se selecciona de tal
suerte que un conjunto especial de sentencias, llamadas axiomas, resultan ser verdaderas. A este modelo se
le conoce como semntica lgica de los axiomas. En este sentido, pueden existir muchos modelos para los
cuales las axiomas resultan ser verdaderas.
Veamos un ejemplo de cmo esto trabaja. Considere el siguiente par de axiomas:

Existen varios modelos posibles para estos axiomas, por ejemplo el siguiente:

Dominio de discurso: {Jorge, Toms, Guillermo}


Relacin de padre: {padre (Jorge, Toms), padre (Toms, Guillermo)}
Relacin abuelo: {abuelo (Jorge, Guillermo)}
Relacin de igualdad: {Jorge = Jorge, Toms = Toms, Guillermo = Guillermo}

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

], especifica que, Existe, al menos, un

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

Se otorga al programador la posibilidad de apoyar al resolvedor de teoremas a travs de


conocimiento operacional. Este conocimiento es esencial para escribir programas lgicos eficientes.
Por ejemplo, considere un programa lgico que realice el ordenamiento de una lista de enteros. Un
programa ingenuo (naive), puede consistir de axiomas que definan una permutacin en una lista, as
como una pregunta que declare la existencia de una permutacin, cuyos elementos se encuentran
ordenados en orden ascendente. Tal programa podra ser corto pero ineficiente. Una forma ms
eficiente de resolverlo consiste en escribir axiomas que expresen las propiedades de un algoritmo
eficiente para el ordenamiento, tal como el quicksort.

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,

estos elementos. El usuario interacta con este

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:

Procesamiento del lenguaje natural, traduccin de un lenguaje a otro.

Aplicaciones para entrenamiento.

Mantenimiento de bases de datos en proyectos del genoma humnao.

Generacin automtica de narraciones.

Anlisis y medicin de redes sociales.

Sistemas de soporte electrnico para mdicos.

Enseguida voy a proporcionar algunos ejemplos de programas desarrollados en Prolog.


Ejemplo 1. Escriba en LPO los hechos y reglas necesarios para representar lo siguiente:

1.

Existe una persona que gobierna a todos los guatemaltecos.

2.

Todos los guatemaltecos tienen un animal.

3.

Reglas que relacionen TienePerro y TieneGato con TieneAnimal.

111

4.

Cada dueo debe vacunar de la rabia a su(s) perro(s).

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.

Los hechos y reglas correspondientes se muestran en las siguientes lneas:


not (0, 1).
not (1, 0).

113

and (0, 0, 0).


and (1, 0, 0).
and (0, 1, 0).
and (1, 1, 1).

compuerta_A (X, Y, Z):- and(X,Y,V), not (V, Z).


compuerta_B (A,B,C,D,F):- and (A, B,X),and(C,D,Y),and(X,Y,Z),
not(Z,F).

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

Ingeniera en Sistemas Computacionales del Instituto Tecnolgico de Acapulco, realizado en 2014.

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

progenitor (patricia, margarita).


progenitor (silvestre, flavia).
padre (X, Y):- progenitor (X, Y), hombre (Y).
madre (X,Y):- progenitor (X, Y), mujer (Y).
hermano (X,Y):- padre (X, H), padre (Y, H), hombre (Y).
hermano (X,Y):- madre (X, H), madre (Y, H), hombre (Y).
hermana (X,Y):- padre (X, H), padre (Y, H), mujer (Y).
hermana (X, Y):- padre (X, H), padre (Y, H), mujer (Y).
abuelo (X, Y):- padre (X, N), padre (N, Y).
abuelo (X,Y):- madre (X, N), padre (N, Y).
abuela (X, Y):- padre (X, N), madre (N, Y).
abuela (X, Y):- madre (X, N), madre (N, Y).
antepasado (X, Y):- padre (X, Y).
antepasado (X, Y):- madre (X, Y).
antepasado (X, Y):- abuelo (X, Y).
antepasado (X, Y):- abuela (X, Y).

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.

4.2 Unificacin y Resolucin


Unificacin. Cuando un lenguaje lgico, como Prolog, recibe como entrada (interrogante, meta, objetivo) una
secuencia de predicados, tal como:
?- dueo (X, Y), perro (Y), nl.
El sistema Prolog intenta satisfacer cada uno de los predicados de entrada realizando un anlisis de izquierda
a derecha. En este anlisis se busca en la base de hechos y reglas, variables que correspondan con los datos
solicitados, por ejemplo, se realiza la asignacin de X a Juan, y de Y a sultn. Si esto ocurre, el sistema
mostrar informacin como la siguiente:

?- dueo (X, Y), perro (Y), nl.


Juan
X = Juan,
Y = sultn
Indicando que hubo una correspondencia entre lo solicitado y los datos almacenados en la base de datos. Por
ejemplo, y tomando como referencia la Figura 4.8 (b), relacionada con el rbol genealgico de Jonathan (ver
Ejemplo 3), se solicita al sistema Prolog lo siguiente:
?- madre (A, patricia)
Prolog realiza una bsqueda en la base de datos correspondiente (ver Figura 4.7) de arriba hacia abajo y de
izquierda a derecha, encuentra dos coincidencias para A, (progenitor (jonathan, patricia) y progenitor (katia,
patricia), y las muestra de la siguiente forma:

?- madre (A, patricia)


A = Jonathan;
A = Katia.
El proceso de correspondencia entre los datos de entrada y los contenidos en la base de datos y reglas
aplicado por Prolog se conoce como unificacin. En este proceso se busca reemplazar variables por valores,
tal como se observ en los ejemplos anteriores. por ejemplo, la unificacin de persona(X, Y, Z) con

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).

Una variable asignada es tratada con el valor asignado.

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.

Todas las dems combinaciones ocasionarn una falla en la unificacin.

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.

Una variable o una constante es un trmino.

2.

Si f es una funcin de aridad n y t1,,tn son trminos, entonces f(t1,,tn) es tambin un trmino.

3.

Cualquier expresin es un trmino solo si puede construirse a travs de los pasos 1 y 2.

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

Y es una variable que no ocurre en X:


sustituir X por Y en la pila y en la sustitucin
agregar Y=X a la sustitucin

121

X y Y son constantes idnticas o variables:


continua

yY

f y

Para alguna funcin f y n>0:


Pu h

De otra forma: Falla = True


Si Falla, entonces resultado Fallido
De otra forma sacar la sustitucin

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

debe ser distinto a . Asimismo, cada trmino

representan a distintas variables, y

es conocido como una unin para

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

) , se pide determinar si estos tomos unifican o

no. El algoritmo realiza los siguientes pasos:


1.

Inicio: ya que los predicados son el mismo se coloca en la pila [

)] as como =.

122

2.

de la pila, dejando [(

Se lee

aplicamos a la pila, dando [(


3.

Se lee

Leer

)].

) de la pila, quedando la pila vaca [ ]. Para el cuarto case, se ajusta la pila para
)], sin ajustes a .

almacenar [(
4.

)]. Para el segundo case realizamos

de la pila, dejando la pila vaca [ ]. Para el primer case se modifica la expresin


, ya que la pila se enuentra vaca no se tiene que aplicar .

5.

La pila esta vaca, as que

resulta ser el unificador para los dos tomos.

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].

Resolucin. Esta tcnica se va a describir desde la perspectiva de un lenguaje de programacin lgica, en


este caso, Prolog. Una vez que el usuario introduce una pregunta (query) y el interpretador de Prolog la
acepta, el sistema Prolog realiza un ciclo que involucra dos actividades: unificacin y resolucin. Ya me refer
a la primera actividad. Sin embargo, antes de presentar la segunda, mostrar algunas tareas del interpretador
con un poco de detalle. Cuando el interpretador recibe la entrada, ste tiene un programa donde residen
hechos y reglas, y el query, formado por una conjuncin de tomos. En este momento el interpretador ejecuta
el siguiente algoritmo:
Mientras el query no est vaco hacer
Selecciona un tomo del query
Selecciona un hecho o regla con el cual el tomo unifica

Si no hay alguna, entonces retorna Falla


De otra forma
Si un hecho o regla fue seleccionado
Entonces
Remover el tomo del query
De otra forma Si una regla fue seleccionada
Reemplaza el tomo en el query por el cuerpo de la regla elegida
Regresa xito
El algoritmo mostrado omite muchos detalles, pero muestra la esencia de las actividades realizadas por el
interpretador. El ajuste al query, el cual ocurre si el proceso de unificacin resulta exitoso, es el proceso de

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.

Determinando las sustituciones: Backtracking


Durante este proceso, se regresa a un objetivo o meta previa, y se busca otra alternativa de satisfacerla. Para
mostrar la manera en que este mecanismo funciona, desde una perspectiva intuitiva, voy a recurrir un ejemplo
tomado del libro de Max Bremer (2005), el cual se refiere a un conjunto de relaciones familiares mostradas en
la Figura 4.10.
Con referencia a la Figura 4.10, y dada la consulta mostrada en la lnea siguiente

?-

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:

[P4] parent(john, mary):- wr

f h r?

f h r j h

ry wr

f h r!

[F2] father(john, mary).

125

40

Logic Programming With Prolog

The Family Relationships Example


This example is concerned with family relationships amongst a group of people.
The clauses shown below comprise 10 facts defining the mother/2 predicate, 9
facts defining the father/2 predicate and 6 clauses defining the parent/2 predicate.
[M1]

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

The child is mary


Los objetivos plantados en la consulta fueron satisfechos. El sistema Prolog muestra el valor de las variables
requeridas, as como los textos resultado de efectos laterales, quedando como se observa en las siguientes
lneas:
?- parent(john,Child),write('The child is '),write(Child),nl.
mother?
father?
father!
The child is mary
Child = mary
Pareciera ser que el proceso de unificacin y resolucin aqu finaliza, sin embargo no es as, el sistema
intenta encontrar otra manera de resatisfacer el ltimo objetivo de [P4]. Esto es, nl, el cual como ya se
mencion falla al ser evaluado en retroceso, al igual que el objetivo siguiente write(father!). Este proceso se
repite buscando la unificacin con todas las clusulas posibles y obteniendo todos los valores posibles, hasta
que el sistema vuelve a la evaluacin de la clusula inicial [P4]. En este punto, no se pueden hacer ms
evaluaciones en retroceso y la consulta finalmente produce una falla, quedando en este ejemplo, la
informacin siguiente:
?- parent(john,Child),write('The child is '),write(Child),nl.
mother?
father?
father!
The child is mary
Child = mary ;
father!
The child is mark
Child = mark ;
father!
The child is francis
Child = francis
?%% No hay ms soluciones posibles

El principio de resolucin fue inicialmente desarrollado como un mtodo aplicado en la demostracin


automtica de teoremas. Posteriormente, se manej una forma restringida de este principio en la
programacin lgica, el cul como ya vimos consiste en un mtodo de inferencia lgica. Enseguida se van a
mostrar las clusulas de Horn y la

resolucin-SLD,

mtodos que tratan de generalizar el proceso de

inferencia.

127

4.3 Clusulas de Horn. Resolucin SLD.


Las clusulas de Horn, son una forma restringida de clusulas utilizadas en programacin lgica. El proceso
de refutacin de las clusulas de Horn se realiza mediante un esquema restringido del procedimiento de
resolucin conocido como: resolucin-SLD, el cual es consistente y completo para las clusulas de Horn.
Clusulas de Horn. Una clusula de Horn es una clusula que presenta la siguiente forma:
15

Donde al menos un literal debe ser positivo. El literal positivo


literales negativos

es la cabeza de la clusula mientras que los

forman el cuerpo. En estas clusulas se aplica la siguiente terminologa:

Un hecho es una unidad positiva ( ) en la clusula de Horn.

Una clusula objetivo es una clusula de Horn sin literales positivas

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.

Un conjunto de procedimientos forman un programa lgico.

Un procedimiento formado por hechos es una base de datos.

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

Sustitucin en clusulas de Horn. Si P es un programa y G una clusula objetivo. Una sustitucin


para las variables en G, resulta ser una sustitucin correcta si
universal tiene influencia sobre todas las variables libres en el trmino

, donde el cuantificador
. Para dar un ejemplo de esto,

consideremos que P representa a un conjunto de axiomas en al mbito aritmtico, en este sentido:

Sea G la clusula objetivo

la sustitucin

, por lo tanto:

, es una sustitucin correcta para G.

Ya que

Sea G la clusula objetivo indicada por

.
, es una sustitucin correcta para G.

y = , la sustitucin vaca:

Sea G la clusula objetivo

En este caso, ya que

no representa una correcta sustitucin.


, y una correcta sustitucin , por definicin

Dado un programa P, una clusula objetivo

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

Resolucin-SLD16. La resolucin SLD, es un caso particular de la resolucin general, donde: a) Los


resolventes son siempre objetivos, es decir, clusulas sin encabezado, b) Los programas son conjuntos
de clusulas de Horn definidas, esto es, hechos y reglas, c) Existe una funcin de seleccin la cul
selecciona un tomo del resolvente a quien aplicar la resolucin.
Antes de pasar a describir el procedimiento de resolucin utilizado en los programas lgicos, voy a
presentar un ejemplo [Ben-Ari, 2012].
Ejemplo 4.3.2. Considere que q(y, b), q(b, z) es la clusula objetivo para el programa mostrado en el
Ejemplo 4.3.1. En cada paso debemos seleccionar un literal en la clusula, as como una clusula cuyo
encabezado se enfrente con el literal seleccionado. Por simplicidad de la explicacin, solamente se van a
mostrar las sustituciones de las variables originales de la clusula objetivo.
1.

Seleccionar

y reemplazar en la clusula 1, dando

2.

Seleccionar p(y, b) y reemplazar en la clusula 5, dando

p(y, b), q(b, z).


q(b, z). Este paso requiere la sustitucin {y

d}.
3.

Solo hay una literal y se reemplaza en la clusula 1, dando

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}.

As que, una vez realizadas las sustituciones, se tiene una refutacin

q(y, b), q(b, z) bajo la sustitucin

. Por lo tanto, por consistencia de la resolucin:

As que, resulta ser una sustitucin correcta y

es verdadera para cualquier modelo de P.

La definicin y representacin formal de la resolucin-SLD se indica a continuacin.


Sea P un conjunto de clusulas de un programa, R una regla para realizar el cmputo y G una clusula
objetivo. Una derivacin para la resolucin-SLD consiste en una secuencia de pasos de resolucin entre
las clusulas objetivo y las clusulas del programa. La primera clusula objetivo G0 es G. Gi+1 se deriva de
Gi eligiendo una literal

perteneciente a Gi, a travs de elegir la clusula

cabeza de

a travs del unificador

unifica con

, de tal forma que, la

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 .

Consistencia de la Resolucin-SLD. La consistencia (soundness) de la resolucin-SLD, puede


expresarse de la siguiente forma. Sea P un conjunto de clusulas de un programa, R una regla
computacional y G una clusula objetivo. Asimismo, suponga que existe una refutacin-SLD de G. Sea

= una secuencia de unificadores utilizados en la refutacin y que sea la restriccin de a las


variables de G. Entonces, es una sustitucin correcta para G.
Completez de la resolucin-SLD. La refutacin-SLD resulta ser completa para casi todos los conjuntos
de clusulas de Horn. El aspecto de completez para la resolucin-SLD se describe de la siguiente
manera: Sea P un conjunto de clusulas de un programa lgico, R una regla de cmputo, G una clusula
objetivo, y una sustitucin correcta. Existe una refutacin-SLD18 de G a partir de P tal que cada es la
restriccin de la secuencia de unificadores =

r b

4.4 . Programacin lgica con clusulas de Horn


Como ya mencion con anterioridad, una clusula de programa consiste en una clusula de Horn con un
literal positivo y uno o ms literales negativos; un hecho es una clusula de Horn con un literal positivo. En
este sentido, un programa lgico est formado por un conjunto de clusulas de programa y hechos. Asimismo,
dado un programa lgico y una clusula objetivo, la resolucin-SLD puede utilizarse para buscar una
refutacin, si sta existe, la negacin de la clusula objetivo es una consecuencia lgica de las clusulas de
programa y hechos, y las sustituciones realizadas durante la refutacin forman la respuesta al programa.
19

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

Una regla de bsqueda es un procedimiento para buscar una refutacin en un rbol-SLD.


Una refutacin-SLD consiste en un algoritmo de resolucin junto con la especificacin de una regla computacional y

una regla de bsqueda.


19

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

para cada clusula objetivo nueva

, 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.

4.5. Semntica de los programas lgicos


En un lenguaje de programacin, la semntica tiene que ver con el significado o interpretacin que se le
asigna a una expresin. En el caso de la programacin lgica, es un mecanismo que permite asignar un valor
Verdadero (V) o Falso (F) a una frmula. Para ello, es necesario dar un significado a cada uno de los smbolos
que aparecen en la frmula. Las conectivas y cuantificadores tienen un significado fijo, mientras que la
interpretacin de constantes, smbolos de funcin y de predicados puede variar. En una interpretacin se
define un dominio de discurso sobre el cul varan las variables, se asignan valores a las constantes y se
definen los smbolos de funcin y de predicados de manera especfica. Por lo tanto, resulta necesario, asignar
un valor concreto a cada smbolo de la frmula en un dominio determinado.
Para tener una percepcin intuitiva de este concepto, les mostrar el siguiente ejemplo tomado de Labra y
Fernndez (2016).
Sea

Tabla 4.1. Interpretacin de la expresin:

sujeta a las interpretaciones mostradas en la Tabla 4.1.

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

programas lgicos: la semntica declarativa, la semntica operacional y la semntica denotacional


(comnmente llamada semntica de punto fijo). En esta seccin se presentarn, sin dar una descripcin a
detalle, algunas nociones y teoremas bsicos relacionados a la semntica de los programas lgicos definidos.
La terminologa presentada corresponde a Lloyd (1987).
Definicin. Sea

un lenguaje de primer orden [Pasarella, 2016].

1. El universo de Herbrand de , denotado

, es el conjunto de todos los trminos de base que pueden

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:

La base de Herbrand de , denotada

, es el conjunto de todos los tomos que pueden formarse a

partir de los predicados que ocurren en

y los trminos en

. Por ejemplo, la base de Herbrand de

es:
{

3.

Una estructura
funcin de
en ,

para

}.

es una estructura de Herbrand si su dominio es

y elementos

de

, y para cada smbolo de

. Para cada valor constante c

. Por ejemplo, una estructura de Herbrand para


, donde

4.

es la siguiente:

para todo

Si representa un conjunto de sentencias, un modelo de Herbrand para es una estructura de


Herbrand que a su vez resulta ser un modelo para . Debido a que en los modelos de Herbrand la
interpretacin de las constantes y los smbolos de funcin son fijas, es posible identificar un modelo
de Herbrand con un subconjunto de la base de Herbrand.

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,

Sea P el programa lgico descrito por los siguientes hechos y clusulas:

135

Tanto el universo como la base de Herbrand de P son, respectivamente:

(
{

) (

) (

( (

) (

))
) (

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.

4.6 Representacin clausada del conocimiento


El conocimiento es un proceso que requiere el uso de datos e informacin, los cuales combina con relaciones
y dependencias. El conocimiento se debe caracterizar de acuerdo a su aplicacin. Asimismo, el conocimiento
puede ser de diferentes tipos (procedimental, declarativo, heurstico). La programacin lgica est diseada
bajo un enfoque declarativo. Por lo tanto, el conocimiento que maneja es declarativo, es decir, es un
conocimiento pasivo, expresado a travs de sentencias que expresan hechos y relaciones del mundo que nos

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

conteniendo informacin del

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,

, por la declaracin: Normalmente,

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

es el nombre de la regla por omisin y

indica que la regla por omisin no

La segunda regla detiene la aplicacin de la omisin a cualquier P que pueda ser un profesor de matemticas.
Asumiendo que el predicado:

forma parte de la base de datos

, 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

, tiene solamente un conjunto de respuestas20,

, (ver Teorema 2.1 [Gelfond y

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:

. En este momento, el programa resultante tiene dos conjuntos


de respuestas, y cada conjunto contiene la expresin ofrecido(lgica, cs). De acuerdo con los autores, este
ejemplo muestra que A-Prolog con disyuncin, permite una forma natural de razonamiento basado en casos
una forma de razonamiento no modelada por los sistemas lgicos clsicos-.
El siguiente ejemplo de representacin de conocimiento en programacin lgica, fue obtenido de Baral y
Gelfond (1994).
Ejemplo 4.6.2. Suponga que un agente que razona, posee el siguiente conocimiento acerca de las aves: Las
aves generalmente vuelan y los pinginos que son aves no vuelan. Pioln es un ave. Ahora suponga que el
agente fue contratado para construir una jaula para Pioln, l la hace con salida a la azotea aunque no posee
argumentos para saber si Pioln vuela o no. Para nosotros sera razonable ver este argumento como no
vlido y rechazar la jaula. Este no sera el caso si Pioln no pudiera volar, por alguna razn (desconocida para
el agente) y rechazramos pagar, ya que el agente de manera innecesaria la dej en la azotea. La
representacin de este conocimiento se muestra a continuacin.
Considere un programa

formado por las siguientes reglas:


.
.
.

Usadas juntas con los siguientes hechos relacionados con aves:

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

la habilidad de volar de las aves (a estas sentencias se les conocen

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

es el modelo estable para

. Por el

lema, vuela(pioln) , s y slo s


,y
.
La sentencia

se deriva de forma inmediata del hecho 1 (h1) y del lema. Asimismo, para probar

necesitamos mostrar que


De esta manera, utilizando

, lo cul se deduce del mismo lema.


y

, juntos con la regla 1 y el lema descrito, se deduce vuela(pioln)

por consiguiente la respuesta a la consulta vuela(pioln) es S.


Con estos ejemplos, se pretende dar una idea de la manera en como se representa el conocimiento en los
programas lgicos.

4.7 Consulta de una base de clusulas


A cada lnea (sentencia), de un programa lgico se le conoce como clusula. Las clusulas estn formadas
por: hechos, reglas y consultas. Como ya se mostr en secciones anteriores, una consulta tiene la forma: ?A1,,An, donde cada Ai consiste en una frmula atmica. A la expresin A1,,An se le denomina meta u
objetivo, y a cada Ai, sub-meta o sub-objetivo.
Como se mostr en la seccin anterior, el conocimiento de cualquier rea se almacena en bases de datos. En
el caso de los programas declarativos, las clusulas encargadas de describir hechos y reglas relacionados
con un conocimiento, forman la estructura de almacenamiento en bases de datos. El inters ahora se centra
en las diferentes estrategias empleadas para recuperar/consultar la informacin almacenada. Lo que voy a
mostrar en esta seccin, es el uso de la lgica como un lenguaje de consulta en bases de datos, ms que ver
el proceso de una consulta, lo cul ya se mostr en secciones anteriores.
De la misma forma que se mostr a la lgica como un lenguaje expresivo para representar datos explcitos e
implcitos, ahora se va a mostrar como un lenguaje de consulta [Nilsson, 2000].

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:

abuelo(X, Z) padres(X, Y), padres(Y, Z).


padres(X, Y) padre(X, Y).
padres(X, Y) madre(X, Y).

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(X, Y) persona(X), persona(Y), padre(X, Y).


padres(X, Y) persona(X), persona(Y), madre(X, Y).
padre(adn, guillermo) hombre(adn), persona(guillermo).
padre(adn, bety) hombre(adn), persona(bety).
.
.
.
De esta manera, los errores de tipo tales como padre(ana, adn) pueden evitarse.
Para hablar de la lgica como un lenguaje de consulta, suponga que para saber quienes son los hijos de
Adn, solamente se tiene que escribir la siguiente clusula objetivo:

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

Ahora considere el siguiente extracto de una base de datos:

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:

gusta(X, Y), persona(X), persona(Y).


En respuesta a esta peticin, Prolog tendra que mostrar todos los individuos del tipo persona. Asimismo,
tambin es posible agregar a las reglas contenidas en la base de datos, literales extras del tipo persona(X).
Un enfoque, el cul muy seguido es empleado cuando se describen las bases de datos deductivas, consiste
en adoptar ciertas suposiciones acerca del dominio que se modela. A esto se le conoce como suposiciones de
21

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

relacin definida por el objetivo antes descrito, se convierte ahora en

. Sin embargo, esto

supone que la base de datos no contiene funciones, solamente un nmero finito de constantes.

4.8 Espacios de bsqueda


Cuando se busca la solucin de un problema bajo el enfoque de la programacin declarativa, se tiene que
representar el problema de alguna forma y posteriormente iniciar la bsqueda de una solucin. En ocasiones,
este proceso reviste la forma de una planificacin que puede conducir a hurgar en un espacio de posibles
soluciones. Bajo esta perspectiva, generalmente se utilizan grafos para representar tanto el problema como el
espacio de soluciones. En esta estrategia, se le conoce como espacio de estados, a la representacin de un
problema que abarca todas las posibles situaciones que se pueden presentar en la solucin del mismo. Esta
seccin la voy a abordar utilizando un problema muy conocido en la comunidad de inteligencia artificial, se
trata del problema de ordenamiento de los cubos (ver Figura 4.14). Y en base al mismo se van a mostrar
tanto los espacios de bsqueda como las estrategias comnmente utilizadas para encontrar el espacio de
soluciones.
El problema a resolver, consiste en encontrar una secuencia de pasos (plan), para colocar los cubos en una
determinada posicin a partir de una configuracin (estado) inicial. Suponga que existe un brazo robtico el
cul se encarga de mover los bloques. La nica regla
21

Closed World Asumptions.

22

Domain Closure Asumption.

a seguir en este problema es la siguiente: slo un

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.

Figura 4.14. Ilustracin del problema de ordenamiento de bloques.

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?.

Figura 4.15. Espacio de estados para el problema de ordenamiento de bloques.

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.

[[a, b, c], [ ], [ ]],

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 N es un nodo objetivo, entonces Sol = [N],

Si existe un nodo sucesor N1, tal que existe un camino Sol1 de N1 al nodo objetivo, entonces Sol =
[N|Sol1].

Estos puntos representados en Prolog se indicarn como sigue:


solucion(N, [N]) :- meta(N).
solucion(N, [N | Sol1]) :- s(N, N1), solucion (N1, Sol1).
Por lo tanto, para obtener la solucin al problema de los bloques se realizar la siguiente consulta a Prolog:
?- solucion ([[c, b, a], [ ], [ ]], Sol).
Sol = [[c, b, a], [ ], [ ]],
[[b, a], [c], [ ]],
[[a], [b, c], [ ]],
[[ ], [a, b, c], [ ]]]
Yes
La solucin se calcula como sigue. En un principio, el estado inicial N = [[c, b, a], [], []], por lo que el programa
se pregunta si N es una meta. La clusula objetivo funciona verificando si la solucin [a, b, c] es miembro del
estado N. Como esta meta falla, Prolog intentar satisfacer su meta inicial con la segunda clusula solucion.
Esto implica generar un sucesor de N (llamada a s(N, N1)). En ese sentido, se calcula N1 = [[b, a], [c], [ ]] y se
verifica si es una solucin. Como la meta falla, se genera un sucesor de N1 y as contina hasta llegar a
[[ ],[a, b, c], [ ]].
Una primera mejora a este algoritmo, consiste en evitar que los nodos visitados vuelvan a ser expandidos,
evitando con esto caer en ciclos. La idea consiste en llevar un registro de los nodos visitados. El resultado en
este caso, es un proceso que va del nodo final hacia el estado inicial. En Prolog, se realiza de la siguiente
forma:
solucion2 (Nodo, Sol) :- primeroProfundidad ([ ], Nodo, Sol1).

147

primeroProfundidad (Camino, Nodo, [Nodo | Camino]) :- meta(Nodo).


primeroProfundidad (Camino, Nodo, Sol) :- s(Nodo, Nodo1),
not (member (Nodo1, Camino)),
primeroProfundidad ([Nodo | Camino], Nodo1, Sol1).

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).

4.9 Programacin lgica con nmeros, listas y rboles.


En un lenguaje de programacin lgica, tal como Prolog, existe un dato bsico que forma las estructuras de
datos: El trmino. El trmino se define de forma recursiva con datos tipo: 1) Constante, como casa, bety,
321, -12.34, direccin, etc., 2) Variables, X, Y, Lista, _345, etc., 3) Estructuras, datos del tipo func(t1, t2,),
donde t1, t2, son trminos. El trmino func es conocido como funcin, su aridad corresponde con el nmero
de argumentos que tiene. En este sentido, las constantes son funciones de aridad 0.
Los operadores aritmticos y relacionales, tales como:

etc., son funciones que generalmente se

utilizan con operadores infijos, por ejemplo:

?-

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)).

% N is the dot product of lists V1 and V2.


dot(V1, V2, N) :- maplist(product,V1,V2,P), sumlist(P,N).
product(N1,N2,N3) :- N3 is N1*N2.

% Matrix multiplication with matrices represented


% as lists of lists. M3 is the product of M1 and M2
mmult(M1, M2, M3) :- transpose(M2,MT), maplist(mm_helper(MT), M1, M3).
mm_helper(M2, I1, M3) :- maplist(dot(I1), M2, M3).
(a)

(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]]

[a, b | [c, d] ] [a, b, c | [d] ] [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)

y[a | b], se consideraran errneas porque b se tomara

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 [

], la unificacin entre las listas hace que

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

elementos repetidos de la lista [a,b,h,b,a,j]. En la Figura 4.19 se ilustra el programa y un ejemplo de la


aplicacin de este programa.

(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.

Un rbol binario se puede definir de las siguientes formas:

nil para designar un rbol vaco, o

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)

tree(tree(nil, 1, nil), 2, tree(nil, 3, tree(nil, 4, nil)))

Para determinar la altura de un rbol binario se uede utilizar un predicado como el descrito en las siguientes
lneas:

156

height (nil, 0).


height (tree(LT, _, RT), H) :- height (LT, H1), height(RT, H2), max(H1, H2, HM), H is HM+1.
Asimismo, un rbol binario bin balanceado, es aquel definido como:

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).

Una versin ms eficiente es la siguiente:


balanced(nil, 0).
balanced(tree(LT, , RT), H) :balanced(LT, H1), balanced(RT, H1), H is H1 + 1.

Aplicaciones. Muchos problemas de Inteligencia Artificial, generalmente aquellos donde interviene el


concepto de bsqueda, se resuelven mediante la implementacin de rboles. Asimismo, estos elementos son
muy tiles en mbitos tan variados como la teora de juegos y el procesamiento del lenguaje natural. Por lo
tanto, podemos mencionar que el manejo eficiente de estas estructuras es sumamente importante para
conseguir programas eficientes de

en diversos

mbitos de la Ciencia. Como ya vimos en secciones

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.

Figura 4.21. rbol que representa la oracin: El tigre come carne.

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

4.10 Control de bsqueda en programas lgicos.


La solucin de problemas a menudo se plantea como un problema de bsqueda. En los lenguajes lgicos, tal
como Prolog, la respuesta a una consulta, conduce a realizar una bsqueda a travs del espacio de reglas y
clusulas, con el fin de obtener una o varias respuestas. Una caracterstica importante de los lenguajes de
programacin lgica es que utilizan una estrategia de bsqueda no determinsta, implementada va retroceso
o guiada por objetivos. Para esto, generalmente se aplica una tcnica de bsqueda primero en profundidad
aplicando un mecanismo de resolucin-SLD. Desde un enfoque declarativo, un programa lgico se define por
un conjunto de reglas y un sistema de programacin lgico intenta encontrar

solucin a una consulta

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

ajedrez: dos cuadros en una

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.

Cuando una bsqueda

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.

4.11 Manipulacin de trminos. Predicados metalgicos.


Un trmino puede estar formado por: i) una constante, b) una variable, c) una estructura, consistiendo de una
funcin (la cual es una constante) seguida por una secuencia de trminos encerrados en parntesis y
separados por comas.
Los predicados meta-lgicos permiten controlar el algoritmo de resolucin facilitando la meta-programacin.
sta consiste en construir programas que manipulan otros programas proporcionando una mayor expresividad
al lenguaje. Para lograr lo anterior, estos predicados pueden ser utilizados para realizar varias tareas, entre
las que figuran las siguientes:
1. Manipulacin de trminos o chequeo de tipos. Los siguientes predicados meta-lgicos se usan para
examinar el estado de instanciacin actual de un trmino:
var(Term)

Es cierto si Term es una variable libre (no instanciada).

nonvar(Term)

Es cierto si Term no es una variable libre.

ground(Term)

Es cierto si Term no contiene variables libres.

atom(Term)

Es cierto si Term est instanciado a un identificador Prolog.

atomic(Term)

Es cierto si Term est instanciado a un identificador a un

nmero.

number(Term)

Es cierto si Term est instanciado a un nmero (entero o real).

integer(Term)

Es cierto si Term est instanciado a un entero.

float(Term)

Es cierto si Term est instanciado a un real.

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

si estuviera definido de la siguiente forma:


is_list(X) :- var(X), !, fail.
is_list([ ]).
is_list([_|T]) :- is_list(T).

Ejemplos de chequeo de tipos:


?- atom(vacio).
Yes
?- integer(-3).

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.

2. Igualdad de trminos. La igualdad de trminos puede determinarse de diferentes formas. La diferencia


estriba en si tiene lugar o no la unificacin de variables en los trminos. El operador = es la propia
unificacin. Esto es, se unifican las variables de los trminos que se comparan. Sin embargo, el operador ==
no unifica las variables de los trminos que se comparan. Por tanto, una variable (no ligada) slo ser igual a
s misma. Por ejemplo:
T1 = T2

Es cierto si T1 y T2 pueden unificarse.

T1 \= T2

Es cierto si T1 y T2 no pueden unificarse.

T1 == T2

Es cierto si T1 y T2 son idnticos, es decir, se unifican sin que haya

ligaduras de

variables.
T1 \== T2

Es cierto si T1 y T2 no son idnticos.

Ejemplos de estos predicados son los siguientes [Labra, 1998]:


?- f(X, 2) = f(1, Y).
X = 1,
Y=2
?- f(X, 2) == f(1, Y).
No
?- f(X, 2) \== f(1, Y).
Yes

3. Conversin de datos en objetivos. El predicado call(X) se cumple si se cumple el objetivo X.


o(X, Y) :- call(X).

o(X, Y) se cumple, si se cumple X o se cumple Y.

165

o(X, Y) :- call(Y).

El predicado o(X, Y) se encuentra predefinido como el operador


en Prolog se sustituye automticamente un objetivo en forma de la variable X por
call(X).

En las siguientes lneas se muestran algunos ejemplos de la aplicacin de este predicado:


?- o(progenitor(ana, tomas), progenitor(tomas, ana)).
Yes
?- progenitor(ana, tomas) ; progenitor(tomas, ana).
Yes
for(0, X).
for(N,X) :- call(X), N1 is N - 1, for(N1, X).
?- for(5, write('*')).
*****
yes
?- T =.. [progenitor, tomas, ana], T.
T = progenitor(tomas, ana) ;

4. Gestin en

Bases de Clusulas. Veremos a continuacin los principales predicados del sistema

PROLOG para gestionar la Base de Clusulas (BC). Es decir, meta-predicados de consulta, insercin,
eliminacin, ...,

de las sentencias (o clusulas) de programa cargadas en memoria. Para ilustrar cmo

funcionan dichos predicados, veremos ejemplos de ellos sobre la siguiente BC:


:- dynamic23
padre, hijo_a, abuelo.
padre(jon, carlos).
padre(carlos, maria).
hijo_a(X,Y) :- padre(Y, X).
abuelo(X, Z) :- padre(X, Y), padre(Y, Z).
Predicado utilizado para consultar la base de clusulas (BC): clause(Cabeza, Cuerpo). Unifica Cabeza (que no
debe ser una variable libre) y Cuerpo con la cabeza y el cuerpo (respectivamente) de una clusula de la BC.
Por ejemplo:
?- clause(padre(X, Y), Z).

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

X = jon, Y = carlos, Z = true;


X = carlos, Y = maria, Z = true

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))).

Inserta dicha clusula como la ltima del procedimiento "hijo_a".


3.2. asserta(Clausula). Inserta Clausula (que debe estar instanciada a una clusula) en la BC como la primera
de su procedimiento. Por ejemplo, la siguiente expresin:
? asserta(padre(juan, eva)).
Inserta dicha clusula delante de las dos del procedimiento "padre".
3.3. retract(Clausula). Elimina de la BC la primera clusula unificable con Clausula (no debe ser una variable
libre). Por ejemplo:
? retract(padre(carlos,Y))
Y

= maria

Elimina "padre(carlos, maria)" de la BC.


3.4. abolish(Nombre / Aridad). Permite la eliminacin de la BC de todas las clusulas de nombre Nombre y
aridad Aridad. Por ejemplo, la expresin:
?- abolish(hijo_a / 2).
Si
Elimina de la BC todas las clusulas del predicado hijo_a de aridad 2.
Los siguientes predicados permiten el listado de las clusulas almacenadas en una BC: i) listing, lista todas
las clusulas de la BC, ii) listing(Nombre), lista todas las clusulas que definen al predicado de nombre
Nombre, iii) listing(Nombre / Aridad), lista todas las clusulas definidas por nombre Nombre y aridad Aridad.
3.5. findall(Instance, Goal, List). Predicado utilizado para recolectar soluciones, List se unifica con la lista de
todas las instancias de Instance que hacen cierto a Goal. Si Goal no es cierto para ningn valor de Instance,
entonces List se unifica con la lista vaca [ ]. Por ejemplo:

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

realiza una bsqueda en

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 libre, por lo que se har backtracking.

?- bagof(P, X ^ pide(X, P), L). %%


L = [cerveza, vino, 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.

% Programa que lee frases (tecleadas en el terminal). La llamada principal es


leer(X).
% LEER, lee una frase.
% Modo de uso: leer(out frase).
leer([P|Ps]) :- get0(C), leepalabra(C,P,C1), restofrase(P,C1,Ps).
% RESTOFRASE, dada una palabra y el carcter que la sigue, devuelve el resto de
la frase.

168

% Modo de uso: restofrase(in palabra, in caracter, out frase)


restofrase(P, _ , []) :- ultimapalabra(P), !.
restofrase(P, C, [P1|Ps]) :- leepalabra(C,P1,C1), restofrase(P1,C1,Ps).
% LEEPALABRA, dado un carcter inicial, devuelve una palabra y el carcter que viene detrs
de la palabra.
% Modo de uso: leepalabra(in carcter, out palabra, out carcter).
leepalabra(C,P,C1) :- caracter_unico(C), !, name(P,[C]), get0(C1). leepalabra(C,P,C2) :en_palabra(C,NuevoC), !, get0(C1), restopalabra(C1,Cs,C2), name(P, [NuevoC | Cs]).
leepalabra(C,P,C2) :- get0(C1), leepalabra(C1,P,C2).
% RESTOPALABRA, dado un carcter inicial, devuelve la lista de caracteres
% y el carcter que viene detrs de la palabra.
% Modo de uso: leepalabra(in carcter, out lista_de_caracteres, out caracter).
restopalabra(C, [NuevoC | Cs], C2) :- en_palabra(C,NuevoC), !, get0(C1),
restopalabra(C1,Cs,C2).
restopalabra(C,[ ],C).
% CARACTER_UNICO, los siguientes caracteres forman palabra por si mismos.
% Modo de uso: caracter_unico(in cdigo_caracter).
caracter_unico(44).

%,

caracter_unico(59).

%;

caracter_unico(58).

%:

caracter_unico(63).

%?

caracter_unico(33).

%!

caracter_unico(46).

%.

% ULTIMAPALABRA, las siguientes palabras terminan una frase.


% Modo de uso: ultimapalabra(in palabra).
ultimapalabra('.').
ultimapalabra('!').
ultimapalabra('?').
% EN_PALABRA, trata los caracteres que pueden aparecer dentro de una palabra.
% La segunda clusula convierte maysculas a minsculas.
% Modo de uso: en_palabra(in caracter, out caracter).
en_palabra(C,C) :- C>96, C<123.
en_palabra(C,L)
:- C>64, C<91, L is C+32.
en_palabra(C,C) :- C>47, C<58.
en_palabra(39,39).
en_palabra(45,45).

%
%
%
%
%

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)

Si un entero no es impar, entonces es par.

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]

rich_father (X, Y) :- rich (X), father (X, Y).

Y dada la siguiente consulta:

?- rich_father (A, B).

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

2 L o gica de Primer Orden

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

Figur a 2.2 El mundo deFigura


los bloques,
usado para ejemplicar el calculo de predicados.
4.26. Ilustracin de El mundo de los bloques.

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:

https://hassanolity.files.wordpress.com/.../the_c_programming_language_2. pdf, 2016.


[Knuth, 2004]. Donald E. Knuth. The Art of Programming. Adisson Wesley, Stanford University, USA, 2004.
[Koopman and Lee, 1989]. P. Koopman and P. Lee. A Fresh Look at Combinator Graph Reduction. ACM,
SIGPLAN89 Conference on Programming Language Design and Implementation, vol. 24, No. 7, pp. 110-12,
Portland, Oregon, 1989.
[Labra y Fernndez, 2016]. Emilio Labra y Daniel Fernndez. Cuaderno didctico Lgica de predicados.
Universidad de Oviedo, 2016.
[Labra, 1998]. Jos E. Labra. Programacin Prctica en Prolog. Departamento de Informtica, Universidad de
Oviedo, Octubre, 1998.

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:

http://www.cs.cmu.edu/~fp/courses/lp/ , August 29, 2006.


[Robinson, 1965]. J. A. Robinson. A machine-oriented logic based on the resolution principle. Journal of the
ACM, 12(1):2341, 1965.
[Roy and Haridi, 2003]. Peter Van Roy and Seih Haridi. Concepts, Techniques, and Models of Computer
Programming. Academic book, published by the Royal Institute of Technology, Swedish, June, 2003.
[Takenobu et al, 2004]. Takenobu T., H. Kataura and M. Ata. Lazy evaluation illustrated for Haskell Divers
GitHub. 2004.
[Urea, 2011]. Carlos Urea Almagro. Lenguajes de programacin. Curso, creado el 24 de octubre de 2011.
[Van Roy 1994]. Peter Van Roy. 1983-1993: The wonder years of sequential prolog implementation. J. Log.
Prog., 19/20:385-441, May, 1994.
[Wadsworth, 1971]. Christopher P. Wadsworth. Semantics and Pragmatics of the Lambda Calculus. PhD
Thesis, Oxford University, 1971.

174

Anexo A
ARCHIVO STANDARD.PRELUDE

Informacin almacenada en el archivo standard.prelude contenido en Gofer versin 2.30a por Mark P. Jones.

-- Operator precedence table: ----------------------------------------------infixl


infixr
infixr
infixl
infix
infixl
infix
infixr
infix
infix
infixr
infixr
infixr

9
9
8
7
7
6
5
5
4
4
3
2
0

!!
.
^
*
/, `div`, `quot`, `rem`, `mod`
+, \\
++, :
==, /=, <, <=, >=, >
`elem`, `notElem`
&&
||
$

-- Boolean functions: ------------------------------------------------------(&&), (||)


False && x
True && x
False || x
True || x

:: Bool -> Bool -> Bool


= False
= x
= x
= True

not
not True
not False

:: Bool -> Bool


= False
= True

and, or
and
or

:: [Bool] -> Bool


= foldr (&&) True
= foldr (||) False

any, all
any p
all p

:: (a -> Bool) -> [a] -> Bool


= or . map p
= and . map p

otherwise
otherwise

:: Bool
= True

-- Character functions: ----------------------------------------------------primitive ord "primCharToInt" :: Char -> Int


primitive chr "primIntToChar" :: Int -> Char
isAscii, isControl, isPrint, isSpace

:: Char -> Bool

175

isUpper, isLower, isAlpha, isDigit, isAlphanum


isAscii c

ord c < 128

isControl c

c < ' '

||

c == '\DEL'

isPrint c

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

:: Char -> Bool

|| c == '\n'
|| c == '\v'

||

:: Char -> Char

toUpper c | isLower c
| otherwise

= chr (ord c - ord 'a' + ord 'A')


= c

toLower c | isUpper c
| otherwise

= chr (ord c - ord 'A' + ord 'a')


= c

minChar, maxChar
minChar
maxChar

|| c == '\r'

:: Char
= chr 0
= chr 255

-- Standard list processing functions: -------------------------------------head


head (x:_)

:: [a] -> a
= x

last
last [x]
last (_:xs)

:: [a] -> a
= x
= last xs

tail
tail (_:xs)

:: [a] -> [a]


= xs

init
init [x]
init (x:xs)

:: [a] -> [a]


= []
= x : init xs

(++)
[]
++ ys
(x:xs) ++ ys

:: [a] -> [a] -> [a]


= ys
= x:(xs++ys)

genericLength
genericLength

:: Num a => [b] -> a


= foldl' (\n _ -> n + fromInteger 1) (fromInteger 0)

length
length
(!!)
(x:_) !! 0
(_:xs) !! (n+1)

-- append lists. Associative with


-- left and right identity [].

:: [a] -> Int


-- calculate length of list
= foldl' (\n _ -> n+1) 0
:: [a] -> Int -> a
= x
= xs !! n

-- xs!!n selects the nth element of


-- the list xs (first element xs!!0)
-- for any n < length xs.

176

iterate
iterate f x

:: (a -> a) -> a -> [a] -- generate the infinite list


= x : iterate f (f x) -- [x, f x, f (f x), ...

repeat
repeat x

:: a -> [a]
= xs where xs = x:xs

cycle
cycle xs

:: [a] -> [a]


-- generate the infinite list
= xs' where xs'=xs++xs'-- xs ++ xs ++ xs ++ ...

copy
copy n x

:: Int -> a -> [a]


-- make list of n copies of x
= take n xs where xs = x:xs

nub
nub []
nub (x:xs)

:: Eq a => [a] -> [a]


-- remove duplicates from list
= []
= x : nub (filter (x/=) xs)

reverse
reverse

:: [a] -> [a]


= foldl (flip (:)) []

elem, notElem
elem
notElem

:: Eq a => a -> [a] -> Bool


= any . (==)
-- test for membership in list
= all . (/=)
-- test for non-membership

maximum, minimum :: Ord a => [a] -> a


maximum
= foldl1 max
minimum
= foldl1 min

-- generate the infinite list


-- [x, x, x, x, ...

-- reverse elements of list

-- max element in non-empty list


-- min element in non-empty list

concat
concat

:: [[a]] -> [a]


= foldr (++) []

-- concatenate list of lists

transpose
transpose

:: [[a]] -> [[a]]


-- transpose list of lists
= foldr
(\xs xss -> zipWith (:) xs (xss ++ repeat []))
[]

-- null provides a simple and efficient way of determining whether a given


-- list is empty, without using (==) and hence avoiding a constraint of the
-- form Eq [a].
null
null []
null (_:_)

:: [a] -> Bool


= True
= False

-- (\\) 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.
(\\)
(\\)

:: Eq a => [a] -> [a] ->


= foldl del
where []
`del` _
(x:xs) `del` y
| x == y
| otherwise

[a]
= []
= xs
= x : xs `del` y

-- map f xs applies the function f to each element of the list xs returning


-- the corresponding list of results. filter p xs returns the sublist of xs
-- containing those elements which satisfy the predicate p.
map

:: (a -> b) -> [a] -> [b]

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]

= f (...(f (f a x1) x2)...) xn


= (...((a `f` x1) `f` x2)...) `f` 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)

:: (a -> a -> a) -> [a] -> a


= foldl 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

:: (a -> b -> a) -> a -> [b] -> [a]


= q : (case xs of
[]
-> []
x:xs -> scanl f (f q x) xs)

scanl1
scanl1 f (x:xs)

:: (a -> a -> a) -> [a] -> [a]


= scanl f x xs

scanl'
scanl' f q xs

:: (a -> b -> a) -> a -> [b] -> [a]


= q : (case xs of
[]
-> []
x:xs -> strict (scanl' f) (f q x) 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)

:: (a -> a -> a) -> [a] -> a


= x
= f x (foldr1 f 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

:: (a -> a -> a) -> [a] -> [a]

178

scanr1 f [x]
scanr1 f (x:xs)

= [x]
= f x q : qs
where qs@(q:_) = scanr1 f xs

-- List breaking functions:


--take n xs
returns the first n elements of xs
-drop n xs
returns the remaining elements of xs
-splitAt n xs
= (take n xs, drop n xs)
--takeWhile p xs returns the longest initial segment of xs whose
-elements satisfy p
-dropWhile p xs returns the remaining portion of the list
-span p xs
= (takeWhile p xs, dropWhile p xs)
--takeUntil p xs returns the list of elements upto and including the
-first element of xs which satisfies p
take
take 0
_
take _
[]
take (n+1) (x:xs)

::
=
=
=

Int -> [a] -> [a]


[]
[]
x : take n xs

drop
drop 0
xs
drop _
[]
drop (n+1) (_:xs)

::
=
=
=

Int -> [a] -> [a]


xs
[]
drop n xs

splitAt
::
splitAt 0
xs
=
splitAt _
[]
=
splitAt (n+1) (x:xs) =

Int -> [a] -> ([a], [a])


([],xs)
([],[])
(x:xs',xs'') where (xs',xs'') = splitAt n xs

takeWhile
::
takeWhile p []
=
takeWhile p (x:xs)
| p x
=
| otherwise =

(a -> Bool) -> [a] -> [a]


[]

takeUntil
takeUntil p []
takeUntil p (x:xs)
| p x
| otherwise

x : takeWhile p xs
[]

:: (a -> Bool) -> [a] -> [a]


= []
= [x]
= x : takeUntil p xs

dropWhile
::
dropWhile p []
=
dropWhile p xs@(x:xs')
| p x
=
| otherwise =

(a -> Bool) -> [a] -> [a]


[]

span, break
::
span p []
=
span p xs@(x:xs')
| p x
=
| otherwise =
break p
=

(a -> Bool) -> [a] -> ([a],[a])


([],[])

dropWhile p xs'
xs

let (ys,zs) = span p xs' in (x:ys,zs)


([],xs)
span (not . p)

-- Text processing:

179

-------

lines s
words s
unlines ls
unwords ws

returns the list of lines in the string s.


returns the list of words in the string s.
joins the list of lines ls into a single string
with lines separated by newline characters.
joins the list of words ws into a single string
with words separated by spaces.

lines
lines ""
lines s

:: String -> [String]


= []
= l : (if null s' then [] else lines (tail s'))
where (l, s') = break ('\n'==) s

words
words s

:: String -> [String]


= case dropWhile isSpace s of
"" -> []
s' -> w : words s''
where (w,s'') = break isSpace s'

unlines
unlines

:: [String] -> String


= concat . map (\l -> l ++ "\n")

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

:: Ord a => [a] -> [a]


= foldr insert []

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)

:: Ord a => [a] -> [a]


= []
= qsort [ u | u<-xs, u<x ] ++
[ x ] ++
qsort [ u | u<-xs, u>=x ]

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.

Los grafos cclicos se utilizan a

menudo para representar de manera eficiente funciones recursivas.


El grafo est formado a su vez, por un conjunto de nodos y un conjunto de vrtices dirigidos (tienen direccin,
la cual se considera en orden descendente, a menos que a travs de flechas se indique lo contrario) que
unen a cada par de nodos. En el clculo lambda, los nodos de un grafo representan la construccin bsica.
La Figura B.1, ilustra la definicin del nodo en un grafo en el cul la mayora de los campos (todas las refs)
son actualizables, ya que el proceso de reduccin modifica al grafo en su lugar.

Figura B.1. Estructura de nodos en un grafo (en lenguaje ML).

Se pueden encontrar los siguientes tipos de nodos (ver Figura B.1):


AppG. Este nodo representa una aplicacin, sus hijos izquierdo y derecho son apuntadores a los campos
rand y rator respectivamente. Un indicador (bandera subbed) se pone en true cuando un nodo es la raz de un

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.

Esquema del interpretador de grafos: TIGRE


Reduccin de grafos. Las principales fuentes de ineficiencia en la mayora de los reductores de grafos
consisten en el anlisis tanto de la columna izquierda (ver Figura 3.14 (a)) como el de etiquetas en los nodos.
Si estos costos se reducen o se eliminan, es posible obtener incrementos sustanciales en velocidad. En esta
seccin se muestran algunos mecanismos implementados en la reduccin de grafos para evitar estas
deficiencias.

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

Figura B.2 Estructura de anlisis de etiqueta [Peyton, 1987].

El mecanismo

propuesto por Kopman (1989),

realiza una mejora al recorrido del grafo mediante la

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.

Figura B.5. Grafo con etiquetas eliminadas.

El enfoque de manejo de grafos mencionado es el utilizado para la implementacin en lenguaje C del


interpretador TIGRE [Koopman, 1989]. Existe una mejora considerable, equivalente a casi el doble de la
velocidad, si se utiliza el lenguaje de ensamble VAX en combinacin con el cdigo en C. Esta ganancia se
obtiene al explotar el soporte hardware que en materia de manejo de grafos tienen la mayora de los
procesadores.

El grafo genrico mostrado en la Figura B.6, es formado al iniciar el recorrido en la


encuentra

columna que se

ms a la izquierda, colocando apuntadores en la pila de los nodos ancestros. Cuando un

combinador es encontrado en el grafo, se ejecuta un cdigo relacionado con su implementacin. La estructura


de datos controla la ejecucin del programa. Un aspecto significativo es que la estructura de datos es en s
misma un programa con dos tipos de instrucciones: apuntadores y combinadores.

Bajo este enfoque, la reduccin de grafos, es esencialmente un proceso de interpretacin de un programa


que reside en la pila del nodo. En otras palabras, el grafo es un programa que consiste principalmente en
llamadas a subrutinas, las cuales llaman a otras subrutinas, y as sucesivamente hasta que algn otro cdigo
es encontrado.

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).

Figura B.7. Ejemplo de punteros reemplazados por llamadas a subrutinas.

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.

Cuando un nodo apunta a un combinador, el cdigo en ensamblador simplemente inicia la ejecucin de su


cdigo, colocando la direccin de retorno en la pila, apuntando a los nodos del lado derecho del nodo padre.
Cuando los nodos del grafo han sido reescritos, solo los valores de apuntadores, los cuales tienen un tamao
de 32 bits en el ensamblador VAX, necesitan reescribirse.

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

Figura C.1. Proceso de reduccin de grafo aplicando punteros.

189

You might also like