Unidad II · Tema 2.5 Disponible

Tipos de datos

Tipos primitivos y compuestos, sistemas de tipos e inferencia.

Objetivo de aprendizaje

Al concluir este tema, el estudiante será capaz de:

  1. Definir tipo de dato como un conjunto de valores y de operaciones, y clasificar tipos en primitivos, compuestos y abstractos.
  2. Distinguir los ejes estático/dinámico y fuerte/permisivo, y ubicar lenguajes con evidencia.
  3. Predecir qué ocurre con una misma operación en distintos lenguajes: produce un valor, es rechazada al compilar, falla al ejecutarse o queda indefinida.
  4. Explicar la inferencia de tipos y aplicar un inferidor simplificado a funciones pequeñas.
  5. Justificar la elección de un sistema de tipos con los criterios de la pragmática (fiabilidad, facilidad de escritura, costo).

Ruta de trabajo del tema

Etapa Contenido Producto del estudiante
1 Planteamiento inicial Hipótesis escrita
2 Qué es un tipo; clasificación Vocabulario preciso
3 Dos ejes: estático/dinámico y fuerte/permisivo Mapa conceptual propio
4 La misma operación en cuatro lenguajes Predicciones por lenguaje
5 Ubicar lenguajes con evidencia Clasificación fundamentada
6 Inferencia de tipos Predicción de lo que se infiere
7 Tipos y pragmática Argumento sobre compromisos
8 Verificación y reformulación Cuestionario y respuesta revisada

1. Planteamiento inicial

Las computadoras manipulan secuencias de bits; es el lenguaje el que decide si una secuencia representa un entero, un decimal o un texto, y qué operaciones tienen sentido entre ellos. Cuando se intenta una operación que mezcla tipos, cada lenguaje toma una decisión distinta.

Considere la expresión "5" + 3. En Python produce un error al ejecutarse; en JavaScript produce "53"; en Java produce "53" por una regla especial del operador +; en C no tiene el mismo sentido porque no existe el tipo texto.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Esta hipótesis se retomará al final de la sección 5.


2. Qué es un tipo de dato

Definición — Tipo de dato: conjunto de valores posibles junto con el conjunto de operaciones válidas sobre ellos. El tipo entero incluye los números enteros y las operaciones de sumar, restar, comparar, etcétera; el tipo texto incluye cadenas de caracteres y operaciones como concatenar.

Los tipos cumplen dos funciones: organizan la información (qué representa un dato) y protegen al programa (impiden operaciones sin sentido, como multiplicar dos textos).

Definición — Verificación de tipos: comprobación de que cada operación se aplica a operandos de los tipos admitidos. Puede realizarse antes de ejecutar (tipado estático) o durante la ejecución (tipado dinámico).

Clasificación habitual:

Categoría Qué es Ejemplos
Primitivos (escalares) Valores simples que el lenguaje no descompone int, float, bool, char
Compuestos (estructurados) Se construyen combinando otros tipos Arreglos, listas, tuplas, registros (struct), diccionarios, uniones
Abstractos (TAD) Se definen por su comportamiento (operaciones e invariantes), no por su representación Pila, cola, conjunto; las clases de la POO (Unidad III - POO)

Importante: en Python no existen tipos primitivos en el sentido de Java o C: incluso un entero es un objeto, como se estudió en el tema 2.1. Python admite anotaciones de tipo (def f(x: int) -> int:), pero el intérprete no las verifica: son documentación y base para herramientas externas (como mypy).


3. Dos ejes independientes

Dos preguntas distintas describen el sistema de tipos de un lenguaje:

Eje Pregunta Extremos
Cuándo se verifica ¿Se comprueban los tipos antes de ejecutar o durante la ejecución? Estático (Java, C, Haskell, Rust) frente a dinámico (Python, JavaScript, Ruby)
Qué tan estricto es ¿El lenguaje convierte tipos por su cuenta o exige conversiones explícitas? Fuerte (Python, Java, Haskell) frente a permisivo o débil (JavaScript, PHP, C)

Importante: son ejes independientes. Python es dinámico y fuerte: "5" + 3 falla al ejecutarse en lugar de convertir en silencio. JavaScript es dinámico y débil. Java es estático y fuerte. C es estático y permisivo. La distinción fuerte/débil es una escala y no una división exacta; por eso se prefiere hablar de grados y de ejemplos concretos.

Criterio Estático Dinámico
Se verifica Antes de ejecutar Durante la ejecución
Ventaja Errores detectados antes; optimizaciones del compilador Flexibilidad; menos código declarativo
Costo Más declaraciones o necesidad de inferencia; rechaza programas que quizá funcionarían Los errores de tipo pueden aparecer en producción, en un camino poco probado

Caso de estudio. El verificador de tipos del lenguaje Mini (tema 1.2.2) era estático y fuerte: detectaba antes de ejecutar que "a" + 1 mezcla tipos, y no convertía de manera implícita. Por eso el programa se rechazaba sin ejecutar ninguna línea.


4. La misma operación en cuatro lenguajes

Aplicación práctica. Para cada caso, anticipe qué ocurre en Python, JavaScript, Java y C. Las posibilidades son cuatro: produce un valor, el compilador lo rechaza, falla al ejecutarse o el comportamiento queda indefinido. Al terminar los nueve casos se muestra un resumen por lenguaje.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Nota: los fragmentos de Java, JavaScript y C se muestran como contraste y no se ejecutan en este sitio. Los de C con comportamiento «indefinido» no tienen un resultado garantizado por el lenguaje: puede variar entre compiladores y ejecuciones.


