[英]How can I verify type existence on compile time in Scala
I have the following traits and classes:我具有以下特征和类别:
sealed trait Signal
sealed trait Description[T]
final case class S1(name: String) extends Signal
final case class D1(name: String) extends Description[S1]
What I try to achieve is that anyone who wants to add Signal will have (at compile time) to create a description.我试图实现的是,任何想要添加 Signal 的人都可以(在编译时)创建描述。
I don't want to change the signature of Description
but for sure not of Signal
我不想更改Description
的签名,但肯定不是Signal
I set my compiler to fail on warning, so I can leverage the fact that my ADT is sealed.我将编译器设置为在警告时失败,因此我可以利用我的 ADT 是密封的这一事实。
My idea was to have such a "compilation guard":我的想法是有这样一个“编译后卫”:
def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
but I get the following error:但我收到以下错误:
<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) }
can't compile for the same reason as无法编译的原因与
def returnItself[S <: Signal](s: S): S = s match { case S1(name) => S1(name) }
Reasons are explained here in details:原因在这里详细解释:
Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter? 如果 A 的泛型子类型被声明为返回参数,为什么我不能返回 A 的具体子类型?
Type mismatch on abstract type used in pattern matching 模式匹配中使用的抽象类型的类型不匹配
If you don't want to mix Description
logic to ADT or define instances of a type class like SignalMapper
manually you can use Shapeless如果您不想将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()
Testing:测试:
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
What I try to achieve is that anyone who wants to add Signal will have (at compile time to create a description.我试图实现的是任何想要添加 Signal 的人都将拥有(在编译时创建描述。
The easy way to do so is to make it part of 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)
}
or或者
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)
}
Of course it isn't far from the simpler当然,它离更简单不远了
sealed trait Signal[S <: Signal] {
def description: Description[S]
}
depending on your requirements.根据您的要求。
Your program is failing, because it can't prove, that type S
is S1
.您的程序失败了,因为它无法证明类型S
是S1
。
Instead of doing pattern matching, you could introduce typeclass which would do a mapping on compile time:您可以引入类型类,而不是进行模式匹配,它会在编译时进行映射:
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)
}
}
Then you can rewrite compilationGuard as:然后您可以将compileGuard重写为:
def compilationGuard[S <: Signal](s: S)(implicit mapper: SignalMapper[S]): Description[S] = mapper.map(s)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.