7

Note I've looked at the following questions/answers to solve the problem without any luck. Call Java Varargs Method from Kotlin - this one has the varargs parmeter at the end of the parameter list, but my question deals with varargs at the start of the parameters list. Kotlin: Convert List to Java Varargs - the same. Other searches yield the same thing. These were the closest I could find.

I am calling the Kotlin String.split method with a single character delimiter. This is a vararg method where the vararg parameter is first of multiple parameters. The method is defined like so:

public fun CharSequence.split(vararg delimiters: Char, 
                              ignoreCase: Boolean = false,
                              limit: Int = 0): List<String>

When I call the method as below, it compiles fine:

fun String.splitRuleSymbol() : String = this.split(':') //ok

But when I try to add the ignoreCase and limit parameters, I get a problem:

fun String.splitRuleSymbol() : String = this.split(':', true, 2) //compiler error

The error I get is...

None of the following functions can be called with the arguments supplied:

public fun CharSequence.split(vararg delimiters: String, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text

public fun CharSequence.split(vararg delimiters: Char, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text

To me, having a vararg parameter followed by other parameters is somewhat odd, but that's beside the point. If I call it as below, it works fine:

 // both of the following compile
 fun String.splitRuleSymbol() : String = 
           this.split(delimiters = ':', ignoreCase = true, limit = 2)
 fun String.splitRuleSymbol2() : String = 
           this.split(';', ignoreCase = true, limit = 2)

Is there a way to pass a vararg Char in to this method without having to qualify my other two parameters with parameter names ignoreCase and limit? Can the compiler not tell that the remaining parameters are not Char?

I have tried the spread operator and a few other ways below , none of which work:

    //compiler errors on all these
    this.split(*':', true, 2) //using the "spread" operator
    this.split(*charArrayOf(':'), true, 2)
    this.split(*mutableListOf(':'), true, 2)
    this.split(*Array<Char>(1) { ':' }, true, 2)

Yes, some of these look ridiculous, I know. But, is there no way to avoid the verbose alternative?

PS As I was formulating my question, I found another expression that compiled.

    this.split(':', limit = 2)

This is less verbose and since I don't need to change the default ignoreCase parameter, it's closer to what I am looking for.

Les
  • 10,335
  • 4
  • 40
  • 60

3 Answers3

11

Your observations are correct. Arguments that are after a vararg parameter can only ever be passed in by using named arguments, otherwise you'd run into ambiguity issues (for a trivial example, let's say when all arguments are of type Any).

The best source I can find for this right now is this book.

The vararg parameter is usually the last parameter, but it does not always have to be. If there are other parameters after vararg, then arguments must be passed in using named parameters

Edit: @Les found a good source on it, see their answer.

zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • Your answer help me to find the definitive paragraph in the Kotlin spec. See my answer. Thx. – Les Sep 28 '17 at 02:45
6

Thanks to zsmb13, I was able to find the following paragraph in the Kotlin Specification (under "Functions and Lambdas")

Only one parameter may be marked as vararg . If a vararg parameter is not the last one in the list, values for the following parameters can be passed using the named argument syntax, or, if the parameter has a function type, by passing a lambda outside parentheses.

I would venture to add that "can be passed" should be changed to "must be passed" since the compiler won't allow otherwise.

Note The lambda part is interesting in that the spec normally only allows a lambda to be moved outside the parenthesis when it is the last parameter. The wording of the spec implies the lambda could be anywhere after the vararg parameter, but experimentation shows that it cannont, i.e., it must be the last parameter in order to be eligible to move outside of the parenthesis.

fun main(args: Array<String>) {
    test("hello", limit = 1, ic = false, delims = ';') { } //ok
    //test2("world", limit = 1, ic = false, delims = ';') { } //error
    test2("world", f = {}, limit = 1, ic = false, delims = ';') //ok
    test("hello world", ';', limit = 1, ic = false) {}  //ok
}

fun test(vararg delims: Char, ic: Boolean, limit: Int, f: () -> Unit) {} 
fun test2(vararg delims: Char, f: () -> Unit, ic: Boolean, limit: Int) {} 
Les
  • 10,335
  • 4
  • 40
  • 60
2

Variable number of arguments (vararg) can be passed in the named form by using the spread operator:

fun foo(vararg strings: String) { /* ... */ }

foo(strings = *arrayOf("a", "b", "c"))
foo(strings = "a") // Not required for a single value

Note that the named argument syntax cannot be used when calling Java functions, because Java bytecode does not always preserve names of function parameters.

Rajesh Dalsaniya
  • 1,977
  • 1
  • 13
  • 12
  • The spread operator isn't necessary when the `vararg` has only one value. Nor is it necessary to use a "named" parameter for the `vararg` or any parameter the occurs before it in the declaration. See my answer. +1 for the part about Java and named arguments. – Les Sep 28 '17 at 02:37