如何强加一个不包含超类型的子类型?

时间:2018-09-10 16:48:26

标签: scala types

实际问题:
假设一家眼镜店的顾客预订了一场音乐会。
演唱会的一些票可坐。
客户带来配偶。 限制:
 1. 两者都是客户的票证和相应配偶的票证是就座 OR 两者都是不坐
如何在类型级别上施加此限制?

我最初的想法:

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

为此,我想通过ConcertReservation[A]上的类型参数A将userTicket和spouseTicket设置为同一类型。
这样做可以使编译器捕获上述违反限制的情况:

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

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

3 个答案:

答案 0 :(得分:3)

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

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

接下来,您可以分别获取两个参数的类型,并隐式检查它们是否等同于参数。您还可以添加类型不等式检查,以确保类型不是票证,但这将要求您包括诸如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.

答案 1 :(得分:2)

如果您从this answer复制install.packages("usmap") library(usmap) usmap::plot_usmap(include = c("HI")) (“非相等类型”)的定义,则可以使用它来确定=!=不是{{1} }:

A

这会导致预期的行为:

Option[String]

答案 2 :(得分:0)

据我对问题的理解,是否仅在运行时才知道是否分配了席位,对此您无法进行编译时检查。

但是,如果您确实想对两者都进行限制,则应该使用: seats: Option[(String, String)],或者如果您想检查运行时,则可以在两个座位上进行一些模式匹配:

val valid = (userTicket.seat, spouseTicket.seat) match {
  case (Some(_), Some(_)) | (None | None) => true
  case _ => false
}