Kotlin methods with Vararg as First Parameter - kotlin

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.

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.

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) {}

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.

Related

Understanding a lambda construct that contains dot followed by brackets

This is the function declaration for rememberCoilPainter:
#Composable
fun rememberCoilPainter(
request: Any?,
imageLoader: ImageLoader = CoilPainterDefaults.defaultImageLoader(),
shouldRefetchOnSizeChange: ShouldRefetchOnSizeChange = ShouldRefetchOnSizeChange { _, _ -> false },
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null,
fadeIn: Boolean = false,
fadeInDurationMs: Int = LoadPainterDefaults.FadeInTransitionDuration,
#DrawableRes previewPlaceholder: Int = 0,
): LoadPainter<Any> {
}
The line of code I am having difficulty understanding is:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null
A dot appears after Builder followed by (size: IntSize)
This is the first time I've seen this construct in Kotlin and am not sure how to interpret it. This is a lambda. Normally the dot after an object refers to a sub component of a class or a package. But the ( ) after the dot isn't clear.
How do I implement the requestBuilder parameter?
This is a function with receiver type as described here: https://kotlinlang.org/docs/lambdas.html#function-types
Function types can optionally have an additional receiver type, which is specified before a dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. Function literals with receiver are often used along with these types.
It could be tricky to understand at first, but this is like you are providing a function/lambda that is a method of ImageRequest.Builder. Or in other words: your lambda receives one additional parameter of type ImageRequest.Builder and it is available in the lambda as this.
You can provide requestBuilder as any other lambda, but note that inside it you will have access to properties and methods of ImageRequest.Builder object that was provided to you.
What you are looking at is a "function literal with receiver". Speaking generically, a type A.(B) -> C represents a function that can be called on a receiver object of A with a parameter of B and return a value of C. Or in your example:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)?
We have a function requestBuilder which can be called on a ImageRequest.Builder with a parameter size: IntSize and returns another ImageRequest.Builder.
Calling this function is just like calling any other function with a lambda as a parameter. The difference: You have access to ImageRequest.Builder as this inside your lambda block.
Hope the following example helps understand lambdas with receiver type:
data class Person(val name: String)
fun getPrefixSafely(
prefixLength: Int,
person: Person?,
getPrefix: Person.(Int) -> String): String
{
if (person?.name?.length ?: 0 < prefixLength) return ""
return person?.getPrefix(prefixLength).orEmpty()
}
// Here is how getPrefixSafely can be called
getPrefixSafely(
prefixLength = 2,
person = Person("name"),
getPrefix = { x -> this.name.take(x) }
)
How do I implement the requestBuilder parameter?
Hope this part of the code snippet answers the above:
getPrefix = { x -> this.name.take(x) }
PS: These lambdas with receiver types are similar to extension functions IMO.

How to skip specification of the generic type parameter in Kotlin?

