Vararg vs. array parameter


#1

In Java, a varargs parameter can be called using either an array or a list of values. Thus, this works:

void code() {
   abc("A")
   String[] array = new String[1]
   abc(array)
}

void abc(String... x) { ... }

I’d like the same option to allow library users to call the method with a zero or more parameters, or with an array of parameters. So in Kotlin, I tried this code:

fun code() {
    abc("A")
    val array = Array<String>(1) { "B" }
    abc(array)
}

fun abc(vararg x: String) { ... }

which causes a compiler error on the abc(array) call, so I added this overload to abc:

fun abc(x: Array<String>) { ... }

which fails at runtime with this ugly message:

	ERROR: Platform declaration clash: The following declarations have the same JVM signature (abc([Ljava/lang/String;)V):
    fun abc(x: Array<String>): Unit
    fun abc(vararg x: String): Unit (75, 1)
	ERROR: Platform declaration clash: The following declarations have the same JVM signature (abc([Ljava/lang/String;)V):
    fun abc(x: Array<String>): Unit
    fun abc(vararg x: String): Unit (79, 1)

Interestingly, Swift has the same semantics that an array cannot substitute for a varargs parameter, but they do allow the second overload and it works fine.

Any suggestions?


Kotlin or Swift? (isn't a flame war, its about opinion)
#2

Kotlin has the spread operator to do this:
https://kotlinlang.org/docs/reference/functions.html#variable-number-of-arguments-varargs


#3

Yes I know about the spread operator. I’d prefer not to force application programs to use it for these reasons:

  1. There might be hundreds of elements in the array. Could that cause runaway stack issues or poor performance?
  2. It’s uglier than it needs to be for application programmers who use my library. The library is available for other programming languages - I would hope that the use would be as elegant for application programmers in Kotlin as it is in Swift and Java.

#4

If the function doesn’t need to mutate the array elements, then you could a list instead for the overload:

fun abc(x: List<String>) { ... }

#5

Yes, an ArrayList<String> parameter is actually another overload for these methods. I’d like to offer all three alternatives, but it looks like the Kotlin application programmers will be limited to vararg and ArrayList.


#6
  1. vararg is passed as array anyway, there’s nothing to fear about stack issues or poor performance.

  2. I’m not sure why do you think it’s ugly. If you’re passing an array as a multiple parameters, semantically it’s very different case and it’s good to have a dedicated syntax for that. Also there are issues when compiler can’t understand, whether you’re passing a spreaded arguments or just an array as one argument, they are real issues in Java and Kotlin solves this ambiguous situation.


#7

It’s not the vararg overload that I am concerned about.

Here is an example of the two calling patterns in Java (applications will use both depending on situation).

setAttribute("color:grey", "bold", "fontsize:14")
setAttribute(defaultAttributes)

where defaultAttributes is an array that might be specified in a totally different place and that the call site has no control over or knowledge of. In Kotlin, the second call must be specified like this

setAttribute(*defaultAttributes)

Yes, it’s only one character, but it’s one more nail in the coffin of complexity that ends up with more confusion and higher maintenance costs. In apprentice programmer thinking, “some functions have an array parameter that is specified without the *, and some functions require it on array parameters, and I’m not sure why”. The reason that the distinction occurs will only be remembered by folks who use the API frequently. That’s bad (and ugly, IMO).


#8

Hello @DonWills!

Try to use @JvmName annotation to get rid of Platform declaration clash error.

@JvmName("absAll")
fun abc(vararg x: String) { ... }
fun abc(x: Array<String>) { ... }

Use it on function version that unlikely be used from Java.


#9

I don’t see how this is confusing. Kotlin makes a distinction between array and vararg parameters. Yes, they are both implemented as array parameters. But by making the distinction, Kotlin can use its type system to flag erroneous invocations as errors instead of emitting a warning like Java has to do.

Given this declaration:

fun abc(vararg x: String)

Kotlin can and will clearly tell you that you cannot pass an array to this function. And that is completely correct. The Kotlin parameter type is definitely not an array. It is implemented as an array, but that is irrelevant.

If you have an array of strings, you can explicitly (like most conversions in Kotlin) convert it to vararg strings using the spread operator. So the rule to remember is: If I have a vararg parameter of a type and an array of the same type, I can pass that array anyway by prefixing it with *.

Now there are 2 situations where you do get into problems:

  • Overloading the same method with an array parameter. This is not needed, and cannot even be done because you essentially try to define 2 methods with the exact same signature. (Assumes the JVM target. For other targets, different signatures may be possible.)
  • Having a vararg parameter of type Any. Forgetting the spread operator in this case results in a perfectly valid call: acceptsVarargAny(arrayOfSomeType). This will not pass the elements of the array separately, but will pass the array itself as the only element.

You have to know both of these “complexities” when you want to use vararg parameters effectively, but you also have to know the “complexities” of all the other language constructs that you use.


#10

an ArrayList parameter is actually another overload

Erm, don’t you think there could be something wrong with your approach?


#11

No. The use-case is a general purpose API with hundreds of methods that will be used by different programming groups with different programming standards and styles. ArrayList, Array and varargs are all useful paradigms in certain situations. It is not my place to make the decision about which programming style/standard/idiom a programmer using the API must adhere to.


#12

You can have all 3 with 2 functions, but clients that have an array have to use the spread operator then. If that is something they are unable to understand, then use 3 functions with different names:

fun abcAll(vararg x: String)
fun abcArray(x: Array<String>)
fun abcList(x: List<String>)

Both solutions may not be perfect, but they are your only options given the constraints of the JVM.


#13

Ugh. It looks like Objective-C with the names of parameters tacked onto the method name to avoid duplicates. I will not pollute the API with such trash.

And, no, it is not a constraint of the JVM. In an earlier post I noted that the String… parameter of Java accepts callers using either zero or more comma-delimited Strings or a single String array. That’s how I had expected Kotlin to work, but alas, the programming language designers decided to make Kotlin different from Java for this feature.


#14

Yes it is: On the JVM you cannot have method overloads for both a vararg and an array parameter of the same type. There can be only 1 of these, but you are free to choose the notation.

Kotlin does not have this restriction, so it can have three functions with the same name and parameter types: vararg, array and list. But because the JVM does not support it, you have to use the solution with @JvmName that @abond suggested above.

You might feel that the Java is less restrictive than Kotlin, but Kotlin tries to prevent the subtle bugs that can occur when you allow passing an array of Object where a series of Object values are expected. Java has warnings to notify you of possibly incorrect code. Kotlin does not need those.

I think this:

fun acceptsAnys(vararg anys: Any) { ... }
val strings = arrayOf("foo", "bar")
acceptsAnys(strings) // Invokes with 1 parameter: an array of strings
acceptsAnys(*strings) // Invokes with 2 parameters: "foo" and "bar". I understand that
        // it can be 0 or more parameters because I explicitly told the compiler I want
        // it that way

Is a lot clearer than:

void acceptsObjects(Object... objects) { ... }
String[] strings = new String[] { "foo", "bar"};
acceptsObjects((Object)strings); // Invokes with 1 parameter: an array of strings. What?
        // Why do I have to add this weird cast?
acceptsObjects(strings); // Invokes with 2 parameters: "foo" and "bar". What? But I pass
        // exactly 1 object, which happens to be an array, and now Java converts that
        // array to the elements it contains and passes those

#15

I don’t want to get into a factual tussle here. I suspect you are confusing the JVM with a programming language that emits JVM byte codes (e.g. Java, Kotlin and others). They are quite different things. There is no concept of varargs in the JVM (specifically, in the Java Virtual Machine specification as updated). Period. The Java Programming language (note the distinction) compiles successfully if the call site has either a list or an array for a single target method with the … syntax as the last parameter in the method definition. In both cases, the Java compiler emits code that is a typed array. Kotlin is different - one person’s subjective description of which is better or less or more restrictive is irrelevant. It was my (naive) assumption that Kotlin would work the same way as Java in this regard. It doesn’t.


#16

Somebody must have misinformed you about Kotlin. Working like Java has not been a goal for the language. It happens to work a lot like Java (e.g. primitive types, type erasure for generics) because having great interoperability with Java was one of the goals.