JavaScript es débilmente tipado

La definición de JavaScript en Wikipedia es la siguiente:

JavaScript (abreviado comúnmente JS) es un lenguaje de programación interpretado, dialecto del estándar ECMAScript. Se define como orientado a objetos, basado en prototipos, imperativo, débilmente tipado y dinámico.

En el tema Symbol: un nuevo tipos de datos comenté que a partir de ES6 los tipos de datos de JavaScript son string, number, boolean, symbol, null y undefined. También comenté en ese tema que en JavaScript el tipo se deduce del valor, no de la variable. Por ejemplo, si hacemos let x = 123 el valor 123 es de tipo number. Posteriomente podemos reasignar la variable a otro tipo de valor, como por ejemplo un string haciendo x = "abc".

En lenguajes fuertemente tipados podríamos declarar una variable como int x = 123, siendo una variable de tipo número entero (integer). El tipo se deduce de la declaración int, no del valor que tenga asignado. Cualquier reasignación posterior a un valor que no sea un número entero causaría un error. Pero por ahora esto no es posible en JavaScript.

Para aliviar la falta de tipado, JavaScript utiliza la coerción de tipos de forma transparente. Supongamos que declaramos dos variables let a = 0, b = 0 intencionadas para ser de tipos number. Posteriormente se asigna el valor a = 123 a la primera y, por error, asignamos un string a la segunda b = "abc". Si fuera necesario obtener la suma numérica de ambas variables a + b el resultado será "123abc", obteniéndose un string y no un number. Esto es porque el operador + también aplica a los string, siendo la operación concatenación de cadenas. Y la concatenación prevalece sobre la suma numérica. Así que JavaScript coerciona el number 123 al string "123". Y luego concatena ambas cadenas. Y sin generar error alguno.

Si intentamos hacer la resta numérica entre esas dos variables obtenemos NaN. Como no hay una operación equivalente a la resta en los string, JavaScript usará la resta numérica. Para ello convertirá a number ambos operandos. Y la conversión del string "abc" a number resulta en NaN indicando que no es un número. Y también sin generar error. Ambos ejemplos se reproducen en el siguiente código:

let a = 0, b = 0;
a = 123;
b = "abc";
console.log(a+b); // "123abc"
console.log(a-b); // NaN
    

Si la primera línea del código anterior pudiera ser int a = 0, b = 0, cuando llegáramos a la línea b = "abc" el programa nos cursaría un error de tipos, con lo que evitaríamos operar con tipos no coherentes. Pero, lamentablemente, JavaScript no tiene declaración de tipos.

La solución de TypeScript

El problema de tipos no coherentes es especialmente preocupante cuando llamamos a una función con unos argumentos cuyos tipos no son los esperados en la función. Esto supone que tengamos que controlar en cada función lo que se recibe. En lo que sigue de este tema veremos como podemos simplificar esta tarea.

Supongamos que tenemos la función sumar(a, b) que espera dos números para sumarlos. Sería de gran ayuda que la función pudiera verificar los tipos en las llamadas. Si en JavaScript pudiéramos hacer esto:

//TypeScript admite esto pero JavaScript no
function sumar(a: number, b: number){
    return a+b;
}
    

Cuando hiciéramos sumar(123, "abc") nos saltaría un error de que el segundo argumento no es un número. Eso precisamente se consigue con TypeScript, un lenguaje que compila a JavaScript aportando ventajas como la verificación de tipos en los argumentos de las funciones.

Si no queremos usar otro lenguaje más que puro JavaScript interpretado, las soluciones a este problema pasarán por ser un poco forzadas. Aunque ES6 puede simplificar el problema, como veremos en los siguientes apartados.

Parámetros y argumentos de una función

Sabemos que cuando declaramos una función como sumar(a, b) lo hacemos con un lista de parámetros a, b. Mientras que cuando la ejecutamos con sumar(1, 2) lo hacemos con una lista de argumentos 1, 2. En la ejecución se asigna cada argumento a cada parámetro en el orden izquierda a derecha de las listas. Si pasamos un único argumento con sumar(1), ese argumento se adjudica al primer parámetro a. Los parámetros que queden sin adjudicar toman el valor undefined.