This is the main body of my function
val client = ConnectionFactory.createClient() # <- Return lettice.io RedisClusterClient
val conn = client.connect()
val command = conn.sync()
var index: String? = null
index = readDataStructure(command, key)
This is my first try to define my readDataStructure function:
fun readDataStructure(command: RedisCommand, key: String): String {
...
kotlin complaints error: 3 type arguments expected for interface RedisCommand<K : Any!, V : Any!, T : Any!>
I want to be able to NOT specifying K, V and T because I am just writing a throwaway script.
Is there any Kotlin lang syntax and can allow me to just pass the command variable as is?
I suppose you are after:
fun readDataStructure(command: RedisCommand<*,*,*>, key: String): String {
?
From Kotlin docs https://kotlinlang.org/docs/tutorials/kotlin-for-py/generics.html:
If you don't have any idea (or don't care) what the generic type might be, you can use a star-projection:
fun printSize(items: List<*>) = println(items.size)
When using a generic type where you have star-projected one or more of its type parameters, you can:
Use any members that don't mention the star-projected type parameter(s) at all
Use any members that return the star-projected type parameter(s), but the return type will appear to be Any? (unless the type parameter is constrained, in which case you'll get the type mentioned in the constraint)
Not use any members that take a star-projected type as a parameter

Functional style main function argument parsing for Kotlin

Please let me know if this way I write a Q&A is inappropriate. Also, I am expecting some better answer, too. The both solutions I provided are not perfect.
There are some Kotlin argument parser on the Internet now, for example GitHub: xenomachina/kotlin-argparser, GitHub: Kotlin/kotlinx.cli or GitHub: ajalt/clikt. However I don't want to add such a huge folder into my (maybe) small project. What I want is a simple and clean solution, for example just a function, with a "fluent" stream-style implementation. Instead, those projects are all containing several files.
What I am thinking is, just need to resolve the command line parameter into a Map<String, List<String>>, use map.containsKey() to get no_argument parameter, and use map[key] to get required_argument parameter.
For example, a command line parameter list
-a -b c -d e f g -h --ignore --join k --link m n o -p "q r s"
will be parsed as:
{-a=[], -b=[c], -d=[e, f, g], -h=[], --ignore=[], --join=[k], --link=[m, n, o], -p=[q r s]}
or we say
mapOf(
"-a" to listOf(), // POSIX style, no argument
"-b" to listOf("c"), // POSIX style, with single argument
"-d" to listOf("e", "f", "g"), // POSIX style, with multiple argument
"-h" to listOf(), // POSIX style, no argument
"--ignore" to listOf(), // GNU style, no argument
"--join" to listOf("k"), // GNU style, with single argument
"--link" to listOf("m", "n", "o"), // GNU style, with multiple argument
"-p" to listOf("q r s") // POSIX style, with single argument containing whitespaces
)
Well, my solution involves immutability and folding with last parameter as well.
fun main(args: Array<String>) {
val map = args.fold(Pair(emptyMap<String, List<String>>(), "")) { (map, lastKey), elem ->
if (elem.startsWith("-")) Pair(map + (elem to emptyList()), elem)
else Pair(map + (lastKey to map.getOrDefault(lastKey, emptyList()) + elem), lastKey)
}.first
println(map)
val expected = mapOf(
"-a" to emptyList(),
"-b" to listOf("c"),
"-d" to listOf("e", "f", "g"),
"-h" to emptyList(),
"--ignore" to emptyList(),
"--join" to listOf("k"),
"--link" to listOf("m", "n", "o"),
"-p" to listOf("q r s"))
check(map == expected)
}
Output
{-a=[], -b=[c], -d=[e, f, g], -h=[], --ignore=[], --join=[k], --link=[m, n, o], -p=[q r s]}
It also handles the case where the first arguments are parameters, and you can access them in map[""]
Here is another implementation:
fun getopt(args: Array<String>): Map<String, List<String>>
{
var last = ""
return args.fold(mutableMapOf()) {
acc: MutableMap<String, MutableList<String>>, s: String ->
acc.apply {
if (s.startsWith('-'))
{
this[s] = mutableListOf()
last = s
}
else this[last]?.add(s)
}
}
}
Directly construct the map structure, but a reference for the last parameter should be kept for adding next arguments. This function is not so streaming, but just need 1 pass of data. And it just simply discard the leading arguments without a previous parameter.
Here is my implementation.
fun getopt(args: Array<String>): Map<String, List<String>> = args.fold(mutableListOf()) {
acc: MutableList<MutableList<String>>, s: String ->
acc.apply {
if (s.startsWith('-')) add(mutableListOf(s))
else last().add(s)
}
}.associate { it[0] to it.drop(1) }
Use fold to group parameters with their corresponding arguments (that is, convert [-p0 arg0 arg1 -p1 arg2] into [[-p0, arg0, arg1], [-p1, arg2]]), then associate into a Map. This function is streaming, but needs 2 pass of data. Also, if there is some leading arguments without previous parameter, it will cause an exception.

Generalize method with nullable arguments and return type

I have a method that converts ByteArray? to base64 String? so that if argument was null output will be null as well. This is its implementation:
fun toBase64String(array: ByteArray?): String? = if(array == null) null else
Base64.getEncoder().encodeToString(array)
But when I pass in not nullable ByteArray method returns String? which is expected. Is there a way to make it generic so such use case will be possible:
val base64 = toBase64String(ByteArray(4))
where base64 will be of type String and not String? since argument was not nullable?
I just started to work with Kotlin and probably don't know language feature that can make this possible.
You can make two overloads, one for nullable ByteArray? and one for non-null ByteArray:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
#JvmName("toBase64StringNullable")
fun toBase64String(array: ByteArray?): String? =
if (array == null) null else toBase64String(array)
We need #JvmName("...") to avoid the declaration clash in the bytecode.
Also, this allows to distinguish the functions in Java.
Usage:
val nonNullBytes: ByteArray = TODO()
val nonNullString = toBase64String(nonNullBytes) // the inferred type is String
val nullableBytes: ByteArray? = TODO()
val nullableString = toBase64String(nullableBytes) // the inferred type is String?
When the argument is of the non-null type ByteArray, the compiler will choose the overload that returns a non-null String.
Probably overloading methods is the best solution for your case, but for the sake of completeness here are two other ways to realise that using only one method (the nullable one):
Not-Null-Asserted operator:
val base64: String = toBase64String(ByteArray(4))!!
Evlis operator:
val base64: String = toBase64String(ByteArray(4)) ?: "defaultString"
if argument was null output will be null as well
If that is the only thing the function does when it encounters null argument, it's better to declare it accepting non-null values and use safe call to deal with nulls:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
val bytes: ByteArray? = ...
val base64 = bytes?.let { toBase64String(it) }
// the same can be written with function reference instead of lambda
val base64 = bytes?.let(::toBase64String)
Here let function is called only when bytes is not null, otherwise the result of the expression is null. When called it invokes the lambda function or the function reference specified as its argument, passing ByteArray which is already checked to be non-null to that function.
Also it can be more convenient to declare toBase64String as an extension for ByteArray, so it can be invoked with safe call without the helper function let"
fun ByteArray.toBase64String(): String =
Base64.getEncoder().encodeToString(this)
val bytes: ByteArray? = ...
val base64 = bytes?.toBase64String()

How can I tell Kotlin that an array or collection cannot contain nulls?

If I create an array, then fill it, Kotlin believes that there may be nulls in the array, and forces me to account for this
val strings = arrayOfNulls<String>(10000)
strings.fill("hello")
val upper = strings.map { it!!.toUpperCase() } // requires it!!
val lower = upper.map { it.toLowerCase() } // doesn't require !!
Creating a filled array doesn't have this problem
val strings = Array(10000, {"string"})
val upper = strings.map { it.toUpperCase() } // doesn't require !!
How can I tell the compiler that the result of strings.fill("hello") is an array of NonNull?
A rule of thumb: if in doubts, specify the types explicitly (there is a special refactoring for that):
val strings1: Array<String?> = arrayOfNulls<String>(10000)
val strings2: Array<String> = Array(10000, {"string"})
So you see that strings1 contains nullable items, while strings2 does not. That and only that determines how to work with these arrays:
// You can simply use nullability in you code:
strings2[0] = strings1[0]?.toUpperCase ?: "KOTLIN"
//Or you can ALWAYS cast the type, if you are confident:
val casted = strings1 as Array<String>
//But to be sure I'd transform the items of the array:
val asserted = strings1.map{it!!}
val defaults = strings1.map{it ?: "DEFAULT"}
Why the filled array works fine
The filled array infers the type of the array during the call from the lambda used as the second argument:
val strings = Array(10000, {"string"})
produces Array<String>
val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null })
produces Array<String?>
Therefore changing the declaration to the left of the = that doesn't match the lambda does not do anything to help. If there is a conflict, there is an error.
How to make the arrayOfNulls work
For the arrayOfNulls problem, they type you specify to the call arrayOfNulls<String> is used in the function signature as generic type T and the function arrayOfNulls returns Array<T?> which means nullable. Nothing in your code changes that type. The fill method only sets values into the existing array.
To convert this nullable-element array to non-nullable-element list, use:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.filterNotNull()
val upper = strings.map { it.toUpperCase() } // no !! needed
Which is fine because your map call converts to a list anyway, so why not convert beforehand. Now depending on the size of the array this could be performant or not, the copy might be fast if in CPU cache. If it is large and no performant, you can make this lazy:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asSequence().filterNotNull()
val upper = strings.map { it.toUpperCase() } // no !! needed
Or you can stay with arrays by doing a copy, but really this makes no sense because you undo it with the map:
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings: Array<String> = Array(nullableStrings.size, { idx -> nullableStrings[idx]!! })
Arrays really are not that common in Java or Kotlin code (JetBrains studied the statistics) unless the code is doing really low level optimization. It could be better to use lists.
Given that you might end up with lists anyway, maybe start there too and give up the array.
val nullableStrings = listOf("a","b",null,"c",null,"d")
val strings = nullableStrings.filterNotNull()
But, if you can't stop the quest to use arrays, and really must cast one without a copy...
You can always write a function that does two things: First, check that all values are not null, and if so then return the array that is cast as not null. This is a bit hacky, but is safe only because the difference is nullability.
First, create an extension function on Array<T?>:
fun <T: Any> Array<T?>.asNotNull(): Array<T> {
if (this.any { it == null }) {
throw IllegalStateException("Cannot cast an array that contains null")
}
#Suppress("CAST_NEVER_SUCCEEDS")
return this as Array<T>
}
Then use this function new function to do the conversion (element checked as not null cast):
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asNotNull() // magic!
val upperStrings = strings.map { it.toUpperCase() } // no error
But I feel dirty even talking about this last option.
There is no way to tell this to the compiler. The type of the variable is determined when it is declared. In this case, the variable is declared as an array that can contain nulls.
The fill() method does not declare a new variable, it only modifies the contents of an existing one, so it cannot cause the variable type to change.