Tema 8: Tipos de datos

26
Tema 8: Tipos de datos Sesión 24: Tipos de datos (1) miércoles 4 de mayo de 2011 Referencias • Programming Languages Pragmatics: Capítulo 7, apartados 7.1 (Type Systems) y 7.2 (Type Checking) • Programming in Scala: Capítulo 5 (Basic Types and Operations) y capítulo 11 (Scala's Hierarchy) miércoles 4 de mayo de 2011

Transcript of Tema 8: Tipos de datos

Page 1: Tema 8: Tipos de datos

Tema 8: Tipos de datos

Sesión 24: Tipos de datos (1)

miércoles 4 de mayo de 2011

Referencias

• Programming Languages Pragmatics: Capítulo 7, apartados 7.1 (Type Systems) y 7.2 (Type Checking)

• Programming in Scala: Capítulo 5 (Basic Types and Operations) y capítulo 11 (Scala's Hierarchy)

miércoles 4 de mayo de 2011

Page 2: Tema 8: Tipos de datos

Indice

• Conceptos generales sobre tipos de datos

• Clasificación de tipos

• Subtipos

• Polimorfismo

• Chequeo de tipos

• Tipos paramétricos en Scala

miércoles 4 de mayo de 2011

Tipos en los lenguajes de programación

• Los bytes que se almacenan en la memoria de un computador no tienen tipos de datos: son sólo ceros y unos que se interpretan en función de las instrucciones ejecutadas.

• El lenguaje ensamblador tampoco tiene el concepto de tipo, son los lenguajes de alto nivel los que lo introducen

miércoles 4 de mayo de 2011

Page 3: Tema 8: Tipos de datos

Tipos en los lenguajes de programación

• Propósitos principales de los tipos:

• Proporcionar contexto implícito en muchas ocasiones, de forma que el programador no tenga que hacerlo explícito. Aumenta la legibilidad y la expresividad de los programas (se pueden decir más cosas con menos líneas de código).

• Permiten comprobar errores en programas sintácticamente correctos, limitando la cantidad de fallos que se pueden cometer al desarrollar

miércoles 4 de mayo de 2011

Tipos en los lenguajes de programación: contexto implícito

• Por ejemplo, en C, la expresión a + b se compila distinto dependiendo del tipo a y b.

• En Scala la información de los tipos de datos se utiliza para inferir el tipo de las variables y realizar la compilación a byte codes de Java

• La función anterior es muy fácil de leer. El hecho de utilizar una x de tipo BigInt permite calcular el factorial de números grandes. En la función se utilizan operadores como * y - sin tener que realizar conversiones de tipos. Y se utilizan las constantes 0 y 1 que, en principio, son de tipo Int. Parece que estamos utilizando tipos normales y no clases.

def factorial(x: BigInt): BigInt = if (x == 0) 1 else x * factorial(x-1)

miércoles 4 de mayo de 2011

Page 4: Tema 8: Tipos de datos

Tipos en los lenguajes de programación: contexto implícito

• Scala transforma el programa anterior en un programa equivalente, utilizando la declaración de la función para saber que x es un objeto de la clase BigInt y la clase de Java java.math.BigInteger:

• El primer programa es mucho más expresivo que el segundo.

import java.math.BigInteger

def factorial(x: BigInteger): BigInteger = if (x == BigInteger.ZERO) BigInteger.ONE else x.multiply(factorial(x.subtract(BigInteger.ONE)))

miércoles 4 de mayo de 2011

Tipos en los lenguajes de programación: comprobación de errores

• Permiten comprobar errores en programas sintácticamente correctos, limitando la cantidad de fallos que se pueden cometer al desarrollar

def sumaStrings(a: String, b: String) = { a+b}var x: Int = 1var y: Int = 2sumaStrings(x,y) !!Error!!

miércoles 4 de mayo de 2011

Page 5: Tema 8: Tipos de datos

Tipos de variables y de objetos

• Dos interpretaciones para los tipos de datos:

• Tipos declarados para los elementos del lenguaje (variables, parámetros, funciones, etc.)

• Tipos de los objetos que se crean en tiempo de ejecución y que se almacenan en las variables, se pasan como parámetros o se devuelven en una función.

• Ambos elementos pueden ser distintos, como en el siguiente ejemplo de Scala:

• El tipo de la variable es AnyVal y el tipo del valor guardado en ella es Double

var x: AnyVal = 1.0

miércoles 4 de mayo de 2011

Tipos de variables y de objetos

• Puede no existir tipo para las variables. En Scheme y otros lenguajes débilmente tipeados las variables, parámetros y funciones no se declaran de un tipo; su tipo puede cambiar en tiempo de ejecución:

• Pero los objetos guardados en las variables sí que tienen un tipo definido. El intérprete comprueba en tiempo de ejecución que los tipos de los objetos y las operaciones son compatibles

(define a "hola")(set! a (cons 1 2))

(define (suma a b) (+ a b))(suma 1.0 2.0)(suma (/ 2 3) (/ 1 3))

(define a 1.0)(+ a “hola”)+: expects type <number> as 2nd argument, given: “hola”

miércoles 4 de mayo de 2011

Page 6: Tema 8: Tipos de datos

Definición de tipo

• Un tipo es un metadato sobre una estructura del lenguaje (variable u objeto) que define el tipo de dato que vamos a poder guardar en ella. Esta definición especifica de forma implícita el tipo de operaciones que vamos a poder realizar sobre los datos.

miércoles 4 de mayo de 2011

Tipos en Scala

• Scala es un lenguaje estáticamente tipeado: todas las variables, parámetros, funciones, etc. tienen un tipo definido

• La forma de declarar el tipo de una estructura es utilizando dos puntos (:)

var x: String = "Hola"def makePlural(c: String): String = { c+"s"}

miércoles 4 de mayo de 2011

Page 7: Tema 8: Tipos de datos

Tipos en Scala

• Scala realiza una inferencia de tipos a partir de las expresiones y podemos no declarar los tipos de las estructuras (en los parámetros de las funciones es obligatorio):

• La inferencia de tipos de Scala es una idea que toma de los lenguajes de programación funcional ML y Haskell.

var x = "Hola"def makePlural(c: String) = { c+"s"}

miércoles 4 de mayo de 2011

Sistemas de tipos

• Un sistema de tipos consiste en:

• Un mecanismo para definir tipos y asociarlos con ciertas construcciones del lenguaje

• Un conjunto de reglas que definen la equivalencia de tipos, compatibilidad de tipos y la inferencia de tipos.

miércoles 4 de mayo de 2011

Page 8: Tema 8: Tipos de datos

Chequeo de tipos

• El chequeo de tipos es el proceso por el que se asegura que el programa obedece las reglas de compatibilidad de tipos

• Un error se denomina una colisión de tipos (type clash en inglés)

• El chequeo de tipos se puede hacer en tiempo de compilación (lenguaje estáticamente tipeado) y/o en tiempo de ejecución

• Por ejemplo, lenguajes como Ada, Pascal, Scala o Java realizan una gran parte del chequeo de tipos en tiempo de compilación

• Lenguajes de script como Python, Ruby o Scheme realizan el chequeo de tipos en tiempo de ejecución

• Otros lenguajes como ensamblador o C no chequean los tipos en tiempo de ejecución

miércoles 4 de mayo de 2011

Clasificación de tipos

• Booleano

• Carácter

• Tipos numéricos

• Tipos enumerados

• Tipos de subrango

• Tipos compuestos

miércoles 4 de mayo de 2011

Page 9: Tema 8: Tipos de datos

Clasificación de tipos

• Tipos compuestos:

• Registros

• Registros variantes (uniones)

• Arrays

• Conjuntos

• Listas

• Ficheros

miércoles 4 de mayo de 2011

Booleanos

• Los tipos booleanos (true y false) existen en la mayoría de lenguajes de programación

• En C no; 1 representa true y 0 false

• En Scala:

var finalLista: Boolean = false

miércoles 4 de mayo de 2011

Page 10: Tema 8: Tipos de datos

Carácter

• En los lenguajes más antiguos (C, Pascal,…) el tipo carácter tiene un byte de tamaño y guardan el código ASCII del carácter

• Los lenguajes más modernos (Java, C#, Scala, …) tienen dos bytes y guardan el código Unicode del carácter

• En Scala:

var a: Char = 'A'var c = '\101' ; número octal var c = '\u0041' ; número hexadecimalvar backslash = '\\'

miércoles 4 de mayo de 2011

Page 11: Tema 8: Tipos de datos

Tema 8: Tipos de datos

Sesión 25: Tipos de datos (2)

Referencias

• Programming Languages Pragmatics: Capítulo 7, apartados 7.1 (Type Systems) y 7.2 (Type Checking)

• Programming in Scala: Capítulo 5 (Basic Types and Operations) y capítulo 11 (Scala's Hierarchy)

Page 12: Tema 8: Tipos de datos

Indice

• Tipos numéricos, enumerados, de subrango

• Sobrecarga

• Coerción

• Polimorfismo

• Inferencia de tipos

Tipos numéricos

• La mayoría de lenguajes diferencian entre distintos tipos numéricos en función de la precisión (enteros o punto flotante) y del tamaño (y la cantidad de números que puede representar)

• En Scala podemos usar los siguientes tipos numéricos y precisiones:

Tipo de valor Rango

Byte Entero de 8-bit en complemento a 2 (-27 hasta 27-1, inclusive)

Short Entero de 16-bit en complemento a 2 (-215 hasta 215-1, inclusive)

Int Entero de 32-bit en complemento a 2 (-231 hasta 231-1, inclusive)

Long Entero de 64-bit en complemento a 2 (-263 hasta 263-1, inclusive)

Float Punto flotante IEEE 754 precisión sencilla 32-bit

Double Punto flotante IEEE 754 precisión doble 64-bit

Page 13: Tema 8: Tipos de datos

Tipos enumerados

• Introducidos por N. Wirth en el diseño de Pascal

• Facilitan la legibilidad de los programas y permiten que el compilador chequee errores

• Ejemplo en Pascal:

• Uno de los problemas de los tipos enumerados es la conversión en tipos "portables" que puedan ser entendidos en otros sistemas (como bases de datos o XML)

type weekday = (sun, mon, tue, wed, thu, fri, sat);

Tipos enumerados

• Es habitual que los valores del tipo enumerado se identifiquen con los enteros (del 0 en adelante) o con Strings correspondientes con su identificador

• En Java cualquier valor enumerado puede convertirse a estos dos tipos con los métodos name y ordinal:

public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }Day day = SUNDAY;String str = day.name();int a = day.ordinal();

Page 14: Tema 8: Tipos de datos

Enumerados en Scala

• Los tipos enumerados son subclases de la clase scala.Eumeration

• Hay que definir un object (lo veremos más adelante, en POO en Scala) que extiende Enumeration:

• El tipo de dato es Color.Value

object Color extends Enumeration { val Red, Green, Blue = Value}

var c = Color.RedColor.Value = Red

Enumerados en Scala

• Es posible modificar el nombre asociado a cada constante:

object Direction extends Enumeration { val North = Value("Norte") val East = Value("Este") val South = Value("Sur") val West = Value("Oeste")}

var d = Direction.Southprint(d)

Page 15: Tema 8: Tipos de datos

Tipos de subrango

• Al igual que los tipos enumerados, fueron introducidos en Pascal

• Un subrango es un tipo cuyos valores componen un subconjunto contiguo de los valores de algún tipo de datos discreto

• En Pascal es posible definir rangos de enteros, caracteres, enumeraciones e incluso otros subrangos:

• Los tipos de subrangos son útiles para asegurar la corrección semántica de los programas

type test_score = 0..100;type work_day = mon..fri;

Tipos de subrango

• En Scala no existen estos subrangos, sólo existe la clase Range de la que podemos crear instancias con un rango de valores. Pero no tipos:

var r = 1 to 100

Page 16: Tema 8: Tipos de datos

Sobrecarga, coerción y polimorfismo (págs. 146-151 PLP)

• Los conceptos de sobrecarga (overloading), coerción (coercion) y polimorfismo (polymorphism) están relacionados y es fácil confundirlos

• Todos tienen que ver con la idea aumentar la expresividad de los lenguajes permitiendo que las mismas líneas de código tengan significados distintos dependiendo de los tipos de las variables implicadas

• Son también conceptos técnicos que dependen cómo se procesa el lenguaje, de forma interpretada o de forma compilada. Por ejemplo, la sobrecarga es una técnica concreta relacionada con el proceso de compilación de un lenguaje, mientras que el polimorfismo es un término más general que se aplica a características del propio lenguaje independientemente de si se compilan o se interpretan los programas.

Sobrecarga

• Se realiza una sobrecarga cuando tenemos distintas definiciones de un mismo término (constante, función, operador) asociados a distintos tipos y dependiendo del tipo de las variables o las expresiones se decide en tiempo de compilación por una definición u otra:

• El compilador decide que el primer operador + se debe compilar a una suma de enteros y el segundo a una suma de reales. El programador de C no tiene que usar operadores distintos.

//Ejemplo en Cint sobrecarga(int a, int b, double x) { double c = a+b; return(c+x);}

Page 17: Tema 8: Tipos de datos

Sobrecarga

• El compilador avisa con un error si no tiene suficiente información para decidir (ejemplo en Ada):

• Las constantes dec y oct están repetidas en las dos enumeraciones. Se asigna la constante correcta gracias al contexto definido por el tipo de las variables mo y pb. El la última instrucción el compilador da un error porque no hay información suficiente para decidir el tipo de oct.

declare type month is (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec); type num_base is (dec, bin, oct, hex); mo: month; pb: num_basebegin mo := dec; -- el mes diciembre (mo tiene de tipo month) pb := oct; -- octal (pb es de tipo num_base) print(oct); -- error! contexto insuficiente para decidi

Sobrecarga

• Lenguajes como Java o C# obligan a explicitar la enumeración que se utiliza:

• En la mayoría de lenguajes orientados se pueden sobrecargar los métodos con distintos tipos de parámetros.

mo = month.dec;pb = num_base.oct;

Page 18: Tema 8: Tipos de datos

Sobrecarga en Scala

• Scala técnicamente no tiene sobrecarga de operadores, porque hemos visto que es un lenguaje orientado a objetos y no tiene operadores en el sentido tradicional

• Cuando escribimos a+b Scala traduce la expresión a (a).+(b): se ejecuta el método + sobre el objeto a pasando como parámetro b

• Sí que tiene sobrecarga de métodos: en el caso anterior el compilador decidirá llamar al método + correspondiente según el tipo del parámetro.

• En Scala es legal utilizar los identificadores +, -, etc. como nombres de métodos, por lo que podemos utilizarlos en nuevas clases y después con variables de estas clases:

• En Scala es legal utilizar los identificadores +, -, etc. como nombres de métodos, por lo que podemos utilizarlos en nuevas clases y después con variables de estas clases:

Sobrecarga en Scala

class Rational(n: Int, d: Int) { val numer val denom

def + (otro: Rational): Rational = new Rational( numer * otro.denom + otro.numer * denom, denom * otro.denom ) ...}

val x = new Rational(1,2)val y = new Rational(2,3)x + y

Page 19: Tema 8: Tipos de datos

Coerción

• De alguna manera lo contrario de la sobrecarga

• Se aplica a funciones y métodos

• Es el proceso por el que un compilador convierte un valor de un tipo en otro tipo cuando el contexto lo necesita (por ejemplo, cuando se usa como parámetro)

• El código anterior es correcto en C; se convierten los valores j y k a doubles y después el valor devuelto por min(double) se vuelve a convertir en int.

double min(double x, double y) {...}...int i,j,k...i = min(j,k)

Conversión de tipos en Scala

• En Scala es posible definir una conversión de tipos implícita de un tipo A a otro B con la palabra clave implicit:

• Cuando hay un error en el chequeo de tipos, el compilador de Scala intenta resolverlo buscando alguna función implícita que realice la transformación

• El objeto scala.Predef, que es importado implícitamente en cualquier programa de Scala, define conversiones estándar, por ejemplo de Int a Double:

implicit def double2String(x:Double):String = x.toStringvar a: String = 2.0

implicit def int2double(x: Int): Double = x.toDouble

var y: Int = 2var x: Double = y

Page 20: Tema 8: Tipos de datos

Polimorfismo

• Se utiliza el polimorfismo cuando tenemos código (estructuras de datos, clases, funciones y métodos, etc.) que puede trabajar con valores de múltiples tipos.

• Los tipos deben tener características comunes que son las usadas por el código polimórfico

• En los lenguajes débilmente tipeados (Ruby, Python) el polimorfismo se suele resolver en tiempo de ejecución

• En los lenguajes estáticamente tipeados podemos diferenciar dos tipos: polimorfismo paramétrico y polimorfismo de subtipos

Polimorfismo

• Polimorfismo paramétrico: el código toma un tipo como parámetro, bien explícita o implicitamente. A esta característica también se conoce como genéricos en muchos lenguajes (Ada, C++, Java, Scala)

• Polimorfismo de subtipos: el código está diseñado para trabajar con un tipo T, pero el programador puede definir extensiones o refinamientos de T (subtipos de T) con los que el código también funcionará correctamente

Page 21: Tema 8: Tipos de datos

Clases genéricas en Scala

class Stack[T] { var elems: List[T] = Nil def push(x: T) { elems = x :: elems } def top: T = elems.head def pop() { elems = elems.tail}}

val a = new Stack[String]a.push("Madrid")a.push("Roma")a.popa.top

Polimorfismo de subtipos

• La forma de polimorfismo más habitual en los lenguajes orientados a objetos

• Se utiliza un polimorfismo de subtipos cuando una variable o parámetro se define de un tipo T y el código puede guardar cualquier subtipo S de T (el hecho de que S es un subtipo de T se suele simbolizar de la siguiente forma: S < T).

• Los subtipos del mismo tipo base son compatibles

• Principio de sustitución de subtipos:

Si S es un subtipo de T, entonces los objetos de tipo T pueden ser reemplazados con objetos de tipo S sin alterar ninguna de las propiedades del programa.

Page 22: Tema 8: Tipos de datos

Jerarquía de tipos de Scala

• En Programación Orientada a Objetos es posible especializar una clase A definiendo otra clase B que la extiende. En ese caso el tipo B es un subtipo de A (lo veremos con más detalle en el próximo tema)

Restricciones en tipos de subclases(Chapter 20, Programming in Scala)

• En Scala es posible definir restricciones sobre los tipos en las clases abstractas que van a ser extendidas

class Food

abstract class Animal { type SuitableFood <: Food def eat(food: SuitableFood)}

class Grass extends Food

class Cow extends Animal { type SuitableFood = Grass override def eat(food: Grass) {}}

Page 23: Tema 8: Tipos de datos

Restricciones en tipos de subclases(Chapter 20, Programming in Scala)

• Las subclase concretas de Animal tienen que definir el tipo de SuitableFood (es como un tipo paramétrico)

• La restricción es que debe ser un subtipo (con el operador <:) de Food

• La clase Cow define como SuitableFood el tipo Grass

• No podemos definir subclases de Animal que sobreescriban el método eat con un parámetro de un tipo que no sea subclase de Food. El siguiente código sería erróneo:

class Dog extends Animal { type SuitableFood = Int // error! Int no es subtipo de Food override def eat (food: Int) {} }

Polimorfismo en tiempo de ejecución

• El polimorfismo en tiempo de ejecución permite invocar métodos y funciones de forma de forma dinámica, teniendo en cuenta el tipo del objeto almacenado en una variable y no el tipo de la variable

• En el siguiente ejemplo, el codigo polimórfico es print a.diAlgo ya que se ejecuta con objetos de tipo Dog y Cat. No se sabe hasta el momento de ejecución (run time) de qué tipo es el objeto en la lista de animales

Page 24: Tema 8: Tipos de datos

Polimorfismo en tiempo de ejecución

abstract class Animal { def diAlgo(): Unit}

class Perro extends Animal { def diAlgo() = { println("Guau!!") }}

class Gato extends Animal { def diAlgo () = { println("Miau!!") }}

var animales: List[Animal] = List(new Perro, new Perro, new Gato)for (a <- animales) a.diAlgo

Guau!!Guau!!Miau!!

Especializando un objeto de una clase concreta

• Supongamos que la clase derivada añade algún comportamiento a la clase padre. Por ejemplo, definimos el método limpiate() en la clase Gato y el método traePalo() en la clase Perro:

class Perro extends Animal { def diAlgo() = { println("Guau!!") } def traePalo() = { println("Tírame el palo") }}

class Gato extends Animal { def diAlgo () = { println("Miau!!") } def limpiate() = { println("Me estoy acicalando"); }}

Page 25: Tema 8: Tipos de datos

Especializando un objeto de una clase concreta

• Los métodos sólo los podremos llamar en variables de cada clase

• Por ejemplo, supongamos que guardamos en una variable del tipo de la clase padre (Animal) un objeto de la clase derivada (Gato o Perro). Sería un error llamar al método definido en la clase derivada. También es un error asignarlo a una variable del tipo derivado:

var a: Animal = new Gato

// Error, método no definido en Animal:a.limpiate()// Error, type mismatch:var g: Gato = a

Especializando un objeto de una clase concreta

• Podemos hacerlo, sin embargo, si convertimos la variable en otra del tipo original. Scala define el método asInstanceOf para ello:

• También se puede utilizar una sentencia match para identificar el tipo de animal:

a.asInstanceOf[Gato].limpiate

var a: Animal = new Gatoa match { case g: Gato => g.limpiate() case p: Perro => p.traePalo()}me estoy acicalando

Page 26: Tema 8: Tipos de datos

Inferencia de tipos

• Scala y otros lenguajes permiten no definir los tipos de las variables cuando pueden ser inferidos por el compilador

• La inferencia de tipos de Scala es una idea que toma de los lenguajes de programación funcional ML y Haskell.

• Es necesario especificar el tipo de los parámetros de una función y de las funciones recursivas

Inferencia de tipos

• La inferencia de tipos tiene que tener en cuenta las relaciones de subtipos:

• ¿Cuál es el tipo de prueba?

• El algoritmo de inferencia de tipos más conocido es el denominado Hindley-Milner y está basado en un proceso de satisfacción de restricciones utilizando la unificación.

abstract class Animalclass Perro extends Animalclass Gato extends Animal

def prueba (a: Int) = { if (a > 10) new Gato else new Perro}