Entendiendo el tipado blando en Javascript

27 Ene 2011

Un poco de teoría rápida

Los lenguajes de programación pueden clasificarse según su tipado o, lo que es lo mismo, según cómo declaramos el tipo de datos que aceptan sus variables. Así, tenemos lenguajes de tipado dinámico o estático y aquellos de tipado fuerte o débil (también conocidos como no-tipados).

Este último grupo distingue a los lenguajes según si permiten o no violaciones de los tipos de datos una vez declarados.

Los denominados fuertemente tipados corresponden con aquellos lenguajes que no permiten comparar u operar con tipos de datos distintos sin realizar una conversión previa; dentro de esta clasificación se encuentran la mayoría de los lenguajes imperativos como Java o Python.

En el otro lado, los lenguajes de tipado blando o débil son aquellos que realizan las necesarias conversiones de forma interna para permitir un tratamiento ambigüo de las variables según se requieran en tiempo de ejecución. Muchos de los lenguajes declarativos como Haskell o Lisp pertenecen a este grupo.

Para muchos desarrolladores web, Javascript es el primer lenguaje de scripting y/o interpretado al que se acercan. Para ellos, asimilar el concepto e implicaciones de un tipado blando puede resultar intuitivo y natural. Sin embargo, para aquellos desarrolladores que proceden de lenguajes fuertemente tipados como C o Java, la libertad que ofrece Javascript en cuanto al tratamiento de las variables es considerado como un peligro potencial que hay que saber manejar.

Tipado blando en Javascript

Javascript pertenece al grupo de lenguajes imperativos pero, sin embargo, se caracteriza por estructuras de tipado débil donde la declaración de variables no exige la asociación con un tipo de datos de forma implícita y unívoca.

Un tipado blando (o no tipado) significa que las variables son declaradas sin un tipo: los valores pueden modificarse, compararse y operar entre ellos sin necesidad de realizar una conversión previa. Consideremos los siguientes ejemplos:

var x = 15; // Number (int) declaration
var y = 15.6 // Number (float) declaration
var z = 'Hello World' // String declaration

En ningún momento indicamos al intérprete qué tipo de datos corresponden con las variables. En el caso de los arrays y los objetos las declaraciones son algo más precisas aunque ligeramente confusas:

var arr = []; // Array literal declaration
var obj = {}; // Object literal declaration

Sin embargo, pese a que no hemos especificado los tipos, Javascript los asigna por nosotros de forma interna:

console.log( typeof( x ) ); // number
console.log( typeof( z ) ); // string
console.log( typeof( arr ) ); // object
console.log( typeof( obj ) ); // object

NOTA: El array, aunque declarado como tal siguiendo la notación literal, pertenece al tipo Object. Esto se explica observando el siguiente cuadro con las distinciones entre primitivas, objetos y casos especiales.

Coerción de datos en Javascript

El concepto de coerción de datos está íntimamente ligado con un tipado débil. Al relegar la conversión de datos al intérprete interno, a menudo se precisa de modificar los valores para adaptarlos y poder realizar operaciones entre ellos.

Las coerciones en Javascript, en determinados casos, resultan muy poco intuitivas y pueden convertirse en fuente de errores.

Consideremos los siguientes ejemplos:

console.log( 7 + 7 + 7 ); // 21
console.log( 7 + 7 + "7" ); // "147"
console.log( "7" + 7 + 7 ); // "777"

Los resultados pueden parecer confusos y necesitan una explicación.

Las operaciones aritméticas de adición (suma) se realizan siguiendo un orden estricto de izquierda a derecha. Si un operando corresponde a un tipo de datos string (cadena), el operador ‘+’ lo concatena en lugar de adicionarlo (sumarlo) y todos los operadores posteriores son convertidos (coercionados) en más cadenas que se concatenan sucesivamente. Por lo que podemos concluir que, si en una operación de suma uno de los operandos es una cadena, el tipo de datos final será otra cadena con el valor acumulado, y los siguientes, concatenados.

