Lenguajes de Programación Capítulo 2. Valores y Tipos Carlos Ureña Almagro Curso 2011-12 Contents 1 Introducción 2 2 Tipos Primitivos 6 2.1 Tipo Lógico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 Naturales y Enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.4 Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.5 Tipos enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3 Tipos Compuestos 14 3.1 Producto Cartesiano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.2 Unión y unión disjunta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.3 Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4 Tipos recursivos 30 4.1 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.2 Árboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5 Equivalencia de tipos 34 1 LP (11-12) 2. Valores y Tipos 6 El principio de completitud de tipos creado October 10, 2011 35 página 2.2 / 35 LP (11-12) 2. Valores y Tipos 1 Introducción El concepto de Valor. Un valor es una entidad que: • Obtenemos como resultado de evaluar una expresión, o • Podamos almacenar, o • Incorporar a una estructura de datos, o • Pasar como argumento a subprogramas, o • Obtener como resultado de una función El concepto de Valor. En definitiva, un valor es cualquier entidad que pueda ser procesada de alguna manera mediante las instrucciones que cada lenguajes de programación incorpore. Ejemplos. Valores en Pascal (1) • Valores primitivos (lógicos, caracteres, enumerados, enteros y reales) • Valores compuestos (registros, arrays, conjuntos y ficheros) • Punteros Ejemplos. Valores en Pascal (2) • Referencias a variables (únicamente como parámetros a subprogramas) • Subprogramas (procedimientos y funciones) Ejemplos. Valores en C. • Valores primitivos (caracteres, enumerados, enteros y reales) • Valores compuestos (registros, arrays) • Punteros • Subprogramas (funciones) creado October 10, 2011 página 2.3 / 35 LP (11-12) 2. Valores y Tipos El concepto de tipo Para hacer más tratable el conjunto de todos los valores posibles en cada lenguaje, clasificamos los valores en tipos. En principio, un tipo es, por tanto, un conjunto de valores. Sin embargo, y como veremos, un tipo es algo más. El concepto de tipo Normalmente, la agrupación de valores para formar tipos se hace teniendo en cuenta: • un modelo matemático común • la existencia de un conjunto común de operaciones aplicables a todos los valores del tipo. • la existencia de un método común para asociar una representación como secuencia de bits a cada elemento del conjunto. Elementos de un tipo Un tipo es un conjunto de valores con: • un modelo matemático asociado (un conjunto S) • un conjunto de operaciones bien definido, cada una de ellas con un modelo matemático asociado (una función) • un conjunto de algoritmos que operan sobre las secuencias e implementan las operaciones anteriores. • una función (matemática) de representación que asocia secuencias de bits a los valores del conjunto S Representación de valores Para cada tipo A existirá una función matemática, biunívoca, bien definida, que asocia, a cada valor posible, su representación como una única secuencia finita de dígitos binarios. Representación de valores • A la función se le llamará repr A , al conjunto de representaciones posibles se le llamará R A • La función es biyectiva entre A y R A creado October 10, 2011 página 2.4 / 35 LP (11-12) 2. Valores y Tipos Representaciones ambigüas • Cada valor de A tiene asociado un único valor de R A • Una representación que no cumple esto es ambigua, ya que existirá algún valor con un conjunto de representaciones posibles. Representación de valores Valores distintos deben tener representaciones distintas: x 6= y =⇒ repr A ( x ) 6= repr A (y) 1 Esto implica que existe la función repr − A , es decir, que la interpretación de cada secuencia de bits en R A es un único valor de A. Ejemplo de representación Números naturales del 0 al 7: Cada uno se puede representar como una secuencia de tres bits, el más significativo primero. Ejemplo de Representación. Números naturales del 0 al 7 (A), y sus correspondientes representaciones (R A ) Número de elementos y tamaño Sea A un conjunto cualquiera: • El número de valores distintos en A se nota como car ( A) (por cardinalidad) • Si las representaciones son todas del mismo tamaño, el número de bytes que tiene cada representación se nota como tam( A). creado October 10, 2011 página 2.5 / 35 LP (11-12) 2. Valores y Tipos Operaciones sobre los valores y su implementación • Para cada tipo, existirá un conjunto de operaciones matemáticas (funciones) bien definidas, que actúan sobre dichos valores. • Cada operación definida sobre los valores se corresponde (biunívocamente) con una operación equivalente definida sobre las representaciones de los valores. Ejemplo de Operación (sumar 1) y su implementación Ejemplo de operación (sumar 1). f : A → A i → f (i ) = (i + 1) mod 8 g : RA → RA ( a, b, c) → g( a, b, c) = ( a0 , b0 , c0 ) donde: a0 = a xor (b and c) b0 = b xor c c0 = no (c) Implementación válida creado October 10, 2011 página 2.6 / 35 LP (11-12) 2. Valores y Tipos Dada una operación f :A→A cualquiera, g será una implementación válida de f , si y solo si para todo valor x en A se cumple que: repr A ( f ( x )) = g(repr A ( x )) Clasificación de los tipos • Tipos primitivos: lógicos, caracteres, naturales y enteros, reales, tipos enumerados • Tipos compuestos: producto cartesiano, unión, aplicaciones, conjuntos de partes • Tipos recursivos Descriptores de tipo • Un descriptor de tipo es una frase (un trozo de texto) del lenguaje que describe un tipo de datos • Hay descriptores de una sola palabra (tipos simples) hasta descriptores de cientos o miles de líneas (por ejemplo, la definición de clases) 2 Tipos Primitivos Tipos Primitivos • Son los tipos más simples, en el sentido que contienen valores que no se pueden descomponer • Lo anterior implica que cada valor de uno de estos tipos es tratado como un todo, no se puede acceder a una de sus partes independientemente de las otras. 2.1 Tipo Lógico Tipo Lógico • Es el más sencillo de los tipos simples. • Es el tipo formado por los dos valores true y false creado October 10, 2011 página 2.7 / 35 LP (11-12) 2. Valores y Tipos • El conjunto está dotado de las operadores lógicos and, or, not • Presente en la mayoría de los lenguajes (Pascal, Ada, C++, Java,C#, python) Representación ambigua de valores lógicos en C • El lenguaje C no contempla este tipo • Es usual en C el recurrir a otros tipos (enteros), para representar los valores lógicos. • La representación basada en enteros es ambigua, lo cual hace fácil cometer errores. Representación ambigua de valores lógicos en C Existe valores que no son ni true ni false. typedef i n t logico ; const logico true = 1 ; const logico f a l s e = 0 ; logico a = 5 ; .... i f ( a != t r u e ) p r i n t f ( " a es f a l s e " ) ; El tipo lógico en varios lenguajes: Leng. Ada C++ Java C# Python 2.2 Nombre Boolean bool boolean bool bool Valores True, False true, false true, false true, false True, False Caracteres Caracteres • Los valores de este tipo son el conjunto de caracteres imprimibles en pantalla, impresora, o cualquier dispositivo de salida. creado October 10, 2011 página 2.8 / 35 LP (11-12) 2. Valores y Tipos • Usualmente se recurre al código ASCII, que define un conjunto de 128 caracteres básicos, y su forma de representarlos en 1 byte. Los lenguajes Ada, Pascal, C usan el código ASCII Códigos ASCII y Unicode • El código ASCII esta limitado: solo contiene caracteres occidentales. Además, hay 128 códigos (del 128 al 255) con varias interpretaciones posibles, según el contexto. • Los lenguajes modernos (Java, C#) recurren a la representación por el estándar UNICODE, de 2 bytes por carácter, y con caracteres arábigos, chinos, cirílicos, etc.. El tipo carácter en varios lenguajes Leng. Ada C++ Java C# Nombre Character char char char (en python no existe este tipo) 2.3 Naturales y Enteros Naturales y Enteros • Estos tipos son subconjuntos finitos de los enteros y naturales. • Estos subconjuntos contienen rangos de valores consecutivos, normalmente incluyendo al cero. • La representación suele ser simplemente en binario (naturales) o en complemento a dos (enteros). Longitud predeterminada • Algunos lenguajes fijan la longitud (en bits) de la representación de estos tipos • Se prima ante todo la portabilidad, se pierde eficiencia. Longitud predeterminada En estos lenguajes, los rangos de valores de los tipos de datos enteros son conocidos a priori y fijos, determinados en la especificación del lenguaje. creado October 10, 2011 página 2.9 / 35 LP (11-12) 2. Valores y Tipos Longitud dependiente de la implementación • Otros lenguajes no predeterminan la longitud de la representación (se permite que dependa de la implementación, S.O., hardware, etc?) • Se permite mejorar la eficiencia, a costa de más complicado lograr portabilidad. Longitud dependiente de la implementación En estos casos, para lograr portabilidad deben de existir construcciones del lenguaje que permiten averiguar el rango de valores asociado a cada tipo. (normalmente esto ocurre en tiempo de compilación) Longitud y formato de la representación: • Los lenguajes Ada, C y C++ no fijan la longitud (aunque se establecen algunas restricciones) • Los lenguajes modernos (Java y C#) fijan la longitud. Tipos enteros en C/C++ Nombre signed char [signed] short [int] [signed] int [signed] long [int] R. (C) SCHAR_MIN - SCHAR_MAX SHRT_MIN - SHRT_MAX INT_MIN - INT_MAX LONG_MIN - LONG_MAX R.mín. (−27 , 27 ) (−215 , 215 ) (−215 , 215 ) (−231 , 231 ) • El número de bits de la representación debe ser no decreciente • El rango de valores debe contener al rango mínimo indicado Tipos naturales en C/C++ Nombre unsigned unsigned unsigned unsigned char short [int] [int] long [int] R. (C) 0 - UCHAR_MAX 0 - USHRT_MAX 0 - UINT_MAX 0 - ULONG_MAX R.mín. [0, 28 ) [0, 216 ) [0, 216 ) [0, 231 ) • El número de bits de la representación debe ser no decreciente • El rango de valores debe contener al rango mínimo indicado creado October 10, 2011 página 2.10 / 35 LP (11-12) 2. Valores y Tipos Tipos enteros y naturales en C/C++ • char puede ser equivalente a unsigned char o a signed char, dependiendo de la implementación • El valor mínimo y máximo indicado son constantes definidas en limits.h. • En C++, se puede acceder mediante funciones a los valores mínimo y máximo: – std::numeric_limits<T>.min() – std::numeric_limits<T>.max() donde T es uno de los nombres de tipo indicados más arriba. (estas funciones están definidas en el archivo limits) Tipos enteros en Ada Nombre Short_Integer Integer Long_Integer Disponible opcionalmente siempre opcionalmente • Los longitudes en bits de cada tipo no están fijas, pero deben ser no decrecientes. Los rangos deben de estar centrados en cero. • El valor mínimo y máximo se puede acceder con T’First y T’Last, donde T designa el nombre de cualquiera de estos tipos. Tipos naturales en Ada Nombre Natural Positive Rango 0 - Integer’Last 1 - Integer’Last • Como se observa, el rango de valores está ligado al rango de valores de los enteros. Tipos enteros en Java Nombre byte short int long creado October 10, 2011 Rango [−27 , 27 ) [−215 , 215 ) [−231 , 231 ) [−263 , 263 ) página 2.11 / 35 LP (11-12) 2. Valores y Tipos • El numero de bits esta fijado por los rangos indicados. • La representación debe ser en complemento a dos. Tipos enteros en C# Nombre sbyte short int long Rango [−27 , 27 ) [−215 , 215 ) [−231 , 231 ) [−263 , 263 ) • El numero de bits esta fijado por los rangos indicados. • Los valores minimo y máximo se pueden acceder como T’MinValue y T’MaxValue, donde T designa el nombre de cualquiera de estos tipos. Tipos naturales en C# Nombre byte ushort uint ulong Rango [0, 28 ) [0, 216 ) [0, 232 ) [0, 264 ) • El numero de bits esta fijado por los rangos indicados. • Los valores mínimo y máximo se pueden acceder como T.MinValue y T.MaxValue, donde T designa el nombre de cualquiera de estos tipos. Tipos enteros en Python No hay naturales, y hay dos tipos de enteros: • Enteros (plain integers): coinciden con los long int de C, y por lo tanto, el rango asociado debe ser, como mínimo, desde −23 1 hasta 23 1 • Enteros largos (long integers): de rango ilimitado (representados en memoria como una secuencia de dígitos de longitud suficiente para representar un número) (en este lenguaje no existe descriptores de tipos pues no hay declaración de variables) creado October 10, 2011 página 2.12 / 35 LP (11-12) 2. Valores y Tipos 2.4 Reales Números reales • Estos tipos representan subconjuntos finitos del conjunto de los números reales. • Se suele usar la representación en coma flotante, por tanto con precisión adaptativa (mayor en el entorno de 1.0, decreciente al crecer el valor absoluto de los números). Números Reales • Al igual que ocurre con los enteros, la representación puede estar preestablecida en el lenguaje (Java, C#), o no (Ada, C/C++). • Si no está preestablecida, se usa la que mejor se adapta a la FPU de la CPU (en la actualidad suele ser el formato IEEE-754) Reales en C/C++ • Se contemplan: float, double, long double • La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decrecientes. Reales en Ada • Se contemplan: Float, (y, opcionalmente: Short_Float, Long_Float) • La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decrecientes. Reales en Java y en C# Ambos lenguajes adoptan el mismo convenio: • El formato obligado es el establecido en la norma IEEE 754. • Se contemplan: – float (32 bits) – double (64 bits). creado October 10, 2011 página 2.13 / 35 LP (11-12) 2. Valores y Tipos Reales en Python • En este lenguaje solo hay un tipo para los números reales, cuya precisión y rango está dictado por el tipo flotante de máxima precisión que este directamente soportado por el hardware donde se ejecutan los programas. • En la práctica lo anterior implica que el tipo tiene la precisión y rango de los double de C (en implementaciones del lenguaje basadas en C, que es lo más frecuente). 2.5 Tipos enumerados Tipos enumerados • En el programa se incluye, asociado cada tipo enumerado, una secuencia de identificadores distintos. • Cada identificador está biunivocamente asociado a un valor del tipo (representa el valor) Tipos enumerados • Un tipo enumerado se pueden implementar en un programa como un conjunto de identificadores asociados a constantes de tipo enteros. • Esta solución plantea problemas de ambigüedad de la representación y/o de la interpretación de los valores enteros Representación de tipos enumerados • La representación de los tipos enumerados suele estar basada en los tipos naturales o enteros • La selección de la representación se realiza automáticamente en tiempo de compilación, en función sobre todo del número de valores del tipo. Tipos enumerados en varios lenguajes • C (ANSI +) y C++ typedef enum { id1 , id2 ,. . . ,idn } idtipo ; • Ada type idtipo is { id1 ,id2 ,. . .,idn } ; creado October 10, 2011 página 2.14 / 35 LP (11-12) 2. Valores y Tipos • Java (JDK 1.5 +) enum idtipo { id1 ,id2 ,. . .,idn } ; • C# Enum idtipo { id1 ,id2 ,. . ., idn } ; • Python: no está incorporado (se rechazó una propuesta de hacerlo) 3 Tipos Compuestos Tipos compuestos y álgebra de conjuntos Los tipos compuestos se obtienen al aplicar varios operadores del álgebra de conjuntos a otros tipos. 1. Producto cartesiano: (conjunto de pares construido a partir de dos conjuntos) 2. Unión y unión disjunta: (conjunto unión de otros dos conjuntos) 3. Aplicaciones: (conjunto de aplicaciones de un conjunto en otro) 3.1 Producto Cartesiano Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el tipo C como: A× B C = En concreto, los valores de C son pares: C = { ( a, b) t.q. a∈A y b∈B} El producto cartesiano es asociativo, lo cual implica que puede extenderse de forma natural a secuencias de conjuntos: C = A1 × A2 × · · · × A n ( n > 1) Los elementos de C son ahora tuplas: C = { ( a1 , a2 , . . . , a n ) t.q. ai ∈ Ai (∀i = 1 . . . n) } • El producto cartesiano es una construcción fundamental para la creación de tipos. • Esta contemplado, de una forma u otra, en todos los lenguajes de programación. creado October 10, 2011 página 2.15 / 35 LP (11-12) 2. Valores y Tipos Operaciones sobre tuplas La operación fundamental sobre las tuplas es la consulta del i-ésimo elemento (Ci ) (existe una función distinta Ci para cada valor distinto de i, con i ∈ {1 . . . n} Ci ∈ A1 × A2 × · · · × An → Ai ( a1 , a2 , . . . , a n ) → ai Esta operación se corresponde con lo que en matemáticas se conoce como una función de proyección Representación de las tuplas La forma de representar un tupla es concatenar las representaciones de sus elementos. Supongamos que a ∈ A y b ∈ B, y que repr A ( a) = r a y repr B (b) = rb . En estas condiciones: repr A× B (( a, b)) = r a ◦ rb (aquí, ◦ es un operador que actua sobre dos secuencias de bits y produce otra secuencia que es la concatenación de las originales). Cardinalidad y tamaño del producto cartesiano En el caso del producto cartesiano, dados dos conjuntos cualesquiera A y B, se cumple que: car ( A × B) = car ( A) car ( B) tam( A × B) = tam( A) + tam( B) Conjuntos de tuplas en ML En algunos lenguajes se implementa el producto cartesiano tal cual: type A = i n t ∗ r e a l ; type C = i n t ∗ r e a l ∗ char ∗ i n t ; El problema es acceder a los elementos individuales de las tuplas (se accede con un índice numérico) Tuplas en Python • Las tuplas están directamente soportadas en este lenguaje, sus elementos son accesibles usando un valor numérico que comienza en cero. • Se incluye la tupla vacía • Las tuplas homogéneas pueden verse como arrays creado October 10, 2011 página 2.16 / 35 LP (11-12) 2. Valores y Tipos Registros y estructuras Lo más usual es etiquetar cada componente de las tuplas con un identificador: • Facilita las referencias a los elementos individuales de una tupla, independientemente de su posición, por tanto, mejora la facilidad de escritura y legibilidad de código, así como su fiabilidad. • Las etiquetas forman parte del tipo: facilita la comprobación de tipos. Tuplas semejantes con etiquetas distintas forman tipos distintos. Registros y estructuras en Ada type C i s record edad : I n t e g e r ; peso : F l o a t ; end r e c o r d ; • Aquí, C = Integer × Float. • En este caso, las etiquetas asociadas a las dos componentes de las tuplas son edad y peso, respectivamente Registros y estructuras en C La estructura similar a la anterior sería como sigue: struct C { int edad ; f l o a t peso ; } ; Nota: el nombre del tipo es struct C Registros y estructuras en C ANSI /C++ En estos lenguajes una estructura se puede declarar como sigue: typedef s t r u c t { int edad ; f l o a t peso ; } C ; creado October 10, 2011 página 2.17 / 35 LP (11-12) 2. Valores y Tipos Registros y estructuras C++ Adicionalemtne, en C++, se puede usar una forma de declaración de clase que equivale a una struct: class C { public : int edad ; f l o a t peso ; } ; Registros y estructuras en Java class C { p u b l i c i n t edad ; p u b l i c f l o a t peso ; } ; En este lenguaje se usa el mecanismo de definición de clases. Registros y estructuras en C# struct C { p u b l i c i n t edad ; p u b l i c f l o a t peso ; } ; Registros y estructuras en Python • Este tipo de datos no está soportado directamente en el lenguaje (ocurre lo mismo en otros lenguajes parecidos) • En las últimas versiones de Python, se puede usar el mecanismo de las clases, aunque hay que tener en cuenta que existe un único tipo (de nombre class) que comprende todas las estructuras posibles con cualquier conjunto de componentes. creado October 10, 2011 página 2.18 / 35 LP (11-12) 2. Valores y Tipos Ejemplo de clase como estructura en Python c l a s s Persona : edad = 0 peso = 0 . 0 nombre = " " alguien alguien alguien alguien = Persona ( ) . nombre = " J o s e Perez " . edad = 45 . peso = 8 2 . 5 p r i n t a l g u i e n . peso ∗ 2 3.2 Unión y unión disjunta Unión y unión disjunta • Estas construcciones son útiles cuando queremos considerar un valor que puede ser de un tipo entre varios. • Es útil cuando se busca eficiencia en uso de memoria. • Los lenguajes más modernos (Java, C#) no contemplan esto. • En Python y otros lenguajes, el sistema de tipos permite en la práctica uniones disjuntas de forma implícita aunque no haya un mecanismo explícito. Unión Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el conjunto C como la unión de ambos: C = A∪ B En concreto, los valores de C son los siguientes: C = { x t.q. x ∈ A ∨ x ∈ B } Una implementación bastante obvia de la unión consiste en representar cada elemento de la unión usando C usando alguna la representación repr A o repr B que corresponda. creado October 10, 2011 página 2.19 / 35 LP (11-12) 2. Valores y Tipos Unión: ambigüedad en la representación Si A y B no son disjuntos, puede ocurrir ambigüedad en la representación. • Si x ∈ A ∪ B, entonces podemos usar tanto repr A como repr B para representarlo. • En el lenguaje C/C++, esto no ocurre pues toda expresión tiene un tipo fijo conocido en tiempo de compilación (que determina la representación) luego todo valor producido al evaluar una expresión es de un tipo determinado. Unión: ambigüedad en la interpretación Puede ocurrir ambigüedad en la interpretación cuando los dos conjuntos R A y R B no son disjuntos. • Ocurre cuando existe alguna secuencia de bits que puede ser interpretable como representación de un A o un B. • En el lenguaje C/C++, este problema si puede darse Ambigüedad en la representación: esquema x ∈ A∩B creado October 10, 2011 repr A ( x ) 6= repr B ( x ) página 2.20 / 35 LP (11-12) 2. Valores y Tipos Unión. Ambigüedad en la interpretación. s ∈ R A ∩ RB 1 −1 a = repr − A ( s ) 6 = repr B ( s ) = b Unión en C En el lenguaje C se contempla la unión: union C { i n t edad ; f l o a t peso ; } ; Por si sola, esta construcción no es útil, esencialmente por los problemas de ambigüedad de la interpretación. Unión Disjunta Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el conjunto C como la unión disjunta de ambos: C = A + B = ({ f alse} × A) ∪ ({true} × B) es decir: C = creado October 10, 2011 { (b, x ) t.q. (b = f alse ∧ x ∈ A) ∨ (b = true ∧ x ∈ B) } página 2.21 / 35 LP (11-12) 2. Valores y Tipos Unión disjunta de varios conjuntos (1) • Los valores true y false actúan como etiquetas, que informan acerca de a que subconjunto de A + B (A ó B) pertenece cada valor. • Los conjuntos de etiquetas pueden tener un número arbitrario de valores, y por tanto la unión disjunta se puede extender a una serie de conjuntos. Unión disjunta de varios conjuntos (2) Supongamos, por ejemplo, que A = {1, 2} y B = {2, 3}. En este caso, el conjunto A + B es: {( f alse, 1), ( f alse, 2), (true, 2), (true, 3)} Unión disjunta de varios conjuntos (2) Podemos definir la unión disjunta de n conjuntos: A1 + A2 + · · · + A n = ({e1 } × A1 ) ∪ ({e2 } × A2 ) ∪ · · · ∪ ({en } × An ) donde E = { e1 , e2 , . . . , e n } es un conjunto cualquiera de n valores, que se usan como etiquetas (todos estos valores han de ser del mismo tipo) Representación de la unión disjunta. Cada valor de la forma (ei , a) ( con a ∈ Ai y ei ∈ E), perteneciente a una unión disjunta, se representa como una secuencia de bits obtenida al concatenar tres secuencias de bits: 1. La representación de ei 2. La representación de a 3. Una secuencia arbitraria de bits de relleno Representación de la unión disjunta Supongamos que U = A1 + A2 + · · · + An (siendo el conjunto de etiquetas E = {e1 , e2 , . . . , en } ), sea a ∈ Ai . En estas condiciones: reprU (ei , a) = repr E (ei ) ◦ repr Ai ( a) ◦ 0| .{z . . 0} n bits creado October 10, 2011 página 2.22 / 35 LP (11-12) 2. Valores y Tipos Donde n es el número de bits de relleno n = max (tam( A1 ), tam( A2 ), . . . , tam( An )) − tam( Ai ) que hace que el tamaño de la representación sea fijo Cardinalidad y tamaño de la unión disjunta Respecto al tamaño y el número de elementos, se cumple que: car ( A1 + · · · + An ) = car ( A1 ) + · · · + car ( An ) tam( A1 + · · · + An ) = tam( E) + max (tam( A1 ), . . . , tam( An )) Operaciones de la unión disjunta Es posible consular la etiqueta o el valor asociado (proporcionando un entero i que identifica el componente que queremos), de forma independiente: Cetiq ∈ A1 + · · · + An → E (d, x ) → d Ci ∈ A1 + · · · + An → Ai x si ei = d (d, x ) → error en otro caso Implementación de la unión disjunta En lenguajes sin unión disjunta, podemos implementarla combinando unión y producto cartesiano: A1 + · · · + A n ⊂ ( E × A1 ) ∪ ( E × A2 ) ∪ · · · ∪ ( E × A n ) = E × ( A1 ∪ A2 ∪ · · · ∪ A n ) nótese que este conjunto incluye pares donde el valor de la etiqueta no se corresponde con el tipo del dato. Unión disjunta: posibles problemas (1/3) El esquema anterior puede presentar problemas: • Las etiquetas permiten al programador resolver la ambigüedad en la interpretación de las representaciones, pero esto debe llevarse a cabo explícitamente en el programa, y por tanto puede hacerse erróneamente. creado October 10, 2011 página 2.23 / 35 LP (11-12) 2. Valores y Tipos Unión disjunta: posibles problemas (1/3) • Es posible producir (por error) alguno de los valores del conjunto anterior que no están en la unión disjunta. Las representaciones de estos valores se interpretan erróneamente. Unión disjunta: posibles problemas (1/3) • El conjunto de etiquetas puede tener un número de valores distinto del número de conjuntos que intervienen Unión disjunta en C/C++ Se debe combinar una struct y una union struct idtipo { E idetiqueta ; union { A1 id1 ; A2 id2 ; ··· An idn ; }; }; Ejemplo de unión disjunta en C struct T { int etiq ; union { f l o a t f ; / ∗ e t i q == 1 ∗ / c h a r c ; / ∗ e t i q == 2 ∗ / } ; } ; Implementación de la unión disjunta en Pascal Se establece una correspondencia entre valores de las etiquetas y tipos (no se comprueba en tiempo de ejecución) creado October 10, 2011 página 2.24 / 35 LP (11-12) 2. Valores y Tipos type idtipo = case idetiqueta : E of begin e1 : ( id1 : A1 ) ; e2 : ( id2 : A2 ) ; ··· en : ( idn : An ) end Ejemplo de unión disjunta en Pascal type T = case e t i q : I n t e g e r o f begin 1 : ( f : Real ) ; 2 : ( c : Char ) end Implementación de la unión disjunta en Ada Este lenguaje contempla explícitamente la unión disjunta, asegurando que en tiempo de ejecución nunca se producen los problemas que aparecen en C o Pascal. Implementación de la unión disjunta en Ada type idtipo ( idetiq : E [ := expr ] ) is record case idetiq is when e1 ==> id1 : A1 ; when e2 ==> id2 : A2 ; ··· when en−1 ==> idn−1 : An−1 ; [ when others ==> idn : An ; ] end case end record Ejemplo de unión disjunta en Ada creado October 10, 2011 página 2.25 / 35 LP (11-12) 2. Valores y Tipos type T ( e t i q : I n t e g e r case e t i q i s when 1 ==> when 2 ==> when o t h e r s ==> end case end r e c o r d 3.3 ) is record f : Real ; c : Char ; i : Integer ; Aplicaciones Aplicaciones Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el tipo C como el conjunto de todas las aplicaciones de A en B C = A → B En concreto, los valores de C son aplicaciones que tienen como dominio A, y como rango B Cardinalidad de A → B El número de aplicaciones posibles de A en B es car ( A → B) = car ( B)car( A) (al primer elemento de A se le puede asignar cualquiera de los car ( B) elementos de B, igual para el segundo elemento de A, el tercero, y así sucesivamente hasta car ( A) veces) Operaciones sobre las aplicaciones La operación esencial es la consulta de la imagen de un elemento del conjunto índice. C : ( A → B) × A → B ( g, a) → g( a) Representación de aplicaciones En un programa, una aplicación f ∈ A → B se puede representar como: • Un subprograma que acepta un parámetro de tipo A y devuelve un resultado de tipo B. (una función) • Una tupla de tantos elementos como valores tenga A. Cada elemento de la tupla tiene la imagen por f de un elemento de A. (un array, o formación, o vector, o matriz) creado October 10, 2011 página 2.26 / 35 LP (11-12) 2. Valores y Tipos Las aplicaciones como arrays (formaciones) Cuando se usan formaciones como medio de representación: • Al conjunto A se le suele llamar conjunto de índices, y a sus valores índices. • Este conjunto debe ser numerable, es decir, podemos escribir sus elementos como A = { a1 , a2 , . . . , a n } donde n = car ( A) (por ejemplo, esto implica que no pueden ser números reales). Las aplicaciones como arrays La representación de una aplicación f ∈ A → B es la concatenación de las representaciones de las imágenes por f de cada uno de los elementos de A repr A→ B ( f ) = repr B ( f ( a1 )) ◦ repr B ( f ( a2 )) ◦ · · · ◦ repr B ( f ( an )) Por tanto, se cumple que: tam( A → B) = tam( B)tam( A) Posibles conjuntos de índices: • El conjunto índice puede ser un rango de enteros, naturales, caracteres o un tipo enumerado • En estos casos se les suele llamar vectores, o arrays unidimensionales. Arrays en C/C++ En estos lenguajes, el conjunto de índices es un rango de naturales que comienza en cero (incluido) int char a [ 3 4 ] ; / / a r r a y de e n t e r o s , / / i n d i c e s d e l 0 a l 33 b [ 2 0 ] ; / / a r r a y de 20 c a r a c t e r e s los arrays se representan en memoria como una secuencia entradas consecutivas en posiciones consecutivas de memoria. creado October 10, 2011 página 2.27 / 35 LP (11-12) 2. Valores y Tipos Arrays en Java y C# En estos lenguajes, los arrays son tipos-referencia, lo cual (en este contexto) quiere decir que un array se representa como una referencia (posiblemente nula) a una zona de memoria donde se almacena el numero de entradas y a continuación las entradas consecutivas. i n t [ ] a = new i n t [ 3 4 ] ; / / a r r a y de e n t e r o s , / / i n d i c e s d e l 0 a l 33 c h a r [ ] b = new i n t [ 2 0 ] ; / / a r r a y de 20 c a r a c t e r e s Arrays en Ada El conjunto de índices puede ser un rango de valores de un tipo primitivo numerable (naturales, enteros, caracteres, lógicos, enumerados) type t1 i s array ( −2..45 ) o f Character ; t y p e t 2 i s a r r a y ( 0 . . 3 4 ) o f Persona ; type t3 i s array ( ’ a ’ . . ’ z ’ ) of Float ; t y p e DiasSem i s ( l u n e s , m ar tes , m i e r c o l e s , j u e v e s , v i e r n e s , sabado , domingo ) ; t y p e t 3 i s Array ( DiasSem ) o f I n t e g e r ; t y p e t 4 i s Array ( l u n e s . . v i e r n e s ) o f I n t e g e r ; se representan en memoria almacenando el tamaño seguido de la secuencia de entradas, almacenadas consecutivas. Arrays multidimensionales • El conjunto índice puede ser el producto cartesiano de n rangos de entre los citados en la transparencia anterior, posiblemente rango distintos de tipos distintos. Cada índice es por tanto una tupla. • Se les llama: matrices o arrays n-dimensionales. • Se les puede ver también como arrays cuyo conjunto rango es a su vez un array (arrays de arrays), ya que se cumple que: ( A × B) → C = A → ( B → C ) • La representación es la que se deduce de la igualdad anterior Arrays multidimensionales en C/C++ En estos lenguajes, un array multidimensional se debe implementar como un array de arrays creado October 10, 2011 página 2.28 / 35 LP (11-12) 2. Valores y Tipos int arr [ 5 ] [ 1 0 ] ; aquí arr es una variable de tipo array con 10 entradas: cada una de ellas es un array de 5 enteros. Todos los datos se almacenan de forma consecutiva en memoria. Arrays multidimensionales en Ada Se pueden tener arrays de arrays, o bien usar una construcción mas simple en el cual se expresa que los subíndices son tuplas. t y p e A r r a y 5 i s Array ( 0 . . 4 ) o f I n t e g e r ; t y p e A r r M u l t i d i m 1 i s Array ( 0 . . 9 ) o f A r r a y 5 ; t y p e A r r M u l t i d i m 2 i s Array ( 0 . . 9 , 0 . . 4 ) o f I n t e g e r ; estos tipos denotan arrays con 10 entradas: cada una de ellas es un array de 5 enteros. Arrays multidimensionales en Java y C# Se pueden declarar como arrays de arrays de la siguiente forma: i n t [ ] [ ] a = new i n t [ 1 0 ] [ 5 ] ; / / J a v a y C# este descriptor de tipo corresponde al tipo de los arrays con 10 entradas, siendo cada una de ellas un array de 5 enteros (nótese que los tamaños van al revés que en C) Arrays dinámicos En algunos casos, el conjunto de índices es heterogéneo, por ejemplo, podemos considerar todos los arrays unidimensionales con índices enteros ∞ [ ({0, 1, . . . , n − 1} → B) n =1 es decir, este tipo son todos los arrays de 0 elementos, o de 1 elementos, o de 2 elementos, etc... Arrays dinámicos • En estos casos, el tamaño de la representación no es igual para todas los arrays. Se les suele llamar arrays dinámicos • La representación incluye una componente que proporcione información sobre que valores son índices correctos (es decir, cual es la longitud del array). En caso contrario, ocurrirá una ambigüedad en la interpretación. creado October 10, 2011 página 2.29 / 35 LP (11-12) 2. Valores y Tipos • Para que una secuencia de este tipo pueda ser interpretada correctamente y de forma eficiente, su parte inicial debe codificar la longitud. • Existe una operación que consiste en consultar el número de elementos de un array Arrays dinámicos en C/C++ • En los lenguajes C/C++, estos arrays se representan como punteros a la secuencia de elementos. • No están dotados con información sobre la longitud, que debe de almacenarse por separado y ser tenida en cuenta • Esto provoca muchos problemas de fiabilidad por accesos fuera de los límites de los arrays • Existen librerías (como la STL) que si proporcionan arrays fiables Arrays dinámicos en Ada Un ejemplo de descriptor de tipo para arrays dinámicos de índices enteros y elementos de tipo carácter es este: t y p e A r r D i n i s Array ( I n t e g e r range <>) o f C h a r a c t e r ; • Se indica que el tipo comprende todos los arrays de caracteres cuyos índices son rangos finitos de enteros. • Al igual que los arrays normales, se pueden usar otros tipos como tipo índice. Arrays dinámicos en Java/C# Todos los arrays en Java y C# (al ser de tipos-referencia) pueden considerarse como dinámicos : char [ ] a ; a = new i n t [ 2 0 ] ; a = new i n t [ 3 0 ] ; / / e l a r r a y ’ a ’ ha cambiado de tamaño Arrays dinámicos dentados en Java y C# En estos lenguajes podemos tener arrays de arrays dinámicos en estos casos, los (sub-)arrays que forman el array global pueden tener distinta longitud (incluyendo longitud 0), se denominan arrays dentados (jagged arrays) char [ ] [ ] a ; / / a r r a y que c o n t i e n e 20 a r r a y s d i n á m i c o s de c a r a c t e r e s a [ 0 ] = new i n t [ 1 0 ] ; a [ 1 ] = new i n t [ 2 0 ] ; creado October 10, 2011 página 2.30 / 35 LP (11-12) 2. Valores y Tipos Arrays rectangulares en C# Este lenguaje incorpora una forma especifica de indicar que un array es multidimensional (al igual que los arrays multidimensionales de Ada o C++) en lugar de un array de arrays posiblemente dentado. char [ , ] a ; / / array rectangular a = new i n t [ 1 0 , 2 0 ] ; a = new i n t [ 2 0 , 1 ] ; se puede generalizar a mas de dos subindices. Las aplicaciones como diccionarios • Existen otras posibilidades para el conjunto índice, como las cadenas de caracteres (p.ej. en Perl). • En Python se pueden usar como conjunto de índices conjuntos heterogéneos de enteros, flotantes, cadenas o tuplas. • En estos casos no se usa la representación con arrays (dado que el conjunto de índices posibles tiene una cardinalidad elevada, y no todos ellos tienen asociada una imagen). La representación no tiene longitud fija. • A estas aplicaciones se les suele llamar diccionarios 4 Tipos recursivos • Los tipos recursivos son los basados en listas y árboles. • Tienen una representación con un tamaño que no es igual para todos los valores posibles. • Se les suele llamar estructuras de datos dinámicas 4.1 Listas El tipo recursivo más sencillo son las listas homogéneas de elementos. Si A es un tipo cualquiera, el conjunto de las listas de A es el menor conjunto L que cumple: L = {null } + ( A × L) Donde null es un valor especial, arbitrario, y donde menor se refiere a menor en el sentido de la inclusión de conjuntos. Si se cumple esto, decimos que: L = listas( A) La anterior definición de las listas implica que, si l ∈ listas( A) , entonces una (y solo una) de estas dos condiciones se deben cumplir: creado October 10, 2011 página 2.31 / 35 LP (11-12) 2. Valores y Tipos • l = ( f alse, null ) en este caso decimos que l es la lista vacía • l = (true, ( a, l 0 )), donde a ∈ A y l 0 ∈ listas( A) en este caso, decimos que: – a es la cabecera de l – l 0 es el resto de l Operaciones sobre listas Una de las operaciones más frecuentes sobre las listas es la consulta sobre si la lista es la lista vacía o no vacia ∈ listas( A) → { f alse, true} (b, x ) → not(b) Operaciones sobre listas Otras operaciones son la consulta de la cabecera y del resto: cab ∈ listas( A) − {null } → A (true, ( a, l )) → a resto ∈ listas( A) − {null } → A (true, ( a, l )) → l Listas en varios lenguajes En C++, Java y C# 1. En C++, la libreria STL incluye las plantillas para clases list, que es un tipo de contenedor (container class) que contiene una secuencia de valores arbitrarios. 2. En Java, existe la clase List como un tipo de colección (implementaciones del interfaz AbstractCollection en java.util), en concreto constituye una secuencia de referencias a instancias de cualquier clase. 3. En C#, existe la plantilla para clases List (en el namespace System.Collections.Generic) Listas en Python Este lenguaje incorpora las listas como tipo de datos, y permite expresiones de tipo lista. • Las listas son heterogéneas: sus elementos pueden ser de tipos distintos y arbitrarios creado October 10, 2011 página 2.32 / 35 LP (11-12) 2. Valores y Tipos • Las listas son accesibles con un índice numérico, que comienza en cero para el primer elemento. Esto le proporciona la posibilidad de ser usadas como arrays a todos los efectos. • Las expresiones de tipo lista se escriben usando corchetes que encierran secuencias de expresiones separadas por comas. Listas en Python Incorpora bastantes operaciones: 1. acceso de lectura/escritura a elementos arbitrarios 2. extracción de sub-listas (slices) 3. concatenación de listas para formar otras 4. inserción de elementos o listas en listas 4.2 Árboles Árboles binarios Sea A un conjunto cualquiera, el conjunto de los árboles binarios con elementos de tipo A, es el menor conjunto T que cumple: T = {null } + ( A × T × T ) En este caso, se escribe: L = arboles-bin( A) Árboles binarios Según la definición anterior, si t es un árbol binario con elementos de tipo A, solo una de estas dos condiciones se cumple: • t = ( f alse, null ) decimos que t es el árbol vacío • t = (true, ( a, tizq , tder )) decimos que: – a es la raíz de t, – tizq es el subárbol izquierdo de t – tder es el subárbol derecho de t creado October 10, 2011 página 2.33 / 35 LP (11-12) 2. Valores y Tipos Árboles n-arios Un árbol binario, si no está vacío, tiene dos subárboles. Esta definición puede generalizarse a árboles con n subárboles (árboles n-arios). T = {null } + ( A × |T × T ×{z· · · × T}) n Finalmente, podemos considerar árboles donde el número de subárboles no es fijo T = {null } + ( A × listas( T )) Operaciones sobre árboles Las operaciones básicas más frecuentes son: • Consulta sobre si un árbol está vacío o no • Consulta del elemento de A en la raíz de un árbol (si no está vacío) • Consulta del i-ésimo subarbol de un árbol (si no está vacío) Representación de tipos recursivos: Hay dos opciones: • Usando secuencias contiguas de bits, de tamaño no fijo. • La representación en memoria suele hacerse por bloques de bits no contiguos, para mejorar la eficiencia en tiempo de las actualizaciones. (se verá más adelante) Tipos recursivos en los lenguajes de programación • Los lenguajes imperativos no contemplan estos tipos (aunque se pueden y suelen implementar usando tipos definidos por el usuario y/o clases, a partir TADs) • Los lenguajes funcionales suelen incorporar construcciones explícitas para listas y árboles. Tipos recursivos en los lenguajes de progr. Ejemplo en ML Supongamos que queremos designar con arbol a los árboles binarios de enteros (int). Se declararía en ML como: creado October 10, 2011 página 2.34 / 35 LP (11-12) 2. Valores y Tipos datatype arbol = vacio | novacio of ( int ∗ arbol ∗ arbol ) ; 5 Equivalencia de tipos • En los lenguajes de programación es necesario saber cuando dos valores pertenecen a tipos equivalentes o no • La forma de determinar si dos tipos son equivalentes constituye el mecanismo de equivalencia de tipos de un lenguaje. Equivalencia estructural. • Dos tipos A y B son equivalentes estructuralmente si contienen los mismos valores (es decir A = B) o bien existe una correspondencia biunívoca trivial entre ambos conjuntos de valores • En estos casos escribimos A ≡ B Equivalencia estructural. Existirá una correspondencia trivial cuando: • Ambos tipos son primitivos, y contienen los mismos valores • Ambos tipos son compuestos o recursivos, y están definidos con el mismo operador de conjuntos, aplicado a conjuntos que a su vez son estructuralmente equivalentes entre ellos. Equivalencia estructural Tipos compuestos y recursivos Dados cuatro tipos arbitrarios A,B,A0 ,B0 , en donde A ≡ A0 y B ≡ B0 , entonces: se cumple que: A×B A+B A→B listas( A) arboles( A) creado October 10, 2011 ≡ ≡ ≡ ≡ ≡ A0 × B0 A0 + B0 A0 → B0 listas( A0 ) arboles( A0 ) página 2.35 / 35 LP (11-12) 2. Valores y Tipos Equivalencia nominal Cada valor manejado en un programa tiene asociado a un tipo. Este tipo puede: • Tener asociado un identificador (su nombre), se dice que es un tipo con nombre. • No tener asociado un identificador, pero si una declaración donde se especifica su estructura (es un tipo anónimo) Equivalencia nominal • Dos tipos con nombre son equivalentes nominalmente si sus nombres coinciden. • Dos tipos anónimos son equivalentes nominalmente si su estructura se define en la misma declaración (la equivalencia nominal implica equivalencia estructural). Valoracion de las formas de equivalencia de tipos • La equivalencia nominal es más fácil de implementar, y además produce programas más fáciles de comprender y menos sujetos a errores. La mayoría de los lenguajes modernos usan equivalencia nominal en la mayoría de los casos. • En algunos casos concretos se usa equivalencia estructural (por ejemplo, en el paso a subprogramas de parámetros por referencia de tipo array). Tiene la ventaja de que el programa es más conciso. Equivalencia de tipos en varios lenguajes. • C/C++ equivalencia estructural (excepto clases en C++, con equivalencia nominal). • Ada equivalencia nominal • Jaca, C#: equivalencia nominal Hay excepciones puntuales. 6 El principio de completitud de tipos El principio de completitud de tipos debería ser seguido en cualquier lenguaje. Establece que: Ninguna operación debería ser prohibida (arbitrariamente) a valores de un tipo determinado. Un ejemplo en el que no se cumple: Una función en C puede devolver un valor de cualquier tipo, excepto el tipo array. creado October 10, 2011 página 2.36 / 35