如何在 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]
}
取决于您的要求。
您的程序失败了,因为它无法证明类型 S
是 S1
。
您可以引入类型类,而不是进行模式匹配,它会在编译时进行映射:
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)
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
我有以下特点 类:
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]
}
取决于您的要求。
您的程序失败了,因为它无法证明类型 S
是 S1
。
您可以引入类型类,而不是进行模式匹配,它会在编译时进行映射:
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)
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