How to skip specification of the generic type parameter in Kotlin? - 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

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 can I construct a generic object with a reified type parameter in Kotlin?

I have a class called Column<E> that delegates to a MutableList<E>.
To sort the Comparable elements ("e") of columns without providing a comparator, I pass a reified type argument ("type") to determine whether e implements comparable using reflection and then use e's compareTo method to construct a comparator. This all works fine.
I also have a function object called AggregateFunction that is used in reduce operations. AggregateFunction holds an actual function (to do the reduction operation), and a name (for programmatically creating a name for the result). There are several subtypes of AggregateFunction. NumericAggregateFunction, for example, takes an input column of type Column and always returns a Double.
The typical use case is to partition the input data into subgroups and return a Column containing the computed values for each subgroup. The catch is that I want to programmatically construct a column to hold the results. In the case of NumericAggregateFunction, I want to create a Column<Double>. For BooleanAggregateFunction, a Column<Boolean>, etc.
If I want Aggregate function to return a MutableList<Double> I can create it without a problem using:
fun resultList() : MutableList<OUT> {
return ArrayList<OUT>()
}
However, the same approach fails to compile for Column, apparently because of the reified type. If I attempt to use the inline function, e.g.
fun resultColumnA() : Column<OUT> {
return Column<OUT>("column name")
}
I get:
Cannot use 'OUT' as reified type parameter. Use a class instead.
I also attempted to call the primary constructor directly, passing in the type parameter as shown below, it also fails to compile:
fun resultColumn() : Column<OUT> {
return Column<OUT>(
inputColumn!!.type,
"column name")
}
I now get the error:
Type mismatch. Required: OUT Found: Any!
Finally, I tried reifying the type parameter in the context of the Aggregate function, adding these two methods:
inline fun <reified OUT> col(nm:String) =
Column(
OUT::class.java,
nm
)
fun resultColumnB() : Column<OUT> {
return this.col("name")
}
But the line return this.col("name") results in a compile time error:
Cannot use 'OUT' as reified type parameter. Use a class instead.
Is there a way to create a Column similar to how the MutableList was created?
If not, is there a way to determine whether the elements of a MutableList are comparable without using a reified type? If I didn't have to do that I could get rid of the type entirely.
Partial Implementation of class Column is included below
package com.fathom.core.tables
inline fun <reified E> Column(nm:String) =
Column(
E::class.java,
nm
)
open class Column<E>(val type: Class<E>, var name: String, val comparator : Comparator<E>? = null, val elements: MutableList<E?> = ArrayList()) : MutableList<E?> by elements{
var formatter: (E?) -> String = { e ->
if (e == null) "" else e.toString()
}
// when present, allows sorting on this vector without providing a comparator to the sort method
var defaultComparator: Comparator<E>? = null
/**
* Returns true if elements contained in this column implement comparable.
* That makes the column sortable
*/
fun isComparable(): Boolean {
return type.interfaces.contains(Comparable::class.java)
}
/**
* Returns an int comparator where the ints are the indexes of the elements of the column rather than the elements.
* It uses the indexes to get the values, which are then compared using
* (a) a Comparator<E> column property named 'comparator', or
* (b) the natural comparator for any column that implements Comparable
*
* #throws UnsupportedOperationException if the column has no comparator and doesn't implement Comparable
*/
#Suppress("UNCHECKED_CAST")
fun rowComparator() : Comparator<Int> {
if (comparator != null) {
return Comparator { r1, r2 ->
val v : E = get(r1) as E
val f1 : E = this[r1] as E
val f2 : E = this[r2] as E
comparator.compare(f1, f2)
}
}
if (!isComparable()) {
throw UnsupportedOperationException(
"Columns that are used in table sorts must either " +
"provide a comparator or contain elements that " +
"implement comparable"
)
}
return Comparator { r1, r2 ->
val v : E = get(r1) as E
val f1 : Comparable<E> = this[r1] as Comparable<E>
val f2 : E = get(r2) as E
f1.compareTo(f2)
}
}
}

Kotlin generic Collection cast

I a very new to Kotlin and working on a simple method that sorts and joins a list to string
private fun generateKey(params: Array<Any>): String {
val genericCollection = if (params.isNotEmpty() && params[0] is Collection<*>) params[0] as Collection<*>
else throw Exception("no params provided for keyGenerator")
return genericCollection.sortedBy { it }.joinToString(separator = "_")
}
but I got this compilation error:
Type parameter bound for R in
inline fun > Iterable.sortedBy ( crossinline
selector: (T) → R? ) : List
is not satisfied: inferred type Any is not a subtype of
Comparable
Any idea how to fix this?
The problem is that you try sort this collection via it. But it can be an instance of any type.
Any however isn't something what can be compared (Any doesn't implement Comparable interface).
So, when you use sortedBy method you have to provide something what can be compared. For example:
return genericCollection.sortedBy { it.hashCode() }.joinToString(separator = "_")
hashCode() returns Int and Int can be easily compared.
In fact until you use <*> as generic type you won't be able to find something better to compare collection.
Guess, you must understand, what exactly you expect from param[0]. In this case, it must be some keyGenerator params. Pretty sure, these params could be String or Numeric type. All you need is to map them by casting to appropriate class. Foe example, String class:
private fun generateKey(params: Array<Any>): String {
val genericCollection = if (params.isNotEmpty() && params[0] is Collection<*>) params[0] as Collection<*>
else throw Exception("no params provided for keyGenerator")
return genericCollection.map { it as String }.sortedBy { it }.joinToString(separator = "_")
}

Kotlin generic constraints - require param to be of same type as other

Let's say I have a set of two pojo's like so:
data class Test (
var id : Long? = null
)
data class TestOther (
var id : Long = 0,
var isCool : Boolean = false
}
and then I have an infix function like so:
infix fun <T : Any?> KProperty<T>.equal(rhs : KProperty<T>) = BinaryExpression<Boolean>(this, rhs, EQUALS)
then this works fine as I'd expect:
Test::id equal TestOther::id
but so does this, since T is all types that extend Any?:
Test::id equal TestOther::isCool
Is there anyway to specify generic constraints such that nullable and non nullable types can be compared, but objects of different types cannot without having to specify an overload for every possible concrete type?
It is not possible to do right now. You may follow the issue for more details
https://youtrack.jetbrains.com/issue/KT-13198
I see a workaround here (similar to the one from the issue). The idea is to wrap the KProperty<R> into a wrapper class without variance. As you see, the KProperty type has out R variance, which works against us in the example. You may follow the link for the details on the declaration-side variance in Kotlin
https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance
The workaround works as strict as expected
class KWrapper<R>(val p : KProperty<R>)
infix fun <T : KWrapper<R>, R> T.equal(rhs : T) = false
val <T> KProperty<T>.wrap get() = KWrapper(this)
val a = Test::id.wrap equal TestOther::id.wrap //fails: Long vs Long?
val b = Test::id.wrap equal Test::id.wrap //works
val c = Test::id.wrap equal TestOther::isCool.wrap // fails Long vs Boolean
The downside is that you need to use .wrap extension property (or extension function) for the left and right parameters separately

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()