【问题标题】:How to impose a subtype not to include the super type?如何强制子类型不包含超类型?
【发布时间】:2019-02-15 03:36:46
【问题描述】:

实际问题:
让我们想象一下,一家奇观馆的客户预订了一场音乐会。
音乐会的一些门票有座位。
客户带来了配偶。 限制:
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)
    )

我想通过 ConcertReservation[A] 上的类型参数 A 来强加 userTicket 和配偶Ticket 必须是同一类型。
这样做可以让编译器捕捉到上述违反限制的行为:

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

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

【问题讨论】:

  • 也许你应该使用assert?写在ConcertReservation的正文中验证逻辑assert((userTicket.nonEmpty &amp;&amp; spouseTicket.nonEmpty) || (userTicket.isEmpty &amp;&amp; spouseTicket.isEmpty))
  • 这在实践中是可行的。感谢您回答的麻烦。但是,我正在寻找类型级解决方案。可以在编译时验证。
  • 另外,您可以使用 Some+Some 和 None+None 保留创建 ConcertReservation 的两个子类型,并使 ConcertReservation 抽象和密封(如 Option 及其子类 Some 和 None)。
  • 如果你想要一个类型限制改变 ConcertReservation 类型参数为 [A <: some threre option onyl>

标签: scala types


【解决方案1】:

您可以将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

接下来,您可以分别获取两个参数的类型,并包含一个隐式检查它们是否相等作为参数。您还可以添加类型不等式检查以确保类型不是 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.

【讨论】:

  • 但是Reservation[Ticket, Ticket](sit1,stand1) 仍然可以编译,不是吗?
  • @TzachZohar 哎呀,忘了测试,你是对的。我认为它可以通过T1 =:!= Ticket 无形检查来修复,但目前没有时间验证。当我有机会时,我会验证然后修复/删除它。
【解决方案2】:

如果您从this answer 复制=!=(“非相等类型”)的定义,则可以使用它来确保A 不是Option[String]

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

这会导致预期的行为:

val seated1 = Ticket(id = "id1", seat = Some("1"))
val seated2 = Ticket(id = "id2", seat = Some("2"))
val unseated1 = Ticket(id = "id3", seat = None)
val unseated2 = Ticket(id = "id4", seat = None)

ConcertReservation(seated1, seated2)     // compiles
ConcertReservation(unseated1, unseated2) // compiles
ConcertReservation(seated1, unseated1)   // does not compile
ConcertReservation[Option[String]](seated1, unseated1) // does not compile either!

【讨论】:

    【解决方案3】:

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

    但如果你真的想同时限制或不限制,你应该使用: seats: Option[(String, String)] 或者如果您想检查运行时,那么您可以在两个座位上进行一些模式匹配:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多