如何在 Scala 中验证编译时的类型是否存在



我有以下特征和类:

sealed trait Signal
sealed trait Description[T]
final case class S1(name: String) extends Signal
final case class D1(name: String) extends Description[S1]

我试图实现的是,任何想要添加 Signal 的人都必须(在编译时(创建一个描述。

我不想更改Description的签名,但肯定不想更改Signal

我将编译器设置为在出现警告时失败,因此我可以利用我的 ADT 已密封的事实。

我的想法是有这样一个"编译卫士":

def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }

但我收到以下错误:

<console>:17: error: type mismatch;
found   : D1
required: Description[S]
def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
                        ^
def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }

由于与 相同的原因无法编译

def returnItself[S <: Signal](s: S): S = s match { case S1(name) => S1(name) }

原因在这里详细解释:

如果将 A的泛型子类型声明为返回参数,为什么我不能返回 A 的具体子类型?

模式匹配中使用的抽象类型的类型不匹配

如果您不想将Description逻辑混合到 ADT 或手动定义类型类的实例(如SignalMapper(,则可以使用 Shapeless

import shapeless.ops.coproduct.Mapper
import shapeless.{:+:, CNil, Coproduct, Generic, Poly1}
def compilationGuard[C <: Coproduct]()(implicit
gen: Generic.Aux[Signal, C],
mapper: Mapper[uniqueDescriptionPoly.type, C]
) = null
object uniqueDescriptionPoly extends Poly1 {
implicit def cse[S <: Signal, C1 <: Coproduct](implicit
gen1: Generic.Aux[Description[S], C1],
ev: C1 <:< (_ :+: CNil)
): Case.Aux[S, Null] = null
}
compilationGuard()

测试:

final case class S1(name: String) extends Signal
final case class S2(name: String) extends Signal
final case class D1(name: String) extends Description[S1] 
// doesn't compile
final case class S1(name: String) extends Signal
final case class S2(name: String) extends Signal
final case class D1(name: String) extends Description[S1]
final case class D2(name: String) extends Description[S1]
// doesn't compile
final case class S1(name: String) extends Signal
final case class S2(name: String) extends Signal
final case class D1(name: String) extends Description[S1]
final case class D2(name: String) extends Description[S2]
// compiles

您的程序失败了,因为它无法证明该类型SS1

与其进行模式匹配,不如引入 typeclass,它将在编译时进行映射:

trait SignalMapper[S] { //typeclass handling of mapping S to D
type D <: Description[S]
def map(signal: S): D
}
//instance of typeclass SignalMapper for S1
//if you'd put it in a companion object of S1, it would be always in scope
object S1 { 
implicit val mapperS1: SignalMapper[S1] = new SignalMapper[S1] {
type D = D1
def map(signal: S1) = D1(signal.name)
}
}

然后你可以将 compilationGuard 重写为:

def compilationGuard[S <: Signal](s: S)(implicit mapper: SignalMapper[S]): Description[S] = mapper.map(s)

斯卡斯蒂

我试图实现的是,任何想要添加 Signal 的人都会有(在编译时创建一个描述。

最简单的方法是使其成为Signal的一部分:

sealed trait Signal[S <: Signal[S, D], D <: Description[S]] {
// optionally
def description: D
}
final case class S1(name: String) extends Signal[S1, D1] {
def description = D1(name)
}

sealed trait Signal[S <: Signal[S]] {
type Descr <: Description[S]
// optionally
def description: Descr
}
final case class S1(name: String) extends Signal[S1] {
type Descr = D1
def description = D1(name)
}

当然,它离更简单并不遥远

sealed trait Signal[S <: Signal] {
def description: Description[S]
}

根据您的要求。

最新更新