Maxence Cramet
Maxence Cramet

Reputation: 594

Scala ignore generic type

I'm trying to avoid to use a generic on Request that contains an optional generic Body to avoid to pass a generic everywhere when it's only needed when writing the body, and also because there's no type when body is not defined:

case class Body[A](content: A)(implicit val bodyWritable: BodyWritable[A])

case class Request(url: String, body: Option[Body[_]])
  
private def executeRequest(request: Request) = {    
    val wsClient: StandaloneWSClient = ???
    val requestWithUrl = wsClient.url(request.url)
    request.body.fold(requestWithUrl)(body => requestWithUrl.withBody(body.content)(body.bodyWritable))
}

The compilation failed with:

Error:(20, 90) type mismatch;

found : play.api.libs.ws.BodyWritable[(some other)_$1(in value body)]

required: play.api.libs.ws.BodyWritable[_$1(in value body)]

I'm wondering if there's a way to not type request.

I'm using "com.typesafe.play" %% "play-ws-standalone" % "2.0.4"

Upvotes: 0

Views: 477

Answers (1)

SwiftMango
SwiftMango

Reputation: 15294

In short you cant do this way. When wildcard is involved, the type parameter of an expression is always different from others, even they come from the same variable. The type A body.content and the type parameter A of body.bodyWritable are resolved as different local anonymous types, even though they come from the same variable body, but Scala does not know that, because they are separate expressions.

To resolve this, the most type-safe way (and recommended) is to add type parameter to Request and executeRequest to ensure the type is resolved as the same.

You can also create a local method that takes a type parameter to make sure the two expressions shares the same generic type:

private def executeRequest(request: Request) = {    
    val wsClient: StandaloneWSClient = ???
    val requestWithUrl = wsClient.url(request.url)
    def f[A](body: Body[A]) = requestWithUrl.withBody(body.content)(body.bodyWritable)
    request.body.fold(requestWithUrl)(body => f(body)) // or shorten to request.body.fold(requestWithUrl)(f)
}

Alternatively, I sometimes move the calling site into the class where the type parameter is declared, in which the type parameter is guaranteed to be the same. Something like:

case class Body[A](content: A)(implicit val bodyWritable: BodyWritable[A]) {
  def getRequest(req: WSRequest) = req.withBody(content)
}

case class Request(url: String, body: Option[Body[_]])

private def executeRequest(request: Request) = {    
    val wsClient: StandaloneWSClient = ???
    val requestWithUrl = wsClient.url(request.url)
    request.body.fold(requestWithUrl)(body => body.getRequest(requestWithUrl))
}

Upvotes: 2

Related Questions