Reified inline function for arrays - kotlin

Is it possible in Kotlin to write an inline function with reified type which can return different kinds of Arrays? I think about something like this:
inline fun <reified E> getArray(key: String, defValue: Array<E>): Array<E>? {
return when(defValue) {
is Array<Int> -> // ...
is Array<String?> -> // ...
else // ...
}
}
And I would like to call it like this:
fun intArray(size: Int): Array<Int> = Array(size) {i -> 0}
fun stringArray(size: Int): Array<String?> = Array(size) {i -> null}
val strings: Array<Int> = getArray(KEY_INTS, intArray(0))
val strings: Array<String> = getArray(KEY_STRINGS, stringArray(0))
But with that I get the error:
Cannot find check for instance of erased type

Explicitly answering question - You can use it by checking the E class:
inline fun <reified E: Any> getArrayInline(key: String, defValue: Array<E>): Array<E>? {
return when(E::class) {
Int::class -> arrayOf(1, 2, 3)
String::class -> arrayOf("a", "b", "c")
else -> throw IllegalArgumentException("Invalid class: ${E::class.qualifiedName}")
} as Array<E>
}
But I discourage using it since:
It's not type safe - you have to perform unsafe cast on the result and it can be called for any array type even if it's not included in the when cases
it's inline - so this entire block of code is copied into bytecode whenever you use the method (see below)
type checking is done at runtime, so it hurts performance
What happens when you use it? Let's check this example:
fun testArrayInline(){
val test = getArrayInline("key", emptyArray<Int>())
val test2 = getArrayInline("key2", emptyArray<String>())
}
Simple right? But once you look into generated bytecode it's not so good. For readablity this is Kotlin bytecode decompiled back into Java:
public static final void testArrayInline() {
String var1 = "key";
Object[] defValue$iv = new Integer[0];
KClass var3 = Reflection.getOrCreateKotlinClass(Integer.class);
Object var10000;
if (Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
var10000 = new Integer[]{1, 2, 3};
} else {
if (!Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(String.class))) {
throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(Integer.class).getQualifiedName()));
}
var10000 = new String[]{"a", "b", "c"};
}
Integer[] test = (Integer[])((Object[])var10000);
String var7 = "key2";
Object[] defValue$iv = new String[0];
KClass var4 = Reflection.getOrCreateKotlinClass(String.class);
if (Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
var10000 = new Integer[]{1, 2, 3};
} else {
if (!Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(String.class))) {
throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(String.class).getQualifiedName()));
}
var10000 = new String[]{"a", "b", "c"};
}
String[] test2 = (String[])((Object[])var10000);
}
It's pretty huge considering that function was called only twice with 2 cases in the "when" block. And it doesn't even do anything useful - you can already see the result of if cases.
Correct way to do it - declare each type as separate non-inline functions:
fun getArray(key: String, defValue: Array<Int>) : Array<Int>{
return arrayOf(1, 2, 3)
}
fun getArray(key: String, defValue: Array<String>) : Array<String>{
return arrayOf("a", "b", "c")
}
You have to write slightly more code, but it does not have any of 3 issues I mentioned above.
You get very clean bytecode that way as well (small size, high performance), this is decompiled bytecode of same example as before but using non-inline functions:
public static final void testArray() {
String var3 = "key";
Integer[] var4 = new Integer[0];
getArray(var3, var4);
var3 = "key2";
String[] var5 = new String[0];
getArray(var3, var5);
}

Related

How can I make a generic function that works for all subclasses of a collection and at the same time accepts the parameters correctly?

