首页 > 解决方案 > 如何强制子类型不包含超类型?

问题描述

实际问题:
让我们想象一下,一个景观屋的客户预订了一场音乐会。
一些音乐会门票有座位。
客户带来了配偶。限制:
1.客户的票和对应配偶的票要么都要么 没有坐
如何在类型级别施加此限制?

我最初的想法是:

case class Ticket[S <: Option[String]](id: String, seat: S)

case class ConcertReservation[A <: Option[String]](userTicket: Ticket[A],
                                                     spouseTicket: Ticket[A])

val concertReservation =
  ConcertReservation(
      userTicket = Ticket(id = "id1", seat = Some("<seatId>")),
      spouseTicket = Ticket(id = "id2", seat = None)
    )

有了这个,我想通过类型参数 A onConcertReservation[A]强加 userTicket 和 SpearTicket 必须是相同的类型。
这样做允许编译器捕获上述违反限制的行为:

Error:(12, 26) type mismatch;
 found   : .....Temp.Ticket[Some[String]]
 required: .....Ticket[Option[String]]
Note: Some[String] <: Option[String], but class Ticket is invariant in type S.
You may wish to define S as +S instead. (SLS 4.5)
      userTicket = Ticket(id = "id1", seat = Some("assad")),

但有可能克服这一点。例如下面的代码(编译):

  val concertReservation2: ConcertReservation[Option[String]] =
    ConcertReservation(
      userTicket = Ticket(id = "id1", seat = Some("assad")),
      spouseTicket = Ticket(id = "id2", seat = None)
    )

有没有一种惯用的方法来实现我想要的?也许是某种“模式”?
谢谢,

标签: scalatypes

解决方案


您可以设置Ticket为 atrait然后进行一些隐式类型检查。

sealed trait Ticket{val id: String}
case class SeatedTicket(override val id: String, seat: String) extends Ticket
case class StandingTicket(override val id: String) extends Ticket

接下来,您可以分别获取两个参数的类型,并包含一个隐式检查它们是否相等作为参数。您还可以添加类型不等式检查以确保该类型不是 Ticket,但这将需要您包含诸如 shapeless 之类的库,或者对类型系统进行更多处理。

case class Reservation[T1 <: Ticket, T2 <: Ticket](user: T1, spouse: T2)(implicit ev: T1 =:= T2, ev2: T1 =:!= Ticket)

当 T1 和 T2 匹配时,它可以正常工作,但是当它们不同时,类型系统可以拾取错误。

val sit1 = SeatedTicket("1","1A")
val sit2 = SeatedTicket("2","1B")
val stand1 = StandingTicket("3")
val stand2 = StandingTicket("4")
Reservation(sit1, sit2) //Runs fine
Reservation(stand1, stand2) //Runs fine
Reservation(sit1,stand1) //error: Cannot prove that SeatedTicket =:= StandingTicket.

推荐阅读