Las Funciones Flecha en Javascript. Parte 1

22 Jun 2016

Introducción

Las funciones flecha son una de las novedades ES6 (ECMAScript 2015) que más pueden desconcertar a los desarrolladores tradicionales. Su estructura intimida a simple vista y, de hecho, cuando nos las encontramos en un código sin avisar, pueden parecer incluso un error de sintaxis. Quizá ya os ha pasado: revisáis un código moderno en un blog, o en un repositorio de GitHub, e inmediatamente se os va la vista a una estructura rara que no parece que vaya a funcionar en la vida. -¿Pero eso es Javascript? – sería otra pregunta recurrente que quizá os haya hecho algún colega mientras jugáis con esta nueva sintaxis a escondidas…

Pero como suele ocurrir, no son algo radicalmente nuevo: los programadores que conozcan por ejemplo la sintaxis de CoffeeScript se sentirán como en casa; es una estructura natural que resulta cómoda, menos redundante que la tradicional, y que de paso pretende solucionar los malentendidos que históricamente ha generado el maldito valor de this en Javascript.

Dado que es un tema complejo que puede dar mucho juego, vamos a dedicarle un par de entradas para estudiarlo. Analizaremos su sentido, estructura, finalidad, compatibilidad y limitaciones…

¡Vamos a ello!

Un vistazo rápido: su sintaxis

La sintaxis de estas funciones es, como mínimo, exótica. Como comenté más arriba, parecen más un error de sintaxis que una estructura válida:

( param1, param2, ...rest ) => { expression; }

Eso sería la definición formal, pero como así no nos enteramos de nada, vamos a tomar un ejemplo en código real:

var add = ( x, y ) => x + y;

Bueno… quizá así tampoco lo veamos claro 😀 Pero solo es cuestión de familiarizarse con esta estructura… básicamente, esa función de ahí arriba (una función pura por cierto), devuelve la suma de los parámetros dados:

console.info( add( 2, 4 ) ); // 6
console.info( add( 4, 4 ) ); // 8
console.info( add( 1, 2 ) ); // 3

Comparemos nuestra función de tipo flecha con su correspondecia en el Javascript más estándar/clásico:

function add ( a, b ) {
    return a + b;
}

Desde un punto de vista de la sintaxis, en nuestra función tradicional tenemos un identificador (un nombre), una recogida de parámetros, un cuerpo y su argumento de retorno. Con este dibujito podemos identificarlo todo gráficamente:

flecha-1

Con las funciones flecha, buscamos simplificar todo lo anterior de un modo mucho más directo y declarativo:

  • Eliminamos la palabra reservada function y nos limitamos a recoger los parámetros mediante los paréntesis tradicionales.
  • Podemos eliminar las llaves que delimitan el scope abriéndolo con una flecha.
  • Podemos eliminar la palabra reservada return.

NOTA: Cuando me refiero a ‘function’ o ‘return’, estoy evitando usar la palabra ‘comando’ porque cada vez que la veo en castellano, se me ponen los pelos de punta. Es una mala traducción (como tantas otras) del inglés command. Sería más apropiado el término ‘instrucción’, pero como tampoco me convence, pues nada: palabra reservada.

Si analizamos la función flecha con otro dibujo, se verán mejor las correspondencias:

flecha-2

Algunas cosas que saltan a la vista:

  • Las funciones flecha son siempre anónimas. Estamos utilizando una estructura declarativa donde asignamos la función a una variable. Eso nos permite su reutilación, pero por definición, estas funciones son anónimas.
  • La sintaxis es mucho más limpia y simple. Superado el shock inicial de su estructura, rápidamente nos acostumbramos y la forma tradicional comienza a parecer verborreica (sí, el tan frecuente verbose anglosajón existe en castellano, ¿por qué no usarlo?).

Preguntas rápidas que se nos echan encima

  • ¿Qué ocurre si solo tenemos un parámetro, o ninguno, o infinitos?
  • ¿Qué ocurre si nuestra función necesita realizar varias operaciones? ¿Cómo desarrollo el cuerpo si no he visto llaves? ¿Cómo indico el valor de retorno?
  • Se ha mencionado más arriba pero, ¿qué ha cambiado en el this?

Vamos a ir viendo ejemplos…

Si solo tenemos un parámetro, podemos obviar el paréntesis:

