Sequences and sequenced parameters

Posted by    |       Ceylon

I've been thinking about the problem of passing a Sequence of values to a sequenced parameter in Ceylon (a varargs parameter in Java terminology). Consider:

void print(Object... objects) { ... }
String[] words = {"hello", "world"};
print(words);   //what does this do?

Does the second line mean that we're passing a single Sequence<String> to print(), or two Strings? Java behaves very strangely in this situation:

print(new String[]{"hello", "world"}); //passes two Strings with a compiler warning asking for an explicit cast to Object[]
print(new Object[]{"hello", "world"}); //passes two Objects with no compiler warning
print(new String[]{"hello", "world"}, new String[]{"hello", "world"}); //passes two String[] arrays as varargs!


Things gets even a little more complicated when you have a generic method like this:

T? first<T>(T... objects) { ... }
String[] words = {"hello", "world"};
first(words);    //what type should be inferred for T?

This really starts to screw up my beautiful clean type argument inference algorithm! Which is why this issue is coming up now - it's a corner case that I only noticed once I actually implemented generic type argument inference in the type analyzer.

So I think we need to make you explicitly specify what you mean when you pass a sequence of values to a sequenced parameter. I have a couple of ideas about how to do this.

Solution 1

First solution, kinda indirectly inspired by groovy, would be a special syntax in the positional parameter method invocation protocol.

String[] words = {"hello", "world"};

print(words);      //pass a single String[]
print(words...);   //pass two Strings

String[]? words2 = first(words);   //infers T = String[];
String? word = first(words...);    //infers T = String

I think this reads fairly naturally. The downside is it's a special-purpose kind of punctuation that needs to be specially explained in the specification.

Solution 2

Second solution is to introduce a special type (a subtype of Sequence) to represent a package of sequenced arguments. Call it SequencedArguments. Then, with a little helper method spread() that wraps up a Sequence as a SequencedArguments, the syntax would look like:

String[] words = {"hello", "world"};

print(words);           //compiler automatically produces a SequencedArguments<String[]>
print(spread(words)));  //explicitly pass a SequencedArguments<String>

String[]? words2 = first(spread(words));    //infers T = String[];
String? word = first(spread(words));        //infers T = String

This is a little more verbose, but reasonable. It also makes the specification easier to write.

Solution 3

Solutions 1 and 2 can be combined very elegantly. We can define:

  • T... means SequencedArguments<T> for any type T
  • e... means SequencedArguments(e) for any expression e

So T... is just a type name abbreviation like T[] and T?, and e... is just an operator expression. We end up with exactly the same syntax as Solution 1, but with the semantics of Solution 2.

I think this works out, and is very much in the spirit of the language. On the other hand, if T... is just an ordinary type declaration, I don't know how we can go about enforcing that a sequenced parameter must be the last parameter in a parameter list. I kinda like the fact that this is an error:

void print(Object... objects, OutputStream stream) { ... } //compile error?

WDYT? Does print(words...) read well to you guys, or does it feel arbitrary?


Let's not confuse this too much with the idea of applying an operation to a tuple of arguments like what you can do in functional languages and dynamic languages. This is superficially similar, but not quite the same.


A reasonable syntactic variation that perhaps reads somewhat better would use all as a keyword:

void print(Object all objects) { ... }
print(words);       //pass a single String[]
print(all words);   //pass two Strings

I could probably get into this if I didn't just hate the idea of keywordizing the very useful word all.

Back to top