Friday, 13 May 2016

Variadic Functions

The recently released node.js 6.0 supports almost all the ES6 features, which is great news. It's pretty nice to be able to write some ES6 code and run it with no need of a transpiler nor anything. This has prompted me into trying to learn all the new ES6 features. The new way to work with variadic functions (yep, this is one of those nice terms to drop in the middle of some technical discussion to gain some recognition :-D) is pretty sweet.

You use the "..." syntax (when used in the function signature it's the rest operator) to declare a function of variable arity. This way you get an array with that variable number of arguments. You use it like this:

function sortAndFormat(...items){
	return "[" + items.sort().join("_") + "]";
}

console.log(sortAndFormat("Xixon", "Paris", "Toulouse", "Berlin"));

Now comes the interesting part. What if the parameters that we want to pass to the function are already in an array? We have to use the "..." syntax in the function invocation (now it works as the spread operator, that is nicely used in many other circunstances).

var cities = ["Xixon", "Paris", "Toulouse", "Berlin"];

console.log(sortAndFormat(...cities));

When a function is variadic the ES compiler will wrap the parameters being passed in an array when doing the call. If we are using spread and rest we are doing 2 inverse operations, so one could think if maybe the compiler will optimize it and do nothing, but it doesn't seem so:

var things = ["a", "e", "o"];
function checkReferences(...items){
	if (items === things){
		console.log("same reference");
	}
	else{
		console.log("different references");
	}
}

checkReferences(...things);
//prints different references

Coming back to the rest-spread combination, during the function invocation the spread operator will turn the array into multiple parameters, that because of the the rest operator in the function declaration will be received in an array. This is quite different from how it is done in C# (and I guess also in Java). If a method uses the params keyword (variadic method) and we have our arguments in an array, we can invoke it directly, without any extra step.


	public static string SortAndFormat(params String[] items)
	{
		return "[" + String.Join("_", items.OrderBy(it => it)) + "]";
	}

var cities = new string[]{Xixon", "Paris", "Toulouse", "Berlin"};
SortAndFormat(cities); 

When invoking a method that has the params keyword, the C# compiler will add code to put the arguments into an array to pass to that method, except if there is a single argument and it is an Array of the arguments type. In that case the compiler assumes that you want to use each element of the array as an argument.

This way of working leads to a problematic case in C#. Let's say a method expects n objects as parameters and in one invocation we want to pass it an array of object as its unique parameter. In that case we have to do some trick. As in this case the compiler will not do the extra wrapping himself, we can wrap that Object[] into another [] ourselves. Another option is to cast that Object[] as Object, so that in that case the compiler will add the extra wrapping code. I already posted about this a few years ago

Console.WriteLine(HowManyArrays(new Object[]{stringAr}));
		Console.WriteLine(HowManyArrays((Object)stringAr));

For that case in ES6, you just will pass the array, skipping the use of the spread operator.

console.log(howManyArrays([]));

Both languages use an inverse approach. In ES6, due to the existence of the spread operator it's very simple to turn an array into arguments, so if that's the behaviour you want you have to do it explicit by using the operator. In C#, the lack of such operator forces the compiler to decide by default that if you are passing an array it means that you want to pass its items, otherwise there would be no way to do it.

This rest-spread functionality is making the use of my beloved arguments pseudoarray mainly unnecessary. It seems they are trying to kill an Optimization killer

No comments:

Post a Comment