How to impose a subtype not to include the super type?

How to impose a subtype not to include the super type?



Practical problem:

Let's imagine the client of a spectacle-house makes a reservation for a concert.

Some tickets for the concert have a seat.

The client brings a spouse.
Restriction:

1. Either both the ticket of the client and the corresponding spouse's ticket are seated OR both are not seated.

How do I impose this restriction at the typelevel?



What I initially thought:


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



With this I wanted to impose, via the type parameter A on ConcertReservation[A], that userTicket and spouseTicket must be of the same type.

Doing this allows the compiler to catch the above violation of the restriction:


ConcertReservation[A]


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



But it is possible to overcome this. For example with the code below (which compiles):


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



Is there a idiomatic way to achieve what I want? Some kind of "pattern" perhaps?

Thanks,






Maybe you should use assert? Write in the body of ConcertReservation validation logic assert((userTicket.nonEmpty && spouseTicket.nonEmpty) || (userTicket.isEmpty && spouseTicket.isEmpty))

– Aleksey Isachenkov
Sep 10 '18 at 17:16



assert


ConcertReservation


assert((userTicket.nonEmpty && spouseTicket.nonEmpty) || (userTicket.isEmpty && spouseTicket.isEmpty))






That would work in practice. Thanks for the trouble of answering. I am however looking for the type-level solution. The one that can be verified at compile time.

– Carlos Teixeira
Sep 10 '18 at 17:21






Also, you can create two subtypes of ConcertReservation with Some+Some and None+None reservations and make ConcertReservation abstract and sealed (like Option with its subclasses Some and None).

– Aleksey Isachenkov
Sep 10 '18 at 17:25



ConcertReservation


ConcertReservation






if you want a type restriction change ConcertReservation type parameter to [A <: Some[String]], the problem threre is that you cant assign a set with Option("id"), onyl Some("id")

– Sebastian Celestino
Sep 10 '18 at 17:46




3 Answers
3



If you copy the definition of =!= ("non-equal types") from this answer, you can then use it to make sure that A isn't Option[String]:


=!=


A


Option[String]


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



This results in the expected behavior:


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!



You can set up Ticket as a trait and then do some implicit type checking.


Ticket


trait


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



Next, you can take the type of the two parameters separately, and include an implicit check that they are equal as a parameter. You can also add a type inequality check to make sure the type isn't Ticket, but that will require you to include a library like shapeless, or do some more mucking around with the type system.


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



When T1 and T2 match, it works fine, but when they are different, the type system can pick up the error.


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.






But Reservation[Ticket, Ticket](sit1,stand1) would still compile, wouldn't it?

– Tzach Zohar
Sep 10 '18 at 18:28


Reservation[Ticket, Ticket](sit1,stand1)






@TzachZohar Oops, forgot to test that, you're right. I think it's fixable with a T1 =:!= Ticket check with shapeless, but don't currently have the time to verify. When I get a chance I'll verify then fix/delete this.

– Ethan
Sep 10 '18 at 19:12


T1 =:!= Ticket



As per my understanding of the problem, whether the seats are allocated or not is known only at the runtime, you cannot have a compile-time check for this.



But if you really want to achieve restriction for both or none, you should use:
seats: Option[(String, String)] or if you want to check on runtime then you can do some pattern matching over both the seats:


seats: Option[(String, String)]


val valid = (userTicket.seat, spouseTicket.seat) match
case (Some(_), Some(_))



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)