Splat operator en JavaScript

Introducción

Dentro de las numerosas “mejoras” que provee CoffeeScript con respecto a JavaScript plano, podemos mencionar el operador `splat` (nombre que toma de Ruby) también conocido como `spread`, que básicamente permite agrupar los argumentos que excedan los declarados en la firma del método/función en un único argumento en forma de lista (un array en JavaScript, para ser más preciso).

Un ejemplo sencillo se puede observar en el siguiente ejemplo dela documentación de CoffeeScript, donde runners:

race = (winner, runners...) ->
  console.log winner, runners

race "foo", "bar", "baz", "me" # foo ["bar", "baz", "me"]

La sintaxis varía de lenguaje a lenguaje, teniendo algunas diferencias, como el hecho que algunos solo soportan el operador como último argumento. Algunas breves muestras:

CoffeeScript

test = (...args) ->
	#...

Ruby

def test(*rest)
	#...
end

Java

void test (int ... numbers) {
	//...
}

ActionScript3+

function test(... args):void
{
	// ...
}

Por lo general, el uso de este operador suele darse en el último argumento de las funciones, siendo las mismas de la forma:

fun(arg1, arg2, ...splat)

Que básicamente equivale a:

fun(arg1, arg2[, item[, item[, ...]]])

Por suerte, este comportamiento no está limitado al caso expuesto, sino que podemos aplicar dicho operador a argumentos intermedios, de la forma:

fun(arg1, ...splat, arg2, arg3)

Que equivale a:

fun(arg1, [item[, item[, ...,]]] arg2, arg3)

Dicha forma aplicará la firma dependiendo la cantidad que reciba

fun(1, 2) -> 1, void, 2, void
fun(1, 2, 3) -> 1, void, 2, 3
fun(1, 2, 3, 4) -> 1, [2], 3, 4
fun(1, 2, 3, 4, 5) -> 1, [2, 3], 4, 5
fun(1, 2, 3, 4, 5, 6) -> 1, [2, 3, 4], 5, 6

Desgraciadamente en JavaScript no tenemos este magnifico feature de forma nativa (todavía…), y si bien podemos emularlo, el resultado es bastante más engorroso de lo deseable.

Implementación

Para implementar este tipo de funcionalidad, basta con echar mano a los function decorators, tema del cual se hablo con anterioridad en este blog. La idea del function decorator es crear una función que envuelva a otras, alterando o no el proceso de entrada/salida de la misma.

Nuestro decorator inicial se corresponderia con el siguiente caso de uso (limitado al último argumento):

function splatify(fn) {
	// implementacion
}

function race(winner, runners) {
	console.log(winner, runners);
}

var splattedRace = splatify(race);

race(1, 2, 3, 4); // 1, 2
splattedRace(1, 2, 3, 4); // 1, [2, 3, 4]

Para llevar a cabo esta simple implementación, vamos a echar mano a la propiedad `length` del objeto Function, que nos dice cuantos argumentos espera la función.

Nuestro decorador recibe la función, la envuelve con otra que genera los nuevos argumentos y la devuelve lista para usar:

function splatify(fn) {
	return function() {
		// Convertimos el objeto arguments en un array
		var args = [].slice.call(arguments);

		// En esta version simplificada utilizamos siempre
		// el ultimo argumento
		var index = fn.length - 1;

		// Nuestros nuevos argumentos, completamos todos los
		// argumentos, menos el ultimo
		var splattedArguments = args.slice(0, index);

		// Y completamos el ultimo con el resto
		splattedArguments = splattedArguments.concat([ args.slice(index) ]);

		// Finalmente ejecutamos la funcion con los nuevos
		// argumentos
		return fn.apply(this, splattedArguments)
	}
}

Esta implementación es bastante simple, pero sería mejor si tuviese soporte para splats en cualquier posición, lo cual modifica un poco la sintaxis final:

function splat(fn) {
	// implementacion
}

var splattedRace = splat(race).byName('winner');
splattedRace(1, 2, 3, 4); // [1, 2, 3], 4

// O agregando la funcion al Function#prototype
Function.prototype.splat = function(target) {
	return splat(this)(target);
};

race.splat(-1)(1, 2, 3, 4); // 1, [2, 3, 4]

Esta implementación la dejo directamente en GitHub, disponible para toda aquella persona que quiera ver y criticar:

Splat.js en GitHub (Gist)



Deja un comentario

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

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>