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

How can I verify type existence on compile time in 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 是密封的这一事实。

我的想法是有这样一个"compilation guard":

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

What I try to achieve is that anyone who wants to add Signal will have (at compile time to create a description.

这样做的简单方法是使其成为 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]
}

取决于您的要求。

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

您可以引入类型类,而不是进行模式匹配,它会在编译时进行映射:

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)

Scastie

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

这里详细解释原因:

如果您不想将 Description 逻辑混合到 ADT 或手动定义 class 类型的实例,例如 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