Apuntes de Tipos Abstractos de Datos Juan M. Molina Bravo curso 2001-2002 0-2 Capı́tulo 1 Introducción a la Programación Basada en Tipos Abstractos de Datos. 1.1 Diseño basado en ttaadd Los sistemas de software se utilizan para modelar situaciones y resolver problemas que se presentan en distintos ámbitos (Matemáticas, Fı́sica, Ingenierı́a, Gestión, ...); por lo que dichos sistemas vienen a ser representaciones de otros sistemas reales o ideales que pueden alcanzar una gran complejidad. Por ello, la construcción de sistemas de software se puede considerar una actividad propia de una ingenierı́a, que requiere los mismos pasos (diseño, implementación, prueba y mantenimiento) que la construcción de cualquier sistema fı́sico complejo; por lo que para poder construir un sistema de software de una cierta envergadura será necesario aplicar mecanismos de abstracción que permitan controlar la complejidad desde el momento del diseño para evitar problemas en las fases posteriores. Como sabemos, en general, la abstracción de un sistema es una descripción simplificada del mismo en función de unos aspectos que se consideran más relevantes y en detrimento de otros menos relevantes y, en particular, la abstracción de un sistema de software supone la aplicación de alguna técnica de abstracción que determina los aspectos que se deben abstraer (procedimientos de actuación, comportamientos funcionales, organización de los datos, estructura global del sistema) y que conduce a distintos métodos de diseño. Las técnicas de abstracción han evolucionado en el sentido de alejar los elementos que aparecen en los sistemas de software de las nociones propias de las máquinas sobre las que se implantan dichos sistemas, aproximándolos a las nociones propias de 1-3 1-4 CAPÍTULO 1. PROGRAMACIÓN BASADA EN TTAADD los dominios en los que se presentan las situaciones que se modelan. Una de estas técnicas, centrada en las abstracciones de datos, conduce al método de diseño basado en tipos abstractos. Este método consiste en • identificar los distintos tipos de datos que interviene en el sistema ası́ como la función principal de dicho sistema, cuando exista, • caracterizar cada tipo de datos en función de las operaciones que se puedan realizar con los objetos de los distintos tipos (haciendo abstracción de sus representaciones concreta), • componer el sistema utilizando libremente objetos de los tipos definidos junto con sus operaciones caracterı́sticas y, • finalmente, implementar cada uno de los tipos utilizados. Para que esta forma de diseño sea eficaz es necesario controlar la corrección del sistema durante su construcción, lo que requiere evitar ambigüedades en las caracterizaciones y errores en las implementaciones. 1.2 Objetivo del curso El propósito último de esta asignatura será construir programas correctos para tareas especı́ficas adoptando una disciplina de diseño basada en el uso de tipos abstractos de datos; es decir, tipos de datos procedentes de los dominios en los que se plantean las tareas y no necesariamente incorporados a los lenguajes de programación al uso. Para ello necesitaremos un marco adecuado en el que se pueda expresar una tarea y verificar si un determinado programa la cumple o no. Para especificar tareas partiremos de la idea de programa imperativo como transformador de estados, según la cual la actuación de un programa se puede resumir en la transformación de un estado, dado por unos valores iniciales de sus variables, en otro, dado por los valores finales de dichas variables. Para verificar que un programa realiza una tarea correctamente necesitamos disponer de una serie de reglas de comportamiento para las distintas construcciones del lenguaje en el que esté codificado el programa que nos indiquen cómo afectan estas construcciones al estado de un programa. Por otro lado, en la programación de alto nivel, próxima a los dominios de los problemas, las variables que intervienen en un programa generalmente representan objetos abstractos y las tareas se fijan con ayuda de los valores que pueden tomar estas variables, es decir, valores abstractos. Pensemos en un sencillo programa para calcular raı́ces cuadradas: su tarea será partir de un número natural N , como valor de una variable de entrada X, y calcular su raı́z cuadrada positiva, como valor de una variable 1.2. OBJETIVO DEL CURSO 1-5 de salida Y . Esta tarea se puede resumir diciendo que el programa debe transformar un estado caracterizado por la relación {X = N ∧ N ≥ 0} en otro caracterizado por √ {Y = + N }. Como vemos, el estado de partida se caracteriza mediante un valor numérico asignado a una variable X —aunque sabemos que la variable no albergará tal valor abstracto, sino una representación en código máquina— y el estado final se caracteriza además con la ayuda de una función abstracta definida en el álgebra de los números naturales. Según esto necesitaremos disponer de valores abstractos para todos aquellos tipos con los que pueda trabajar un programa, ası́ como de funciones definidas sobre ellos y propiedades dadas generalmente mediante ecuaciones. El marco que buscamos nos lo puede proporcionar la lógica de predicados con varios universos debidamente extendida para que pueda tratar con sentencias de corrección de programas. En esta versión de la lógica de predicados se pueden fijar varios universos, ası́ como distintas funciones y predicados relativos a entes de dichos universos. La idea subyacente es que los universos o dominios sirven para clasificar los términos sobre los que se va a razonar, las operaciones sirven para construir términos (que representan entes) de los distintos universos mientras que los predicados sirven para expresar relaciones entre términos que se pueden cumplir o no. Para estudiar la corrección de un programa • Se fijará un lenguaje lógico donde los universos representarán los distintos tipos de datos que intervienen, las funciones servirán para simbolizar operaciones con los datos y los predicados propiedades de dichas operaciones, siempre a nivel abstracto. • Además este lenguaje lógico se ampliará con la introducción de las variables del programa (cuyos valores abstractos representan los estados de dicho programa) y con las construcciones propias del lenguaje de programación, para construir términos de un universo especial: el universo de programas. • También se introducirá un predicado especial, {P }Π{Q}, para construir sentencias de corrección parcial o tripletas de Hoare, que permite expresar propiedades o caracterizaciones P y Q de los estados de un programa Π al comienzo y al final de su ejecución. Estos predicados se utilizan para establecer las “tareas” que deben realizar los programas, mientras que las caracterizaciones de los estados se enuncian como relaciones entre los valores abstractos de ciertas variables de dichos programas. Del estudio y manipulación de estas sentencias se ocupa la primera parte de la asignatura relativa a la especificación y verificación de programas. 1-6 CAPÍTULO 1. PROGRAMACIÓN BASADA EN TTAADD De lo que acabamos de decir, se deriva también la necesidad de disponer de descripciones abstractas de los tipos de datos con los que se vaya a operar (pilas, colas, árboles, ...) con sus propiedades y funciones, tal como ocurre con tipos más conocidos: los tipos numéricos, cuyas propiedades se establecen mediante ecuaciones algebraicas. De esto se ocupa la segunda parte de la asignatura relativa a la especificación de tipos abstractos de datos, donde se estudiarán las propiedades de distintos tipos abstractos, con cuyas representaciones operan los programas, para poder expresar propiedades de los estados de dichos programas. Por último, las fórmulas del cálculo de predicados y las fórmulas de corrección deben interpretarse sobre estructuras algebraicas para darles sentido. De hecho, las representaciones en máquina de los distintos tipos de datos se puede suponer que definen una estructura algebraica concreta. Pero para poder razonar a partir de las especificaciones de tipos abstractos sin tener que recurrir a ningún modelo concreto necesitaremos un cálculo deductivo consistente que nos permita obtener deducciones en la seguridad de que se cumplirán en cualquier modelo. De la misma forma para poder hacer deducciones sobre el comportamiento de los programas tendremos que ampliar el cálculo deductivo anterior con reglas especializadas para la manipulación de sentencias de corrección, ligadas a las distintas construcciones del lenguaje de programación, que son las que permiten la verificación.