Типы более высокого порядка как параметры типа

У меня есть общий запечатанный класс, используемый для представления либо отдельных значений, либо пар значений (разделенных до и после определенного события):

sealed class Splittable<T>

data class Single<T>(val single: T) : Splittable<T>()

data class Split<T>(val before: T, 
                    val after : T) : Splittable<T>()

Я хотел бы определить классы данных, которые являются общими (параметризуемыми) поверх Splittable, чтобы свойства класса должны были либо быть все Single, либо все Split. Я думал, что это будет сделано:

data class Totals<SInt : Splittable<Int>>(
    val people : SInt,
    val things : SInt
)

val t1 = Totals(
    people = Single(3),
    things = Single(4)
)

val t2 = Totals(
    people = Split(3, 30),
    things = Split(4, 40)
)

Но я ошибся, потому что он допускает недопустимые комбинации:

val WRONG = Totals(
    people = Single(3),
    things = Split(4, 40)
)

Более того, что, если мой класс имеет более одного базового типа, например, и Int, и Boolean? Как написать общую подпись?

data class TotalsMore<S : Splittable>(
    val people : S<Int>,
    val things : S<Int>,
    val happy  : S<Boolean>
)

val m1 = TotalsMore(
    people = Single(3),
    things = Single(4),
    happy  = Single(true)
)

val m2 = TotalsMore(
    people = Split(3, 30),
    things = Split(4, 40),
    happy  = Split(true, false)
)

Объявление класса данных дает ошибки:

error: one type argument expected for class Splittable<T>
    data class TotalsMore<S : Splittable>(
                              ^
error: type arguments are not allowed for type parameters
        val people : S<Int>,
                      ^
error: type arguments are not allowed for type parameters
        val things : S<Int>,
                      ^
error: type arguments are not allowed for type parameters
        val happy  : S<Boolean>
                      ^

Итак, похоже, я не могу передать тип более высокого типа в качестве параметра типа. облом.

Я могу разделить два дженерика:

data class TotalsMore<SInt : Splittable<Int>,
                      SBoolean: Splittable<Boolean>>(
    val people : SInt,
    val things : SInt,
    val happy  : SBoolean
)

Это работает, но делает еще более очевидным, что вы можете смешивать и сочетать Single и Split, что я хочу запретить:

val WRONG = TotalsMore(
    people = Single(3),
    things = Single(4),
    happy  = Split(true, false)
)

Я хотел бы, чтобы каждый объект этих классов данных состоял либо из всех значений Single, либо из всех значений Split, а не из смешивания и сопоставления.

Могу ли я выразить это, используя типы Котлина?


person Tobia    schedule 06.10.2018    source источник


Ответы (1)


Для вашего класса Totals требуется параметр универсального типа, но вы не указываете его в конструкторе своего примера. Это работает с выводом типа: компилятор Kotlin вычисляет универсальный тип из других параметров. Причина, по которой вы можете смешивать Single и Split в своем первом примере WRONG, заключается в том, что компилятор видит два аргумента и выводит из них общий супертип. Итак, вы на самом деле строите Totals<Splittable<Int>>.

Если бы вы явно указали подтип, вы не смогли бы смешивать:

val WRONG = Totals<Single<Int>>(
    people = Single(3),
    things = Split(4, 40) /**  Type inference failed. Expected type mismatch: inferred type is Split<Int> but Single<Int> was expected */
)

Итак, что вы хотите сделать, так это принять подклассы Splittable, но не сам Splittable в качестве общего параметра.

Этого можно добиться с помощью дополнительного интерфейса для ваших подклассов и дополнительного общего ограничения с пунктом where:

sealed class Splittable<T>

interface ConcreteSplittable

data class Single<T>(val single: T) : Splittable<T>(), ConcreteSplittable

data class Split<T>(val before: T, 
                    val after : T) : Splittable<T>(), ConcreteSplittable

data class Totals<SInt : Splittable<Int>>(
    val people : SInt,
    val things : SInt
) where SInt : ConcreteSplittable

val t1 = Totals<Single<Int>>(
    people = Single(3),
    things = Single(4)
)

val t2 = Totals(
    people = Split(3, 30),
    things = Split(4, 40)
)

val WRONG = Totals( /** Type parameter bound for SInt in constructor Totals<SInt : Splittable<Int>>(people: SInt, things: SInt) where SInt : ConcreteSplittable is not satisfied: inferred type Any is not a subtype of Splittable<Int> */
    people = Single(3),
    things = Split(4, 40)
)

Что касается второй части, я не думаю, что это вполне возможно. Как вы заметили, аргументы типа не допускаются для параметров типа.

К сожалению, вы также не можете ввести параметр третьего типа S и ограничить SInt и SBool как общим типом S, так и Splittable<Int> или Splittable<Bool> соответственно.

data class TotalsMore<S, SInt, SBool>
(
    val people : SInt,
    val things : SInt,
    val happy  : SBool
) where S : ConcreteSplittable, 
  SInt : S, 
  SInt : Splittable<Int>, /** Type parameter cannot have any other bounds if it's bounded by another type parameter */
  SBool : S,
  SBool : Splittable<Boolean> /** Type parameter cannot have any other bounds if it's bounded by another type parameter */

Что вы можете сделать, так это создать «безопасные» псевдонимы типов, например:

data class TotalsMore<SInt : Splittable<Int>, SBool : Splittable<Boolean>> (
    val people : SInt,
    val things : SInt,
    val happy  : SBool )

typealias SingleTotalsMore = TotalsMore<Single<Int>, Single<Boolean>>

typealias SplitTotalsMore = TotalsMore<Split<Int>, Split<Boolean>>

val s = SingleTotalsMore(
    people = Single(3),
    things = Single(4),
    happy  = Single(true) )

Однако создание смешанного TotalsMore все еще возможно.

person Dario Seidl    schedule 06.10.2018
comment
Ага, понятно. Требуя и суперкласса, и интерфейса, я запрещаю суперклассу одному выполнять контракт. А как насчет второй части, где я комбинирую Int и Boolean? (Вторая часть была на самом деле сутью моего вопроса, но я запутался с первым примером.) - person Tobia; 06.10.2018
comment
Хорошо, я хотел посмотреть, отвечает ли это на ваш первый вопрос, прежде чем пытаться ответить на второй. Я не думаю, что то, что вы пытаетесь сделать во второй части, вполне возможно. Как вы заметили, вы не можете использовать аргумент типа для параметра типа. Вы также не можете добавить третий общий параметр, чтобы ограничить остальные одним и тем же типом без потери их аргументов. Лучшее, что я могу придумать, это создать псевдонимы типов. Я обновлю свой ответ. - person Dario Seidl; 06.10.2018