更高种类的类型作为类型参数

时间:2018-10-06 13:24:03

标签: kotlin higher-kinded-types

我有一个通用的密封类,用于表示单个值或值对(在特定事件之前和之后分割):

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

此外,如果我的班级有多个基本类型,例如IntBoolean怎么办?如何编写通用签名?

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
)

这有效,但更明显的是,您可以混合和匹配我想禁止的“单人”和“拆分”:

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

我希望这些数据类的每个对象要么全部由Single值组成,要么由所有Split值组成,而不是混合搭配。

我可以使用Kotlin的类型来表达它吗?

1 个答案:

答案 0 :(得分:2)

您的Totals类需要一个泛型类型参数,但是您无需在示例的构造函数中指定一个。工作方式是类型推断:Kotlin编译器从其他参数中找出泛型。在第一个Single示例中混合使用SplitWRONG的原因是,编译器会看到两个参数并从中推断出公共超类型。因此,您实际上是在构建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子句的generic constraint来实现:

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并将SIntSBool限制为普通类型SSplittable<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仍然可能。