Help

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.

8 comments:
 
14. Jul 2011, 09:36 CET | Link

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.

ReplyQuote
 
14. Jul 2011, 09:45 CET | Link

Sorry, is it possible to remove mail email address from the above post? Did not know that it is displayed publicly.

 
14. Jul 2011, 18:44 CET | Link

Solution 3 looks pretty reasonable to me. print(words...) makes plenty of sense. Not a big fan of keywording 'all'.

Jeff

 
15. Jul 2011, 01:38 CET | Link
Sjur

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?
 
17. Jul 2011, 21:16 CET | Link
Roben wrote on Jul 14, 2011 03:45:
Sorry, is it possible to remove mail email address from the above post? Did not know that it is displayed publicly.

Um ... I can remove your whole comment if you like. I can't just edit some part of it...

 
17. Jul 2011, 21:18 CET | Link
Roben wrote on Jul 14, 2011 03:36:
Interesting topic! But what about words.asArguments() which simply creates a SequencedArguments<T>?

That would also work. Though perhaps words.arguments would be more idiomatic.

 
17. Jul 2011, 21:20 CET | Link
Jeff Schnitzer wrote on Jul 14, 2011 12:44:
Solution 3 looks pretty reasonable to me. print(words...) makes plenty of sense. Not a big fan of keywording 'all'. Jeff

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.

 
17. Jul 2011, 21:21 CET | Link
Sjur wrote on Jul 14, 2011 19:38:
Would this apply to all uses of arrays as a sequenced parameter list? Or only when there is a disambiguate?

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[].

Post Comment