Home > front end >  Dependent type constraints (?) on trait in Scala
Dependent type constraints (?) on trait in Scala

Time:02-01

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

https://tpolecat.github.io/2015/04/29/f-bounds.html

  • Related