32
Tema 8: Tipos de datos Sesión 25: Tipos de datos (2)

Tema 8: Tipos de datos - dccia.ua.es · 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)

  • Upload
    vancong

  • View
    223

  • Download
    0

Embed Size (px)

Citation preview

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)

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

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();

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)

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

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

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;

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

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

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

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.

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) {}}

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

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

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

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}