Tabla de contenidos
- TOC
- Intro
- Estático vs Dinámico
- ¿Y la inferencia de tipos?
- Fuerte vs débil
- Usos prácticos
- ¿Pros y contras?
- TLDR
- Ver también
Intro
En programación solemos confundir algunos términos cuando hablamos del tipado de un lenguaje. Todos tenemos nociones de lo que es el tipado estático o dinámico, y podemos dar diferencias (casi siempre a base de puros ejemplos) de lenguajes fuerte o débilmente tipados, pero ¿entendemos realmente esto? ¿podemos explicárselo a alguien que lo oye por primera vez? ¿podemos escribirlo en un post para que otro lo entienda?
En este post trataré de explicar estos temas en fácil y dejarlo lo más claro posible. La idea es que al final tengas ese momento "ajá!" donde por fin te quede más claro todo esto y puedas discutirlo con más confianza.
Estático vs Dinámico
Hablar de tipado estático y dinámico vs fuerte y débil es hablar de polos en ejes distintos. Vamos primero con estático vs dinámico:
Tipado estático
En tipado estático el tipo está ligado a la variable. Los tipos se chequean en tiempo de compilación.
Esto significa que en un lenguaje de tipado estático, una vez que la variable se declara de un tipo, no se puede cambiar el tipo más adelante; obviamente sí se le puede asignar otros valores del mismo tipo, porque el tipo queda ligado a la variable y no a los valores que ésta toma.
En Java ☕ :
String s = “hello”;
System.out.Println(s) // "hello"
s = “good”;
System.out.Println(s) // "good"
s = "world";
System.out.Println(s) // "world"
Este código es perfectamente válido, porque se declara s
como de tipo String
, al igual que los valores que se le asignan después.
Pero si tratáramos de hacer esto:
String s = “hello”;
System.out.Println(s) // "hello"
s = 5;
System.out.Println(s)
El programa no compilará y veremos un error de tipos incompatibles: no podemos asignarle un int
a algo de tipo String
.
El take away de esto es que en el tipado estático, una vez que una variable se declara de un tipo, queda permanentemente asociada a ese tipo, y este no puede cambiar a lo largo de su vida. Puedes asignarles otros valores siempre que sean del mismo tipo (obviamente a menos que sea constante).
Tipado dinámico
Por otro lado:
En tipado dinámico, el tipo está ligado al valor. Los chequeos son en tiempo de ejecución.
Como el tipo está ligado al valor, el tipo de la variable puede cambiar a medida que cambian el tipo de los valores que se le asigna.
En Python 🐍 :
x = 1
print(type(x)) # <class 'int'>
x = “hello”
print(type(x)) # <class 'str'>
x = 3.14
print(type(x)) # <class 'float'>
Es perfectamente válido y corre sin problemas, porque el tipo de x
depende de los valores que se le asignen (int
, str
y float
, respectivamente).
Pero en Go, que es un lenguaje estáticamente tipado, esto ni siquiera compila:
var x int = 5
fmt.Println(x)
x = “hello”
fmt.Println(x)
Porque con var x int = 5
le indicamos al compilador que x
es de tipo int
, y al ser estáticamente tipado, esto no puede cambiar nunca.
¿Y la inferencia de tipos?
Ojo que en Go (y varios otros lenguajes), hay inferencia de tipos, es decir que el compilador/intérprete puede inferir el tipo de una variable en la misma declaración sin que se lo indiques explícitamente, a partir del valor que le asignes.
Pero eso no significa que el lenguaje deje de ser estaticamente tipado:
var x int = 2 // declaración larga, indicamos explícitamente el tipo int
x := 5 // declaracion corta, usa inferencia de tipos
fmt.Println(x)
x = “hello”
fmt.Println(x)
Sigue siendo inválido; no porque haya inferencia de tipos significa que el tipado es dinamico. La diferencia es que en vez de decirles nosotros explícitamente que x
es int
, el compilador lo infiere por el tipo del valor que le asignamos (5
). Eso. Una conveniencia para el programador.
Fuerte vs débil
El problema al hablar de tipado fuerte y débil es que no hay un consenso único ni una definición muy formal de qué es cada cosa. Cada quien dice cosas más o menos similares para cada una, pero no hay un acuerdo profesional canónico.
Pero para aterrizarlo un poco, tipado fuerte suele referirse a que el compilador obliga una especie de “disciplina de tipos” en tiempo de compilación. O sea que “deberían haber” reglas más estrictas de tipado en tiempo de compilación; generalmente esto tiene que ver con asignación de variables, valores de retorno y argumentos de funciones.
En cambio, un tipado débil supuestamente tiene reglas menos estrictas de verificación de tipos.
Usos prácticos
En la práctica, por lo general se asocian los lenguajes de tipado dinámico con lenguaje interpretados como Python, Ruby, Perl o Javascript, mientras que los lenguajes de tipado estático suelen ser compilados, como Go, Java* o C.
Sin embargo, esto no implica que el tipado deba estar restringido a la forma de traducir el lenguaje. Perfectamente pueden haber lenguajes interpretados con tipado estático, y lenguajes compilados con tipado dinámico.
Además, pueden haber varias mezclas entre estático/dinámico y fuerte/débil:
C, a pesar de ser de tipado estático, también es considerado débilmente tipado, porque mediante casts podemos transformar un tipo a otro, especialmente al usar apuntadores. Esto tiene sus pro y sus contras, pero C asume que el programador sabe lo que hace (¡JAJA!) y le otorga esta libertad.
Python es de tipado dinámico, pero considerado fuertemente tipado.
*Java es un caso particular porque su traducción es híbrida, o sea interpretada y compilada a la vez, pero la idea es la misma.
¿Pros y contras?
En general, los lenguajes dinámicamente tipados, usualmente interpretados o de scripting, nos permiten iterar rápido y poner a prueba las ideas sin mucha complicación. Es común usar Python o Ruby para ver si el algoritmo que pensamos funcionará o no. Su naturaleza dinámica nos permite concentrarnos más en la idea y no tanto en los detalles del lenguaje como declarar correctamente los tipos de cada variable, y eliminar el código boilerplate. Nos ayuda a tener prototipos más rápido sobre los que podemos discutir.
Sin embargo, también suele ocurrir que estos lenguajes interpretados son más lentos en ejecución que otros lenguajes compilados. Esto es algo bien conocido, y aunque recientemente se ha visto que ya no es tan así, es bueno que lo tengas presente.
También, los lenguajes dinámicos suelen postergar los errores de tipos a tiempo de ejecución, por lo que si no somos rigurosos en los tests, habrán flujos del código que se escaparán y fallará en producción, porque tratamos de usar una variable de un tipo como si fuera de otro.
Por otra parte, los lenguajes estáticos y compilados, suelen fallar en tiempo de compilación si hay algún error de tipos, como pasar un string a una función que espera un entero. Esto nos ayuda a estar más seguros del código que desplegamos y evitar inconsistencias.
TLDR
Estático/dinámico son polos de un mismo eje, y es donde hay más claridad y consenso. Las teoría está más desarrollada aquí y es más clara entre las diferencias de cada uno.
En el tipado estático, el tipo queda ligado a la variable/objeto.
En el tipado dinámico, queda ligado al valor que se le asigne a la variable en un momento dado; a medida que cambie el tipo del valor, cambiará el tipo de la variable.
Fuerte/débil son polos de otro eje, pero está mucho menos claro y definido a qué se refiere. Hay como “ideas generales”, pero no una definición rigurosa universal.
Espero que este post te haya gustado :) Si te interesa, puedes suscribirte para que sepas cuando subo nuevo contenido :D
Top comments (0)