我有以下特征和类:
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
您的程序失败了,因为它无法证明该类型S
S1
。
与其进行模式匹配,不如引入 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]
}
根据您的要求。