// Generic Function but not work as expecting
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
fun main() {
val originalList: List<String> = readln().split(" ")
val originalSet: Set<String> = originalList.toSet()
val word: String = readln()
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
// Incorrect: Int and String are different types
val dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
}
Is the question about the fact that the following line compiles?
listOf(1, 2, 3).dropElements(word)
If so, then what the compiler is doing is inferring these types:
listOf(1, 2, 3).dropElements<List<Int>, Any>(word)
This is possible because the type parameter in List is covariant, i.e. it is defined as List<out E>. This means that a List<Int> is also a List<Any>.
Doc about generics and variance here.
Your function is working as I would expect.
I think you are expected the integers in dropElements3 to reduce with word, but the problem is that readln() is returning a String, so an integer is not matching the String representation of the same Here is your original code (using kotest library to assert the answers)
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
class ATest {
inline fun <reified C : Collection<T>, T> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> filter { it != word } as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
#Test
fun main() {
val originalList: List<String> = listOf("fish","dog","cat","bird")
val originalSet: Set<String> = originalList.toSet()
var word = "cat"
val dropElements1: List<String> = originalList.dropElements(word).also(::println)
dropElements1 shouldBe listOf("fish","dog","bird")
val dropElements2: Set<String> = originalSet.dropElements(word).also(::println)
dropElements2 shouldBe listOf("fish","dog","bird")
var dropElements3: List<Int> = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3)
word = "2"
dropElements3 = listOf(1, 2, 3).dropElements(word).also(::println)
dropElements3 shouldBe listOf(1, 2, 3) // integer 2 != "2" no dropped elements
var word2 = 2 // now this is an integer
dropElements3 = listOf(1, 2, 3).dropElements(word2).also(::println)
dropElements3 shouldBe listOf(1, 3)
}
}
The List filter and Set - operations are removing an object based on a equality test on members (and hashcode too for the Set). How can Kotlin/Java know you want to treat integers as like Strings?
The only way you can solve this is to decide how to transform integers into strings (or visa versa). Of course there are multiple string representations of integers - decimal, hexadecimal, and so on...
I think T is a covariant type parameter with the upper bound T: comparable is a great deal!
data class Koko(val name: String) : Comparable<Koko> {
override fun compareTo(other: Koko) = name.compareTo(other.name)
}
inline fun <reified C : Iterable<T>, reified T : Comparable<T>> C.dropElements(word: T): C {
return when (this) {
is Set<*> -> (this - word) as C
is List<*> -> (this.filter { it != word }).toList<T>() as C
else -> throw Exception("I can't implement all out of Collection types")
}
}
fun main() {
val list: List<String> = listOf("apple", "banana", "orange")
val set: Set<String> = list.toSet()
val mutableList: MutableList<String> = list.toMutableList()
val listOfKoko: List<Koko> = List<Koko>(5) { Koko("Name$it") }
val mapOfKoko: Map<Int, String> = list.withIndex().associate { it.index to it.value }
val dropElements1: List<String> = list.dropElements("apple").also(::println)
val dropElements2: Set<String> = set.dropElements("apple").also(::println)
val dropElements3: List<Koko> = listOfKoko.dropElements(Koko("Name1")).also(::println)
val dropElements4: MutableList<String> = mutableList.dropElements("apple").also(::println)
// Incorrect: different types ot not is Iterable
val dropElements5: List<String> = list.dropElements(1).also(::println)
val dropElements6: List<Int> = listOf(1, 2, 3).dropElements("apple").also(::println)
val dropElements7: Map<Int, String> = mapOfKoko.dropElements(Koko("Name1")).also(::println)
}

Proper use of Number class in Kotlin

