I have the following model:
sealed trait MyRequest {
type Response <: MyResponse
}
sealed trait MyResponse {
type Request <: MyRequest
}
case class SayHelloRequest(name: String) extends MyRequest {
override type Response = SayHelloResponse
}
case class SayHelloResponse(greeting: String) extends MyResponse {
override type Request= SayHelloRequest
}
...
Is there some way, at the type level, I can enforce that the request/response pair match together? So if SayHelloRequest
has a response type SayHelloResponse
then SayHelloResponse
must have a request type of SayHelloRequest
.
Something like:
MyRequest#Response#Request =:= MyRequest
MyResponse#Request#Response =:= MyResponse
CodePudding user response:
Try F-bounds
sealed trait MyRequest { self =>
type This >: self.type <: MyRequest { type This = self.This }
type Response <: MyResponse { type Request = self.This }
}
sealed trait MyResponse { self =>
type This >: self.type <: MyResponse { type This = self.This }
type Request <: MyRequest { type Response = self.This }
}
case class SayHelloRequest(name: String) extends MyRequest {
override type This = SayHelloRequest
override type Response = SayHelloResponse
}
case class SayHelloResponse(greeting: String) extends MyResponse {
override type This = SayHelloResponse
override type Request = SayHelloRequest
}
implicitly[SayHelloRequest#Response#Request =:= SayHelloRequest] // compiles
implicitly[SayHelloResponse#Request#Response =:= SayHelloResponse] // compiles
implicitly[MyRequest#Response#Request <:< MyRequest#This] // compiles
implicitly[MyResponse#Request#Response <:< MyResponse#This] // compiles
or a type class (and F-bounds)
// type class
trait RequestResponse {
type Request <: MyRequest[Request]
type Response <: MyResponse[Response]
}
object RequestResponse {
type Req[Rq <: MyRequest[Rq]] = RequestResponse { type Request = Rq }
type Resp[Rsp <: MyResponse[Rsp]] = RequestResponse { type Response = Rsp }
type Aux[Rq <: MyRequest[Rq], Rsp <: MyResponse[Rsp]] =
RequestResponse { type Request = Rq; type Response = Rsp }
// materializers
def req[Rq <: MyRequest[Rq]](implicit
reqResp: RequestResponse.Req[Rq]
): RequestResponse.Aux[Rq, reqResp.Response] = reqResp
def resp[Rsp <: MyResponse[Rsp]](implicit
reqResp: RequestResponse.Resp[Rsp]
): RequestResponse.Aux[reqResp.Request, Rsp] = reqResp
}
sealed abstract class MyRequest[This <: MyRequest[This]](
implicit val reqResp: RequestResponse.Req[This]
) { self: This =>
type Response = reqResp.Response
}
sealed abstract class MyResponse[This <: MyResponse[This]](
implicit val reqResp: RequestResponse.Resp[This]
) { self: This =>
type Request = reqResp.Request
}
case class SayHelloRequest(name: String) extends MyRequest[SayHelloRequest]
case class SayHelloResponse(greeting: String) extends MyResponse[SayHelloResponse]
implicit val sayHelloRequestResponse: RequestResponse.Aux[SayHelloRequest, SayHelloResponse] = null
val rr = RequestResponse.req[SayHelloRequest]
implicitly[rr.Response =:= SayHelloResponse] // compiles
val rr1 = RequestResponse.resp[SayHelloResponse]
implicitly[rr1.Request =:= SayHelloRequest] // compiles