Типы с более высоким родом как параметры типа - PullRequest
0 голосов
/ 06 октября 2018

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

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, так что свойства класса должны быть либо одинарными, либо полностью разделенными. Я думал, что это сделает это:

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, а не из сочетания и совпадения.

Могу ли я выразить это с помощью Kotlinтипы?

1 Ответ

0 голосов
/ 06 октября 2018

Вашему классу 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 -clause:

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 все еще возможно, хотя.

...