5. Ubicar lenguajes con evidencia

Ubicar un lenguaje en los dos ejes exige evidencia: qué ocurre con operaciones concretas. No basta con recordar etiquetas.

Aplicación práctica. Cada descripción resume el comportamiento de un lenguaje. Ubíquelo en los ejes.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Contraste con el planteamiento inicial

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


6. Inferencia de tipos

En un lenguaje estático, escribir el tipo de cada variable es costoso y repetitivo. La inferencia de tipos reduce ese costo.

Definición — Inferencia de tipos: mecanismo por el cual el compilador deduce el tipo de una expresión o de una variable a partir de su uso, sin que el programador lo declare. Conserva las garantías del tipado estático con una escritura más breve.

Lenguaje Ejemplo Observación
Haskell f x = x * x + 1 Infiere el tipo completo de funciones sin anotaciones (algoritmo de Hindley-Milner)
Rust let x = 5; Infiere el tipo entero de x a partir del uso
C++ auto x = 5; auto deduce el tipo a partir del inicializador
Java var x = 5; Desde Java 10, para variables locales
Python def f(x: int) -> int Las anotaciones son opcionales y las verifica una herramienta externa

Cómo se infiere. Cada operador impone restricciones sobre sus operandos: por ejemplo, a + b exige que ambos tengan el mismo tipo y que sea numérico o de texto; len(s) exige que s sea un texto. El inferidor parte de que cada variable puede ser de cualquier tipo y reduce los conjuntos de tipos posibles con cada restricción. El resultado puede ser:

Resultado Significado
Determinado Cada variable tiene exactamente un tipo posible
Indeterminado Las restricciones permiten más de un tipo; se necesita una anotación o un valor por omisión
Contradicción Ningún tipo satisface todas las restricciones: el programa se rechaza

Caso de estudio. En def f(x): return x * x + 1, la multiplicación exige que x sea numérico (Int o Float) y la suma con el literal 1 (que es Int) obliga a que el resultado de x * x sea Int: por lo tanto x es Int. En def f(a, b): return a * b no hay un literal que fije el tipo: queda indeterminado entre Int y Float.

Aplicación práctica. Escriba su propia función de una sola línea return (o elija un ejemplo) y observe cómo se reducen los conjuntos de tipos. Active la predicción para anticipar el resultado. El modelo es simplificado: exige que los dos operandos de un operador aritmético tengan el mismo tipo, como lo hacen Haskell u OCaml; Python, en cambio, permite mezclar enteros y decimales.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


7. Tipos y pragmática

La elección de un sistema de tipos es una decisión de diseño con compromisos, como las analizadas en el tema 1.2.3:

Decisión Favorece Cuesta
Tipado estático Fiabilidad: errores detectados antes de ejecutar; rendimiento Facilidad de escritura (más declaraciones o inferencia), flexibilidad
Tipado dinámico Facilidad de escritura y prototipado rápido Fiabilidad: los errores de tipo aparecen en ejecución
Tipado fuerte Fiabilidad: no hay conversiones silenciosas Conveniencia de escritura
Inferencia Brevedad con garantías estáticas Los mensajes de error pueden ser más difíciles de interpretar

Los tipos también se enlazan con las unidades siguientes: los tipos abstractos de datos preparan el concepto de clase (Unidad III), y los tipos algebraicos de los lenguajes funcionales (Unidad IV) permiten describir datos con precisión y comprobar que un programa los trata a todos.


8. Errores conceptuales frecuentes

Error común: confundir «estático/dinámico» con «fuerte/débil». Son ejes independientes.

Error común: afirmar que Python «no tiene tipos». Tiene tipos fuertes, verificados en ejecución y ligados a los valores.

Error común: creer que las anotaciones de tipo de Python se verifican al ejecutar. No lo hacen; las verifican herramientas externas.

Error común: creer que la inferencia convierte a un lenguaje en dinámico. Los tipos se siguen verificando antes de ejecutar; solo se omiten las declaraciones.

Error común: pensar que el tipado estático garantiza que el programa sea correcto. Detecta una clase de errores (los de tipos), no todos.


9. Verificación del tema

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


10. Libreta de trabajo

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


11. Preguntas de reflexión

  1. ¿Qué ventajas y riesgos trae un sistema de tipos con inferencia frente a uno con anotación explícita?
  2. Para una aplicación de un laboratorio de investigación que cambia con frecuencia y para un sistema de control de un dispositivo médico, ¿qué sistema de tipos elegiría en cada caso? Justifique con los criterios de la pragmática.
  3. ¿Qué tipo de error de un programa no detecta un sistema de tipos estático? Proponga un ejemplo.
  4. Si las anotaciones de tipo de Python no se verifican al ejecutar, ¿qué beneficios aportan?
  5. Compare la decisión de Java para "5" + 3 (concatenar) con la de Python (error). ¿Qué ganó cada una y qué perdió?

12. Lecturas recomendadas

  • Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulo sobre tipos de datos y sección sobre verificación de tipos. Pearson.
  • Python Software Foundation. The Python Language Reference, sección «The standard type hierarchy», y typing: support for type hints. Acceso libre en docs.python.org.
  • Pierce, B. C. (2002). Types and programming languages. MIT Press. (Para quien desee profundizar en sistemas de tipos e inferencia.)