var double = x => x * 2;
 
console.info( double( 2 ) ); // 4
console.info( double( 5 ) ); // 10

Si no necesitamos parámetros, tenemos que incluir el paréntesis vacío:

var hi = () => 'Hello World';
 
console.info( hi() ); // Hello World

Si necesitamos una entrada más compleja, un aspecto importante es que el objeto arguments no funciona:

var test = () => arguments;
 
console.info( test( 'foo', 'bar' ) ); // ERROR: arguments is not defined

Sin embargo, el operador de arrastre sí lo hace correctamente:

var test = ( ...args ) => args;
 
console.info( test( 'foo', 'bar' ) ); // [ 'foo', 'bar' ]

Si necesitamos incluir varias instrucciones en nuestra función, entonces necesitamos incluir las clásicas llaves y el retorno:

var foo = ( param1, param2 ) => {
    var result;
 
    // Do amazings things here...
 
    return result;
};

NOTA: Ojo con las llaves. Un error de sintaxis frecuente es olvidar la flecha delante.

Un ejercicio práctico de refactorizado

Ahora que más o menos conocemos la estructura, veamos cómo convertir una función clásica a esta nueva sintaxis. Para no perdernos de golpe, lo haremos de forma progresiva.

Cojamos por ejemplo esta función que calcula la media aritmética de los valores de una matriz (un array) dado.

function getAvg( ...values ) {
    return values.reduce( function ( p, c ) {
        return p + c;
    } ) / values.length;
}
 
getAvg( 3, 7 ); // 5
getAvg( 2, 10 ); // 6

La idea es simple: sumamos todos los valores y los dividimos por el número total de ellos. En el cole, esto nos habría venido bien para saber nuestras notas finales 🙂

Pasemos ahora a la conversión. Tenemos aquí una función dentro de otra, lo que permite mucho juego. Eliminemos por ejemplo la primera parte:

var getAvg = ( ...values ) => {
    return values.reduce( function ( p, c ) {
        return p + c;
    } ) / values.length;
}

Genial; hemos eliminado la instrucción ‘function’ pero dejado el ‘return’ de momento. Ahora eliminemos la segunda función:

var getAvg = ( ...values ) => {
    return values.reduce( ( p, c ) => p + c ) / values.length;
}

Esto se va quedando limpito: la segunda nos ha permitido eliminar tanto la instrucción ‘function’ como su las llaves y el ‘return’.

Vamos a por otro paso eliminando el primer ‘return’:

var getAvg = ( ...values ) => values.reduce( ( p, c ) => p + c ) / values.length;

Esto está mucho mejor, pero es una línea muy larga. Javascript nos permite introducir un salto de línea tras las flechas, lo que facilita un código indentado más legible:

var getAvg = ( ...values ) =>
    values.reduce(
        ( p, c ) => p + c
    ) / values.length;

El primer paréntesis es necesario por el hecho de que para recoger los valores, estamos usando el operador de arrastre (…values). Si fuese un parámetro normal, como una matriz directamente, podríamos eliminarlo. Quedaría así:

var getAvg = arr =>
    arr.reduce(
        ( p, c ) => p + c
    ) / arr.length;
 
console.info( getAvg( [ 2, 8 ] ) );

La diferencia en este caso es que como entrada, necesitamos ya indicar un ‘array’.

Realizar este tipo de ejercicios de refactorizado es una excelente manera de que nos acostumbremos a utilizar estas funciones.

Conclusión (parte 1)

Las funciones flechas son una herramienta interesante que pueden resultar intimidantes si no se miran con calma. Para algunos, serán una evolución ‘natural’, pero para otros desarrolladores más tradicionales, suponen un cambio importante en la sintaxis al que puede costar acostumbrarse. Si estáis dentro de este grupo, os animo a que hagáis unos cuantos ejercicios de refactorizado como el de arriba para ir practicando.

Tras las formas, lo que tenemos finalmente es una reducción de la sintaxis, un código presumiblemente más limpio, y una corrección necesaria que aún no hemos tratado: la del contexto y valor de ‘this’.

Ese tema, junto a los usos avanzados de estas funciones, su compatibilidad y otras hierbas, quedan para la segunda parte de este artículo.

Más:

Aún no tenemos debug!
Ningún marciano se ha comunicado.

Deja un comentario

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