Kotlin NDArray with a lambda constructor with gene

2019-06-22 01:19发布

问题:

I'm trying to create a very simple generic NDArray class in Kotlin that takes a lambda expression as an init function.

class NDArray<T>(i: Int, j: Int, f: (Int) -> T) {
    val values: Array<T> =  Array(i * j, f)
}

A typical usage would be:

fun main(args: Array<String>){
    val m = NDArray(4, 4, ::zero)
}

fun zero(i: Int) =  0.0

My problem is that the Kotlin compiler complains on the initialisation of values in the constructor

values = Array(i * j, f)

by saying "Cannot use 'T' as reified type parameter. Use class instead". Why ?

EDIT:

If I instead replace the Kotlin Array implementation with my own MyArray it compiles:

class NDArray<T>(i: Int, j: Int, f: (Int) -> T) {
    val values: MyArray<T> =  MyArray(i * j, f)
}

class MyArray<T>(i:Int, init: (Int) -> T) {
    ...
}

Not sure why Kotlin treats MyArray differently from a regular Array when both have the same constructor ?

回答1:

Creating a Java array requires specifying an element type. In the case of your class, the element type is provided only as a type parameter of the class, and generics in Java are erased at runtime. Because of that, the element type of the array is not known, and it's not possible to create it.

If you would want to create a custom wrapper around standard Array<T>, it would have to look like the following:

class NDArray<reified T>(i:Int, j:Int, init: (Int) -> T) {
    val arr = Array<T>(i * j, init)
}

The reified keyword means that your T is not erased, and may be used in places where a real class is needed, like calling the Array() constructor.

Note that this syntax is not supported for class constructors, but it is still useful for factory functions (must be inlined)

fun <reified T> arrayFactory(i:Int, j:Int, init: (Int) -> T) = Array<T>(i * j, init)


回答2:

Based on input from yole and voddan this is the best solution I found to the problem so far:

class NDArray<T>(val values: Array<T> ){

    companion object Factory{
        inline operator fun <reified T>invoke(i: Int, j: Int, noinline init: (Int) -> T) = NDArray(Array(i * j,init))
    }
}

This allows the usage of reified as a constructor by using a companion object. Construction calling convention can be done using operator invoke. This works now:

fun main(args: Array<String>){
    val m = NDArray(4,4, ::zero)
}

fun zero(i:Int) =  0.0 

The only problem ( except for the convoluted syntax invoke() ) is that the NDArray constructor need to be public. Is there a better way perhaps ?

NOTE! Following issues KT-11182 affects this design pattern