Seleccionar página

Preguntarse si comparar funciones tiene sentido puede resultar un poco extraño, pero en más ocasiones de las que creemos estamos comparando funciones. Comparar funciones es relativamente sencillo, las funciones son objetos, y como cualquier objeto si dos variables referencian exactamente el mismo elemento y las comparamos, nos van a decir que son iguales.

function sum(a, b) {
    return a + b;
}
var sum2 = sum;
console.log(sum2 === sum);  // true

La cosa se complica, por ejemplo, si comparamos las propiedades de un objeto y entre estas propiedades hay funciones. Dependiendo de cómo se hayan creado las propiedades podrán hacer referencia a la misma función o no. Vamos a ver tres casos diferentes:

function PatternConstructor() {
    this.sum = function (a, b) {
        return a + b;
    }
}
var o1 = new PatternConstructor();
var o2 = new PatternConstructor();
console.log(o1.sum === o2.sum); 	// false

class PatternClass {
    sum (a, b) {
        return a + b;
    }
}
var o3 = new PatternClass();
var o4 = new PatternClass();
console.log(o3.sum === o4.sum); 	// true

function PatternPrototype() {
}
PatternPrototype.prototype.sum = function (a, b) {
    return a + b;
};
var o5 = new PatternPrototype();
var o6 = new PatternPrototype();
console.log(o5.sum === o6.sum);	// true

Como podemos ver, si comparamos las funciones asignadas al objeto en el constructor, son funciones diferentes en cada objeto. Esto es así ya que asignamos las funciones a las propiedades de cada objeto por medio de this.sum. Aunque tengan el mismo código fuente, son funciones independientes y pueden, por ejemplo, mantener retenidas diferentes variables al tener diferentes clausura.

Si hacemos lo mismo con las funciones definidas en la clase o con una cadena de prototipos, hacen referencia exactamente la misma función. Estas funciones son definidas una sola vez y cualquier objeto hace referencia a la función original.

Debemos insistir, las funciones, de forma natural, se comparan igual que los objetos y son iguales sólo si hacen referencia a exactamente el mismo elemento, no si tienen iguales propiedades o el mismo código fuente.

Aquí es donde empieza nuestra discusión, ¿tiene sentido intentar comparar de alguna otra forma dos funciones para comprobar si son equivalentes?

¿Comparar el código fuente?

El código fuente de una función se puede obtener por medio de Function.prototype.toString(), que aunque el resultado depende de la implementación específica de cada motor de Javascript, es parte del estándar desde la versión 1. No confundamos este método con Function.prototype.toSource() que no es parte de estándar y que sólo soporta Firefox.

Si utilizamos .toString() sobre una función definida en nuestros programas obtendremos una representación de su código fuente. De esta forma podríamos cambiar el ejemplo anterior para comparar el código de la función:

function PatternConstructor() {
    this.sum = function (a, b) {
        return a + b;
    }
}
var o1 = new PatternConstructor();
var o2 = new PatternConstructor();
console.log(o1.sum.toString() === o2.sum.toString());   // true

class PatternClass {
    sum (a, b) {
        return a + b;
    }
}
var o3 = new PatternClass();
var o4 = new PatternClass();
console.log(o3.sum.toString() === o4.sum.toString());   // true

function PatternPrototype() {
}
PatternPrototype.prototype.sum = function (a, b) {
    return a + b;
};
var o5 = new PatternPrototype();
var o6 = new PatternPrototype();
console.log(o5.sum.toString() === o6.sum.toString());   // true

Esta aproximación parece muy prometedora, pero si intentamos utilizar este método toString() con funciones definidas por Javascript, el resultado puede ser muy descorazonador, ya que cuando se utiliza con funciones predefinidas obtenemos algo más o menos de este tipo:

console.log(Object.prototype.valueOf.toString());
// function valueOf() { [native code] }

De esta forma, si comparamos dos funciones nativas obtendremos que son siempre iguales si tienen el mismo nombre, aunque su comportamiento pueda ser muy diferente:

console.log(Object.prototype.valueOf.toString() === Number.prototype.valueOf.toString());

¿Podríamos comparar el comportamiento de las funciones?

Podemos ir todavía más allá, dos funciones podrían considerarse iguales si exponen exactamente el mismo interfaz y retornan los mismos valores para los mismos parámetros de entrada, asilándonos de la implementación que internamente tengan. Parece una aproximación muy interesante, pero tiene importantes problemas prácticos.

En primer lugar, no podemos asegurar que las funciones que se nos presenten sean inmutables y no alteren el estado de los objetos u otros elementos que conlleven un cambio de estado de algún tipo. Por ello no podemos establecer un juego de pruebas seguras, donde pasando un de parámetros a dos funciones comprobemos que devuelven el mismo resultado.

El segundo problema es que en cualquier caso sólo podríamos hacer la comprobación con un conjunto finito de parámetros y en ningún caso podríamos abordar un conjunto infinito como son los números enteros o las cadenas de texto, que pueden ser recibidos como parámetros por las funciones que queremos analizar.

¿Cómo abordamos esta situación en equal()?

Para nuestra función equal(), que venimos desarrollando en esta serie de artículos, vamos a aceptar, como aproximación limitada, un parámetro de configuración {functionSource: true} para comparar el código fuente de las funciones. No será útil en muchos casos, como con las funciones nativas de Javascript, pero en algún caso puede ser interesante ver qué resultado se obtiene.

Advertimos que esta es una función experimental y por lo tanto se debe utilizar con cuidado, ya que su resultado puede estar condicionado por alguna característica específica del entorno Javascript donde se está utilizando.

Para hacer esta comparación añadiremos estas líneas a nuestra función:

if ((aType === 'function')) {                // Function type
    if (options.functionSource && a.toString() === b.toString()) {
        return FUNCTION;
    }
    return NOT_EQUAL;
}

Como vemos, aunque parecía que nuestra función equal() ya era bastante completa, lo cierto es que no tenía en cuenta esta situación, que aunque un poco peculiar, no queríamos dejar de tratar. Estamos ya a punto de terminar, aunque todavía nos falta optimizar todo esto para que se ejecute con rapidez y de paso hacer un repaso a algunas aproximaciones de optimización de nuestro código. Puedes descargar la versión final de esqueal.js con npm.

Novedades

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.