Para el resto de operaciones básicas ( resta, multiplicación y división ), Javascript siempre trata de convertir en primer lugar las cadenas en números, por lo que los resultados resultan más intuitivos:

console.log( 10 - 2 - 4 ); // 4
console.log( 10 - 2 - "4" ); // 4
console.log( "10" - 2 - 4 ); // 4

Un ejemplo de coerción que suele pasar inadvertido es cuando operamos en bases diferentes al sistema decimal:

console.log( 1 + 012 ); // 11

Otro caso típico de coerción en Javascript ocurre durante el método de comparación no-estricta ‘==’ del que hemos hablado ya alguna vez. Cuando utilizamos este método, se realizan de forma interna las conversiones oportunas entre datos para poder llevar a cabo la comparación:

console.log( 1 == "1" ); // True

En este caso, como hemos mencionado más arriba, el algoritmo de comparación siempre trata de realizar una conversión desde cadenas a números para su evaluación.

Conversión de variables. Javascript cast

Muchos lenguajes de programación, como por ejemplo PHP, utilizan un método cast que permite convertir un tipo de variable en otro. Javascript ofrece una serie de comandos que pueden ayudarnos a realizar una conversión similar.

Para convertir una cadena en un número, podemos utilizar cualquiera de los siguientes métodos:

var foo = '5';
console.log( typeof parseInt( foo ) );
console.log( typeof parseFloat( foo ) );
console.log( typeof ( foo - 0 ) );
console.log( typeof ( foo * 1 ) );
console.log( typeof ( foo / 1 ) );
console.log( typeof ( +foo ) );

Los ejemplos anteriores son fáciles de seguir: parseInt y parseFloat obtienen la parte entera o flotante de una cadena; las operaciones aritméticas (‘-‘, ‘*’ y ‘/’) convierten internamente las cadenas en números tal y como vimos más arriba. Finalmente, el operador unario ‘+’ al, no precisar de pasos adicionales, es la forma más rápida y elegante de realizar la conversión.

El paso contrario, de número a cadena, se realiza mediante el método nativo toString:

var foo = 1;
console.log( typeof ( foo.toString() ) ); // string

Para pasar un número o una cadena a un objeto Booleano, podemos utilizar el recurso de la doble negación ‘!!’:

var foo = 'Hello World';
console.log( !foo ); // false
console.log( !!foo ); // true

Conclusión

Javascript es un lenguaje de tipado blando (o no tipado) que permite una enorme flexibilidad a la hora de definir variables. Sin embargo, aunque esta característica es bienvenida por los programadores noveles, los más veteranos encuentran aquí uno de los puntos más controvertidos del lenguaje y un foco potencial de errores. Conocer la forma en que Javascript maneja los tipos de datos es importante para evitar las incongruencias derivadas de una coerción inesperada de valores.

En este artículo hemos visto algunos de los escenarios más comunes que pueden inducir a comportamientos inesperados por parte del intérprete interno.

Más información

Javascript 2.0 predefined types
Jeremy Martin, Understanding Loose Typing in JavaScript

Más:

{3} Comentarios.

  1. Alejandro

    Hola,

    Muy buen artículo.
    Sólo quería realizarte un comentario acerca de que mencionas como ejemplo que php es un ejemplo fuertemente tipado, el cual no es así.
    Entiendo que habrás querido mencionar otro lenguaje quizás o no te habrás dado cuenta en la redacción que quedó ese «php» ahí.

    Saludos,

    Alejandro

    • Carlos Benítez

      Cierto; seguramente, durante la redacción quise decir Python. Ya está actualizado.
      Gracias por el apunte!

  2. Matías

    Hola. No es cierto que Haskell sea débilmente tipado. Al contrario. Es el lenguaje con tipado más fuerte que conozco y hasta te diría que su tipado es el más fuerte posible. Tal vez la confusión surja porque estos no necesitan declararse de forma explícita, sino que son inferidos por un sistema de inferencia de tipos muy poderoso. Pero eso no lo hace debilmente tipado.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *