I wan't to create a generic class around 2D arrays and enrich it with a bunch of helper functions. But reify keyword is giving me a lot of trouble. Here is a simplified example of what i have and the errors triggered by IJ (please ignore poor construction of object and possible IndexOutOfBoundException) :
class MyMatrix<T>(var matrix: Array<Array<T>>) {
var height = matrix.size
var width = matrix[0].size
fun get(x: Int, y: Int): T = matrix[y][x]
fun set(x: Int, y: Int, data: T) {
matrix[y][x] = data
}
fun dump(transformer: (T?) -> String) {
println("==== ($width x $height)")
for (y in 0..<height) {
print("#${y.toString().padStart(3, '0')} : ")
for (x in 0..<width) {
val t = get(x, y)
print(transformer(t))
}
println()
}
println()
}
fun row(y: Int): Array<T> {
return matrix[y]
}
inline fun <reified U> column(x: Int): Array<U> {
return Array(height) { y -> matrix[y][x] as U }
}
}
val aa = Array(3) { y ->
Array<Int?>(6) { x -> y * 100 + x }
}
val m = MyMatrix(aa)
m.set(1, 1, null)
m.dump { i -> i?.let { "%03d ".format(i) } ?: "--- " }
// ==== (6 x 3)
// #000 : 000 001 002 003 004 005
// #001 : 100 --- 102 103 104 105
// #002 : 200 201 202 203 204 205
// All getters return 204
println(m.get(4, 2))
println(m.row(2)[4])
println(m.column<Int>(4)[2])
Before getting to that result, I followed this path of trial and error for the column
method :
Step A:
fun column(x: Int): Array<U> {
return Array(height) { y -> matrix[y][x] }
}
producing the error on "return Array" : "Cannot use 'T' as reified type parameter. Use a class instead."
Step B:
inline fun <reified T> column(x: Int): Array<T> {
return Array<T>(height) { y -> matrix[y][x] }
}
With, error on "matrix[y][x]" : "Type mismatch. Required: T#1 (type parameter of ...MyMatrix.column) Found: T#2 (type parameter of ...MyMatrix)"
Step C: Naming the 2nd T "U": "Type mismatch. Required: U Found: T"
inline fun <reified U> column(x: Int): Array<U> {
return Array(height) { y -> matrix[y][x] }
}
Step D: adding "as U" as shown in the above snipped.
That seems to work provided i have to change the call site:
val col = grid.column<Boolean>(x)
I am really not please with the workaround given that
I hope i did something wrong given my partial knowledge of generics in Kotlin. What would be the clean way to write a good generic library ? Thanks for your help.
row
and column
can be written as extension methods instead.
inline fun <reified T> MyMatrix<T>.row(y: Int) =
Array(width) { x -> get(x, y) }
inline fun <reified T> MyMatrix<T>.column(x: Int) =
Array(height) { y -> get(x, y) }
This way,
Note that I'm using get
- This makes it possible to make matrix
private.
Also consider making the get
method an operator
:
operator fun get(x: Int, y: Int): T = matrix[y][x]
This allows you to use the [x, y]
syntax on MyMatrix
s.