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:
//Java:
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!
Ugh!
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?
P.S.
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.
UPDATE
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
.
Interesting topic!
But what about words.asArguments() which simply creates a SequencedArguments<T>? This way, you only have to extend the sequence API with the asArguments()-method and avoid new operators (although is still possible) or keywords.
Sorry, is it possible to remove mail email address from the above post? Did not know that it is displayed publicly.
Solution 3 looks pretty reasonable to me. print(words...) makes plenty of sense. Not a big fan of keywording 'all'.
Jeff
Would this apply to all uses of arrays as a sequenced parameter list? Or only when there is a disambiguate? Ex.;
void print(String... strings) { ... } String[] words = {"hello", "world"}; print(words); //Is this ok?Um ... I can remove your whole comment if you like. I can't just edit some part of it...
That would also work. Though perhaps words.arguments would be more idiomatic.
Yeah, after thinking it through a bit, and realizing that ... works out to be kinda like the inverse of the sequence instantiation syntax { a, b, c }, I really warmed up to it. (Note that {foo}... == foo and {foo...}==foo, which is a nice symmetry.
I've implemented this in the type analyzer and spec.
It would apply every time you pass a sequence to a sequenced parameter. i.e. every time you want to treat a parameter declared T... as if it were declared T[].