Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin methods with Vararg as First Parameter

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.

like image 944
Les Avatar asked Sep 27 '17 19:09

Les


3 Answers

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.

like image 111
zsmb13 Avatar answered Nov 03 '22 08:11

zsmb13


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) {} 
like image 24
Les Avatar answered Nov 03 '22 08:11

Les


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.

like image 25
Rajesh Dalsaniya Avatar answered Nov 03 '22 07:11

Rajesh Dalsaniya