El ejemplo sumar(1) nos devolvería NaN pues no podemos sumar un número y un valor undefined. ES6 incorpora los parámetros por defecto para resolver este problema de parámetros no definidos.

function sumar(a=0, b=0){
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // 1
console.log(sumar(null, 2)); // 2
console.log(sumar(undefined, 2)); // 2
console.log(sumar()); // 0
    

Lo anterior no resuelve el problema de verificación de tipos. Si pasamos un argumento que no sea número a nuestra función, por ejemplo un tipo string, JavaScript coercionará el primer argumento a un string para poder aplicar la concatenación de cadenas:

function sumar(a=0, b=0){
    return a+b;
}
console.log(sumar(123, "abc")); // "123abc"
    

Lo que podemos hacer es verificar el tipo adecuado en el interior de la función:

function sumar(a=0, b=0){
    if (typeof a !== "number" || typeof b !== "number"){
        throw new Error("Uno o más argumentos no son numéricos.")
    }
    return a+b;
}
console.log(sumar(123, "abc")); // Error: Uno o más argumentos 
                                // no son numéricos.
    

Pero eso resulta engorroso. Intentaremos buscar una solución más cómoda.

Verificador de tipos de parámetros de funciones

Supongamos que tenemos un sistema para verificar los tipos de los argumentos recibidos al ejecutar una función. En el top de nuestro script declararíamos la variable $error y la funcion $verify(func, args). Esta función servirá para verificar los tipos de los argumentos args que se están pasando a la función func. Asignamos la función a una constante para impedir reasignaciones indebidas.

let $error = false;
const $verify = function(func, args){
    let error = false;
    //Verifica tipos. Si no hay error 
    //devuelve false, en otro caso 
    //devuelve una cadena de error.
    //...código omitido...
    return error;
};
    

En lo anterior omitimos el código que verifica los tipos y que explicaremos más adelante. Por ahora nos centramos en el uso de esa función verificadora. Si no hay error en la verificación devolvería falso. En otro caso si no pasara la verificación devolvería un string con la descripción del error. Usaremos esta técnica con el siguiente ejemplo:

function sumar(a=0, b=0){
    if ($error = $verify(sumar, arguments)) throw new Error($error);
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // 1
console.log(sumar(123, "abc")); // Error: Verificación de tipos en la
//función 'sumar(a, b)': El tipo 'string' del argumento 'b' es distinto
//del tipo 'number' declarado en su parámetro.  
    

El asunto está en agregar una primera línea en las funciones donde deseemos verificar tipos. LLamamos a $verify(sumar, arguments) y si devuelve una cadena de error lanzamos un new Error($error).

Vemos que se produce un error al ejecutar sumar(123, "abc"). El argumento "abc" es un string. Mientras que del parámetro b=0 obtenemos que el tipo del valor por defecto 0 es un number. Y es precisamente de ahí desde donde tomamos los tipos declarados. Lo que hace $verify() es comparar que el tipo del parámetro sea igual que el del argumento.

Aún en el caso de que no declaremos parámetros con valores predeterminados, la ejecución se comportaría como si la línea verificadora no existiera. Pues esto:

function sumar(a, b){
    if ($error = $verify(sumar, arguments)) throw new Error($error);
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // NaN
console.log(sumar(123, "abc")); // "123abc"
    

ejecutará lo mismo que esto:

function sumar(a, b){
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // NaN
console.log(sumar(123, "abc")); // "123abc"
    

En el fondo prescindir de los valores predeterminados en los parámetros equivale a que estos tengan el valor undefined. Pues si no se aporta un argumento entonces tomará ese valor no definido. Así que la declaración function sumar(a, b) equivale a function sumar(a=undefined, b=undefined), como se observa al ejecutar el código siguiente comparado con el anterior:

function sumar(a=undefined, b=undefined){
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // NaN
console.log(sumar(123, "abc")); // "123abc"
    

En nuestra función verificadora $verify() cuando un parámetro no tiene valor por defecto o específicamente es valorado a undefined entenderemos que el tipo del parámetro es undefined. En ese caso se verificará correctamente sea cual sea el tipo del argumento. El ejemplo anterior con verificación nos dará los mismos resultados:

function sumar(a=undefined, b=undefined){
    if ($error = $verify(sumar, arguments)) throw new Error($error);
    return a+b;
}
console.log(sumar(1, 2)); // 3
console.log(sumar(1)); // NaN
console.log(sumar(123, "abc")); // "123abc"
    

Los tipos que se verificarán son todos los primitivos number, string, boolean y symbol. Los tipos undefined son verificados siempre correctamente tal como comentamos antes. El tipo null obligará a que el argumento sea también null. En cuanto a los tipos superiores incluimos array y object. En el siguiente ejemplo un tipo es nulo:

function fun(a=null, b=0){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return b;
}
console.log(fun(null, 123)); // 123
console.log(fun("abc", 123));
//Verificación de tipos en la función 'fun(a, b)': 
//El tipo 'string' del argumento 'a' es distinto 
//del tipo 'null' declarado en su parámetro.
    

En el siguiente código esperamos un array para multiplicar sus elementos. Si pasamos algo que no sea un array nos acusará un error. Por supuesto, si omitimos el argumento es como si lo pasáramos undefined y pasará la verificación. En ese caso se usará el valor por defecto del parámetro, un Array vacío:

function multiply(arr=[]){
    if ($error = $verify(multiply, arguments)) throw new Error($error);
    return arr.reduce((p, v) => p*v, 1);
}
console.log(multiply([1, 2, 3])); // 6
console.log(multiply()); // 1
console.log(multiply("abc")); // Error: Verificación de tipos en la
//función 'multiply(arr)': El tipo 'string' del argumento 'arr' es
//distinto del tipo 'array' declarado en su parámetro.
    

Los parámetros por defecto también pueden pasarse en un destructuring de objeto, tal como explicamos en el tema desestructurado de parámetros. Se trata de pasar un destructuring de objeto donde sus propiedades serán los parámetros que vamos a usar en la función. Esto tiene la ventaja de que llamamos a la función con argumentos con nombre y sin importar el orden. Si la función verificadora encuentra una estructura con un único parámetro objeto de la forma {p1:v1, p2:v2, ...}={} verificará las propiedades del objeto como los efectivos parámetros a usar:

function sumar({a=0, b=0}={}){
    if ($error = $verify(sumar, arguments)) throw new Error($error);
    return a+b;
}
console.log(sumar({a:1, b:2})); // 3
console.log(sumar({b:1, a:2})); // 3
console.log(sumar({a:1})); // 1
console.log(sumar({a:1, b:"abc"})); // Error: Verificación de tipos
//en la función 'sumar({a, b})': El tipo 'string' del argumento 'b'
//es distinto del tipo 'number' declarado en su parámetro.
    

El siguiente ejemplo interactivo le permitirá probar distintos casos de uso:

Ejemplo: Verificador de tipos en argumentos de funciones

Tenemos una función con un parámetro de cada tipo. Supongamos que con cada argumento recibido vamos a realizar una operación concreta que nos obliga a verificar los tipos, pues en otro caso nos daría un error o un resultado no esperado. Aunque en el ejemplo nos limitamos a devolver los valores. La siguiente opción parámetros en un objeto se explica en el texto.

function fun(str="", num=0, bol=false, sym=Symbol(), 
arr=[], obj={}, und=undefined, nul=null, zzz){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return [str, num, bol, sym, arr, obj, und, nul];
}
        
function fun({str="", num=0, bol=false, sym=Symbol(), 
arr=[], obj={}, und=undefined, nul=null, zzz}={}){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return [str, num, bol, sym, arr, obj, und, nul];
}
        

Llamaremos a la función con los argumentos de la tabla siguiente:

let resultado = fun("abc", 123, true, 
Symbol.for("x"), [1,2], {a:1, b:2},
undefined, null, "abc");
        
let resultado = fun({str: "abc", num: 123, bol: true, 
sym: Symbol.for("x"), arr: [1,2], obj: {a:1, b:2},
und: undefined, nul: null, "abc"});
        

En esta tabla puede modificar un valor para simular que no es del tipo esperado y observar el error que se genera:

ParámetroTipoArgumento
str=""string
num=0number
bol=falseboolean
sym=Symbol()symbol
arr=[]array
obj={}object
und=undefinedundefined
nul=nullnull
zzzSin valor

Si no hay error el valor retornado por la función deberá ser como lo siguiente:

["abc", 123, true, Symbol("x"), [1, 2], {a: 1, b: 2}, undefined, null, "abc"]

No causará error si utiliza valores undefined en cualquier argumento, o bien si en el parámetro con ese tipo usa cualquier valor. O con el parámetro zzz que no tiene valor predeterminado y se le asigna el tipo undefined. En otro caso si a un argumento asignamos un valor no coherente con el tipo del parámetro nos aparecerá el mensaje del primer error encontrado:

La función fun() tiene ahora un objeto con los tipos declarados en sus parámetros obtenidos a partir de los valores predeterminados:

Este ejemplo usa ES6 en modo estricto. Puedes consultar el código JS original de este ejemplo.

Valores en los parámetros

En los ejemplos de los apartados anteriores usábamos una cadena de parámetros con valores predeterminados de tipos primitivos u objetos vacíos: "", 0, false, Symbol(), [], {}. Sin embargo para los tipos string, number, boolean y symbol podríamos usar cualquier valor literal, como en este ejemplo:

function fun(a="abc", b=-10E+2, c=true, d=Symbol.for("x")){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return a + b + c + d.toString();
}
console.log(fun("XYZ", 123, false, Symbol.for("y")));
// XYZ123falseSymbol(y)
console.log(fun(123)); // Error: Verificación de tipos en la función
// 'fun(a, b, c, d)': El tipo 'number' del argumento 'a' es distinto
// del tipo 'string' declarado en su parámetro.
    

Un valor predeterminado en un parámetro puede ser cualquier expresión que devuelva un valor. Pero la función verificadora tiene que parsear los parámetros para obtener los tipos a partir de los valores. Y esto lo hacemos con expresiones regulares.

No es posible parsear JavaScript con cierta complejidad usando sólo expresiones regulares. Es posible parsear valores literales string, number, boolean y symbol. Pero si pensamos en un Array o un objeto que contenga a su vez una mezcla de tipos diferentes y anidados, el parseado con expresiones regulares puede resultar imposible.

Además una expresión que devuelva un valor podría ser algo complejo de parsear como function (a=0, b=a+1). Si sólo pasamos un argumento en la llamada, el tipo del segundo argumento está condicionado por el primero. Si es un número, el segundo también será un número. Pero si es un string el segundo será de ese tipo. El tema podría complicarse más, pues incluso podemos usar una función en ejecución como la del siguiente código.

function fun(a=1, b=function(){return a+1}()){
    return a + b;
}
console.log(fun()); //3
    

Se evidencia que sólo es posible obtener el tipo de los valores predeterminados antes de la ejecución de la función sólo si son valores de tipos primitivos. Por lo tanto condicionamos esto y además que los array y object sean vacíos. La función del siguiente ejemplo no será verificada pues tiene un valor predeterminado que es un Array no vacío. Se observa que la función se ejecuta sin error, pero no verifica que los tipos de los argumentos no coinciden con los de los parámetros. Vea que el objeto de tipos resultante es nulo.

function fun(a="abc", b=[1, 2]){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return a + b;
}
console.log(fun(1, 2)); // 3
console.log(fun[Symbol.for("$types")]); // null
    

En el siguiente ejemplo tenemos un valor predeterminado que es una expresión, por lo que la función no será verificada:

function fun(a=1, b=a+1){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return a + b;
}
console.log(fun(1, 2)); // 3
console.log(fun[Symbol.for("$types")]); // null
    

Lo que si permite la función verificadora es un parámetro sin valor predeterminado. En ese caso se le adjudica el tipo undefined, como en este ejemplo para el segundo parámetro:

function fun(a=1, b){
    if ($error = $verify(fun, arguments)) throw new Error($error);
    return a + b;
}
console.log(fun(2, "a")); // 2a
console.log(fun[Symbol.for("$types")].types);
// {a: "number", b: "undefined"}
    

Verificando funciones

Podemos verificar una declaración de función:

function funDec(a=0){
    if ($error = $verify(funDec, arguments)) throw new Error($error);
    return a;
}
console.log(funDec("abc"));
//Error: Verificación de tipos en la función 'funDec(a)': El tipo 
//'string' del argumento 'a' es distinto del tipo 'number' 
//declarado en su parámetro.
    

También una expresión de función:

let funExp = function (a=0){
    if ($error = $verify(funExp, arguments)) throw new Error($error);
    return a;
}
console.log(funExp("abc"));
//Error: Verificación de tipos en la función 'funExp(a)': El tipo 
//'string' del argumento 'a' es distinto del tipo 'number' 
//declarado en su parámetro.
    

Una IIFE o función auto-ejecutable también puede verificarse, aunque debe ser nominada para poder referirnos a ella en la llamada a $verify():

(function iife(a=0){
    if ($error = $verify(iife, arguments)) throw new Error($error);
    console.log(a);
})("abc");
//Error: Verificación de tipos en la función 'iife(a)': El tipo 
//'string' del argumento 'a' es distinto del tipo 'number' 
//declarado en su parámetro.
    

Incluso también podemos verificar parámetros de una función generadora. Vea que ejecutar un generador no supone que se ejecute su código, pues se lleva a cabo con el primer next(). En este ejemplo el generador espera un número. Creamos un primer iterador enviando un 7 que es verificado correctamente. Con un segundo iterador envíamos un string observándose el error de tipo:

function* gen(a=0){
    if ($error = $verify(gen, arguments)) throw new Error($error);
    let x = 3 * (yield a);
    yield x;
} 
let iter1 = gen(7);
console.log(iter1.next()); // Object {value: 7, done: false}
console.log(iter1.next(100)); // Object {value: 300, done: false}
let iter2 = gen("x");
console.log(iter2.next());
//Verificación de tipos en la función 'gen(a)': El tipo 
//'string' del argumento 'a' es distinto del tipo 'number'
//declarado en su parámetro.
    

Sin embargo no podemos verificar una función flecha con esta técnica. Esto es debido a que las funciones flecha no exponen el objeto arguments, como se observa en este código. La función es ejecutada sin error, siendo nulo el objeto de tipos, indicativo de que no reconoció el patrón de función.

let funFle = (a=0) => {
    console.log(arguments); // []
    if ($error = $verify(funFle, arguments)) throw new Error($error);
    return a;
}
console.log(funFle("abc")); // abc
console.log(funFle[Symbol.for("$types")]); // null
    

Implementando la línea verificadora

He intentado hacer que la línea verificadora no dependa de la función. Si en lugar de $verify(func, arguments) pudiéramos poner $verify(this) siendo this una auto-referencia a la función, evitaríamos que esa línea tuviera que ser modificada en cada función para particularizarla con la correspondiente función:

//Error: this no auto-referencia la función
function sumar(a=0, b=0){
    if ($error = $verify(this)) throw new Error($error);
    return a+b;
}
    

La referencia this apunta a undefined en modo estricto y a Window en modo normal. No hay forma de obtener una auto-referencia a la propia función. A no ser que estemos en modo normal y usemos el casi-obsoleto arguments.callee:

//"use strict";
function sumar(a, b){
    console.log(arguments.callee); // function sumar(a,b){..}
}
sumar(1, 2); 
    

Pero en modo estricto nos daría el error TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them. Como considero que debemos usar JavaScript siempre en modo estricto es por lo que he preferido no liar más el asunto y pasar explicítamente la función en $verify(func, arguments).

Si estuviésemos en modo normal podríamos no pasar los argumentos haciendo $verify(func) y obtenerlos allí con func.arguments. Pero en modo estricto arguments sólo es accesible dentro de la función que se está ejecutando. Así que por ahora no queda más remedio que verificar con $verify(func, arguments).

Detalles de la función verificadora de tipos

Figura
Figura. Objeto de tipos para verificar los argumentos.

En este apartado explicaré un poco el código de la función $verify(func, args) para verificar los tipos de los argumentos. Cuando ejecutamos la función comprobamos que ya tenga un objeto [Symbol.for("$types")], como el de la Figura, que hemos obtenido con un ejemplo como function fun(a="", b=0). Así el primer parámetro es de tipo string y el segundo es number, tipos que obtenemos de los valores predeterminados. Ese objeto de tipos nos servirá para verificar los argumentos.

Los códigos expuestos en este apartado están simplificados para una mejor explicación. Puede verlos completos en el código de verificación de tipos.

Para la verificación empezamos preguntando si la función ya tiene ese objeto con los tipos. Si no los tiene los agregamos por primera y única vez usando la función $addTypes(func) que comentaremos más abajo. El código de la parte que verifica los tipos se basa en montar un objeto usando como claves las del objeto de tipos y como valores los asignados en orden izquierda-derecha desde los argumentos. Esto lo hacemos así pues pueden haber menos argumentos que parámetros. Luego comprobamos que los tipos de esos valores coinciden con los tipos del objeto de tipos.

const $verify = function(func, args){
    let error = false;
    if (typeof func[Symbol.for("$types")] === "undefined") {
        $addTypes(func);
    }
    if (func[Symbol.for("$types")] !== null){
        let  types = func[Symbol.for("$types")].types;
        if (args === null || typeof args !== "object" || 
        !args.hasOwnProperty("length")){
            error = `No existe arguments.`;
        } else if (args.length>0) {
            let obj = {}, n = -1;
            for (let key of Object.keys(types)){
                n++; if (n>=args.length) break;
                obj[key] = args[n];
            }
            for (let key of Object.keys(obj)){
                if (!types.hasOwnProperty(key)){
                    error = `No hay tipo para el argumento '${key}'.`;
                } else if (obj[key]!==undefined && 
                types[key] !== undefined && 
                types[key] !== "undefined") {
                    let tipo = typeof obj[key];
                    if (obj[key]===null){
                        tipo = "null";
                    } else if (tipo === "object"){
                        tipo = obj[key].constructor.name.toLowerCase();
                    }
                    if (tipo !== types[key]) {
                        error =  `El tipo '${tipo}' del argumento ` +
                        `'${key}' no es del tipo '${types[key]}' `+
                        `declarado en su parámetro.`;
                    }
                }
                if (error) break;
            }
        }
    }
    return error;
};
    

La función que agrega un objeto de tipos a una función es la del código siguiente, donde se observa que tomamos el código de la función para buscar un patrón de parámetros en su declaración. Hemos omitido las expresiones regulares de las variables patron y patronParametros por simplicidad. El primer patrón comprueba si el código de la función contiene una cadena de parámetros con valores predeterminados de tipos primitivos, objetos o Array vacíos y parámetros sin valores predeterminados. En otro caso el patrón fallará y la función $addTypes() agregará un valor nulo al objeto de tipos Symbol.for("$types") pero sin cursar error alguno. Eso hará que la función $verify() no ejecute la verificación, con lo que se comportará como si no hubiera verificación de tipos.

Si el patrón no falla montamos el objeto de tipos a partir de los valores predeterminados usando el segundo patron. La propiedad paramsInObject es un booleano para indicar si los parámetros se están suministrando con un destructuring de objeto, algo como function fun({a=0, b=0}={}).

const $addTypes = function(func){
    let code = func.toString();
    let patron = "..."; // Omitido por simplicidad
    let reg = new RegExp(patron);
    let bus = code.match(reg);
    if (!bus){
        func[Symbol.for("$types")] = null;
    } else {
        let lista = bus[1];
        let paramsInObject = /\}\s*=\s*\{\s*\}$/.test(lista);
        func[Symbol.for("$types")] = {
            types: null, 
            paramsInObject: paramsInObject
        };
        let patronParametros = "..."; // Omitido por simplicidad
        let reg = new RegExp(patronParametros, "g");
        bus = [];
        while ((temp = reg.exec(lista)) !== null) bus.push(temp);
        let tipos = {};
        for (let item of bus){
            let nombre = item[1], valor = item[2];
            if (!valor) valor = "undefined";
            tipos[nombre] = ("'\"`".includes(valor[0]))?"string":
            (/^[\d.+-]/.test(valor))?"number":
            (valor==="true"||valor==="false")?"boolean":
            (valor.indexOf("Symbol")===0)?"symbol":
            (valor[0]==="[")?"array":
            (valor[0]==="{")?"object":
            valor;
        }
        func[Symbol.for("$types")].types = tipos;
    }
};