Can anyone help me implement these methods in Kotlin?
I want to find min, max elements of array of numbers and also sort array in ascending order. Here is a code
class DataArray<Number>(vararg numbers: Number) {
private val array = mutableListOf<Number>(*numbers)
fun getMin() {
return array.minByOrNull { it! } //doesn't work
}
fun getMax() = array.max() //doesn't work
fun sort() = array.sort() //doesn't work
private fun <E> MutableList<E>.max(): Any { //was created to use in function above, but resulted in stack overflow
return this.max()
}
private fun <E> MutableList<E>.sort(): Any { //was created to use in function above, but resulted in stack overflow
return this.sort()
}
override fun toString(): String {
var str = ""
for(i in array)
str += "$i "
return str
}
}
fun main() {
val arr = DataArray(2, 5, 2, 6, 9, -3, 56, 16, 72, 8)
println(arr.getMax())
println(arr.getMin())
println(arr.sort())
print(arr)
}
Note that the word Number here declares a generic parameter called Number. It does not refer to kotlin.Number. You might have intended it to declare a generic parameter with a bound of Number instead, in which case you should have written:
class DataArray<T: Number>(vararg numbers: T) {
...
}
But even if you did, it still wouldn't work as Numbers are not comparable.
You would have to further constrain T to Comparable<T>:
class DataArray<T: Number>(vararg numbers: T) where T: Comparable<T> {
Then you can do:
fun getMin() = array.minOrNull()
fun getMax() = array.maxOrNull()
fun sort() = array.sort()
Extension functions on MutableList are unnecessary.
(Note that technically, the T: Number constraint is also unnecessary if you just want to use minOrNull, maxOrNull, and sort. I'm assuming you are planning on using one of the methods in kotlin.Number. Otherwise you can delete that constraint.)
You seem to be trying to implement your own MutableList by delegation. Keep in mind that you can easily do this using by:
class DataArray<T: Number>(
vararg numbers: T
) : MutableList<T> by mutableListOf(*numbers) {
override fun toString(): String {
var str = ""
for(i in this) // rather than "array", use "this"
str += "$i "
return str
}
}

How to print multiple attrributes from a hashMap that is a property inside a toString override

I am learning Kotlin and writing code to check my understanding. I'm trying to use a toString override to print the values of a hashMap that is a property of a class. I can't get it to work. Instead I get output like "kotlin.Unit() -> kotlin.Unit". Also, I don't understand why the values of the hashMap ARE printing out before the toString output. I don't know where that output is coming from. Please help me. Thanks. Below is my code and the output I'm getting.
Code:
package ch07.ExpandoObject
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
class Person(
val name: String = "",
age: Int? = null,
var isMarried: Boolean? = null ,_attributes: kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
)
:PropertyChangeAware()
{
var _attributes : kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes!!.set(attrName, value)
_attributes!!.set("name", this.name)
}
override fun toString() = "Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) " +
"${_attributes?.get("name")} " + "$name " +
this._attributes!!.forEach { (attrName, value) -> println("$attrName = $value") } +
{
for ((attrName, value) in this._attributes!!) {
println("attribute $attrName = ${this._attributes!![attrName]}")
}
}
val _age = ObservableProperty(propName = "age", propValue = age, changeSupport = changeSupport)
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(initialValue = age?:99999,onChange = observer)
}
class ObservableProperty(val propName: String,
var propValue: Int?, val changeSupport: PropertyChangeSupport
) {
fun getValue(): Int? = propValue
fun setValue( newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
open class PropertyChangeAware {
val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
fun main(args: Array<String>) {
val p = Person("Bob", 89, isMarried = false)
val data = mapOf("lastname" to "Jones", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p)
}
Here is the current output:
name = Bob
company = JetBrains
lastname = Jones
Person(name="Bob", age=89, isMarried=false) Bob Bob kotlin.Unit() -> kotlin.Unit
Thanks, again, for any help.
You should not use print() or println() functions inside toString() because they output their arguments to the standard output immediately instead of appending them to the string returned to the caller.
Let's examine the output kotlin.Unit() -> kotlin.Unit you're getting. It consists of two parts:
kotlin.Unit is the string representation of attributes!!.forEach { ... } expression. forEach function returns without value, and in Kotlin it's expressed by returning the Unit object value. Its string representation is appended to the string you're returning.
the second part, () -> kotlin.Unit, is also the string representation of the lambda function expression { for((attrName, value) in ...) }. This function takes no parameters, and returns without value, which means that its type is () -> Unit. Note that in Kotlin the block { ... } declares a local lambda function. If you instead want to run the code inside of that block, use the run function: run { ... }
The goal of toString function is to build the string representation of an object. And for that you can use buildString function:
override fun toString() = buildString {
append("Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) ")
append("${_attributes?.get("name")} ").append("$name ")
this._attributes!!.forEach { (attrName, value) -> append("$attrName = $value") }
for ((attrName, value) in this._attributes!!) {
append("attribute $attrName = ${this._attributes!![attrName]}")
}
}
This function creates a StringBuilder and passes it as a receiver to its functional argument, where you call append or appendln on that receiver. Then buildString converts that string builder to a string and returns it.

GSON-based DSL causing a NPE

I've been trying to create a Kotlin DSL for creating GSON JsonObjects with a JSON-like syntax. My builder looks like this
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
class JsonBuilder(builder: JsonBuilder.() -> Unit) {
init {
builder()
}
val result = JsonObject()
infix fun String.to(property: Number) = result.addProperty(this, property)
infix fun String.to(property: Char) = result.addProperty(this, property)
infix fun String.to(property: Boolean) = result.addProperty(this, property)
infix fun String.to(property: String) = result.addProperty(this, property)
infix fun String.to(property: JsonElement) = result.add(this, property)
infix fun String.to(properties: Collection<JsonElement>) {
val arr = JsonArray()
properties.forEach(arr::add)
result.add(this, arr)
}
operator fun String.invoke(builder: JsonObject.() -> Unit) {
val obj = JsonObject()
obj.builder()
result.add(this, obj)
}
}
fun json(builder: JsonBuilder.() -> Unit) = JsonBuilder(builder).result
And my test looks like this
fun main() {
val json = json {
"name" to "value"
"obj" {
"int" to 1
}
"true" to true
}
println(json)
}
However, upon execution it causes a NullPointerException pointing to the first String extension function used, which I don't find very descriptive as I don't see anything being nullable up to that point. Moreover, I don't see how it really differs from the regular execution which of course doesn't cause a NPE.
val json = JsonObject()
json.addProperty("name", "value")
val obj = JsonObject()
obj.addProperty("int", 1)
json.add("obj", obj)
json.addProperty("true", true)
My question is what's exactly causing the exception (and how to prevent it).
The issue is that you've specified the initialiser block earlier than the result object, causing it to be null when you come to use it - this can be visualised by the following (decompiled output of your code).
public JsonBuilder(#NotNull Function1 builder) {
Intrinsics.checkParameterIsNotNull(builder, "builder");
super();
builder.invoke(this);
this.result = new JsonObject();
}
Therefore, the solution is to move the declaration and initialisation of result earlier than the initialiser block.
class JsonBuilder(builder: JsonBuilder.() -> Unit) {
val result = JsonObject()
init {
builder()
}
// ...
}
And the result is now...
{"name":"value","int":1,"obj":{},"true":true}
EDIT: You'll also want to allow chaining with your DSL, and fix a bug you currently have.
operator fun String.invoke(builder: JsonBuilder.() -> Unit) {
val obj = JsonBuilder(builder).result
result.add(this, obj)
}
Which produces the correct result of
{"name":"value","obj":{"int":1},"true":true}

Kotlin: how to pass an object function as parameter to another?

I am trying to learn functional Kotlin and have written this test code:
import java.util.*
data class BorrowerX(val name: String, val maxBooks: Int) {
companion object {
fun getName(br: BorrowerX): String = br.name
fun findBorrowerX(n: String, brs: ArrayList<BorrowerX>): BorrowerX? {
val coll: List<BorrowerX> = brs.filter { BorrowerX.getName(it) == n }
if (coll.isEmpty()) {
return null
} else return coll.first()
}
fun findBorrowerX2(n: String, brs: ArrayList<BorrowerX>, f: (BorrowerX) -> String): BorrowerX? {
val coll: List<BorrowerX> = brs.filter { f(it) == n }
if (coll.isEmpty()) {
return null
} else return coll.first()
}
}
}
In the REPL I can successfully call "findBorrowerX":
import BorrowerX
val br1 = BorrowerX(name = "Borrower1", maxBooks = 1)
val br2 = BorrowerX(name = "Borrower2", maxBooks = 2)
val br3 = BorrowerX(name = "Borrower3", maxBooks = 3)
val brs1 = arrayListOf(br1, br2, br3)
BorrowerX.findBorrowerX("Borrower1", brs1)
BorrowerX(name=Borrower1, maxBooks=1)
BorrowerX.findBorrowerX("Borrower-Bad", brs1)
null
But how do I make the call to "findBorrowerX2":
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX.getName(???))
And pass the iterated BorrowerX to getName??
This looks related, but I'm not sure:
Kotlin: how to pass a function as parameter to another?
Thank you in advance for your help with this!
EDIT:
Here is the equivalent Scala code for what I want to do:
def findBorrowerX2(n: String, brs: List[BorrowerX], f: BorrowerX => String): BorrowerX = {
val coll: List[BorrowerX] = brs.filter(f(_) == n)
if (coll.isEmpty) {
null
} else {
coll.head
}
}
scala> BorrowerX.findBorrowerX2("Borrower3", brs1, BorrowerX.getName(_))
res1: BorrowerX = BorrowerX(Borrower3,3)
scala> BorrowerX.findBorrowerX2("Borrower33", brs1, BorrowerX.getName(_))
res2: BorrowerX = null
Perhaps this is not possible in Kotlin?
You can use :: operator to get a function reference:
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX.Companion::getName)
Here BorrowerX.Companion::getName is a reference to the function getName declared in the companion object (named Companion) of the class BorrowerX. It has the type KFunction1<BorrowerX, String> which is a subtype of the required functional parameter type (BorrowerX) -> String.
It's worth noting that you can use :: operator to get a property reference too:
BorrowerX.findBorrowerX2("Borrower1", brs1, BorrowerX::name)
BorrowerX::name has the type KProperty1<BorrowerX, String> which also is a subtype of (BorrowerX) -> String. When invoked with the specified BorrowerX instance it returns the value of its name property.
As stated in the documentation on lambdas:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1, { it.name })
or when the lambda is the last parameter of the method:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1) { it.name }
Stating types and parameter names explicitly often improves readability:
BorrowerX.findBorrowerX2("Borrower-Bad", brs1) { borrower:BorrowerX -> borrower.name }