Kotlin NDArray с лямбда-конструктором с универсальным возвращаемым типом

Я пытаюсь создать очень простой общий класс NDArray в Kotlin, который принимает лямбда-выражение в качестве функции инициализации.

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

Типичное использование:

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

fun zero(i: Int) =  0.0

Моя проблема в том, что компилятор Kotlin жалуется на инициализацию значений в конструкторе

values = Array(i * j, f)

говоря: «Невозможно использовать« T »в качестве параметра типа. Вместо этого используйте класс». Почему ?

ИЗМЕНИТЬ:

Если вместо этого я заменю реализацию массива Kotlin своим собственным MyArray, он скомпилируется:

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

Не знаете, почему Kotlin обрабатывает MyArray иначе, чем обычный массив, когда оба имеют один и тот же конструктор?


person Tomas Karlsson    schedule 23.02.2016    source источник
comment
почему бы просто не class NDArray<T>(i:Int, j:Int, f: (Int) -> T) { val values: Array<Int> = Array<Int>(i*j,f) } ?   -  person voddan    schedule 23.02.2016
comment
Определенно более лаконично, но проблема все еще остается, поскольку Array должен быть типа T, а не Int.   -  person Tomas Karlsson    schedule 23.02.2016
comment
неидиоматический код Kotlin трудно читать. Пожалуйста, смотрите редактирование   -  person voddan    schedule 23.02.2016
comment
Красиво и лаконично. Правка принята, спасибо!   -  person Tomas Karlsson    schedule 23.02.2016
comment
чтобы MyArray действовал как Array, он должен иметь общий параметр reifiled T. Пока это разрешено только для inline функций.   -  person voddan    schedule 24.02.2016
comment
Все еще не объясняет, почему компилятор видит разницу между MyArray и Array в приведенном выше примере. Насколько я могу судить из справочного документа, Array‹T› не объявлен с ключевым словом reifeid. Может быть, магия компилятора, поскольку это особый тип Kotlin?   -  person Tomas Karlsson    schedule 24.02.2016
comment
да, магия подразумевается. Пожалуйста, смотрите мое редактирование ответа йола   -  person voddan    schedule 24.02.2016


Ответы (2)


Создание массива Java требует указания типа элемента. В случае вашего класса тип элемента предоставляется только как параметр типа класса, а дженерики в Java стираются во время выполнения. Из-за этого тип элемента массива неизвестен, и создать его невозможно.

Если вы хотите создать собственную оболочку для стандартного Array<T>, она должна выглядеть следующим образом:

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

Ключевое слово reified означает, что ваш T не стирается и может использоваться в тех местах, где нужен настоящий класс, например, при вызове конструктора Array().

Обратите внимание, что этот синтаксис не поддерживается для конструкторов классов, но он по-прежнему полезен для фабричных функций (должен быть inlined)

fun <reified T> arrayFactory(i:Int, j:Int, init: (Int) -> T) = Array<T>(i * j, init)
person yole    schedule 23.02.2016
comment
Подумал, что это как-то связано со стиранием шрифта. Если вы посмотрите на это с точки зрения JVM, я увижу проблему создания массива универсального типа T[] во время выполнения. С точки зрения Kotlin я все еще немного озадачен, см. редактирование. - person Tomas Karlsson; 23.02.2016
comment
По сути, обертка Array‹T›() невозможна без использования фабричного шаблона? Кстати, смотрите редактирование - person Tomas Karlsson; 24.02.2016

Основываясь на информации от yole и voddan, это лучшее решение, которое я нашел для проблемы:

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

Это позволяет использовать reified в качестве конструктора с помощью объекта-компаньона. Соглашение о вызовах конструкций может быть выполнено с помощью вызова оператора. Это работает сейчас:

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

fun zero(i:Int) =  0.0 

Единственная проблема (за исключением запутанного синтаксиса invoke()) заключается в том, что конструктор NDArray должен быть общедоступным. Возможно, есть лучший способ?

ПРИМЕЧАНИЕ! Следующие проблемы KT-11182 влияют на этот шаблон проектирования.

person Tomas Karlsson    schedule 24.02.2016