0

I'm a bit new to Scala and I'm trying to write a generic client for a RESTful api I would like to use. I'm able to provide concrete Reads[T] and Writes[T] for the specific case classes I would like to instantiate my client for, however the compiler expects to find a Reads[T] and Writes[T] for any type, not just the types I'm using. Some code to illustrate (I've omitted irrelevant sections):

My generic client:

class RestModule[T](resource: String, config: Config) ... with JsonSupport{
...
def create(item: T): Future[T] = {
    val appId = config.apiId
    val path = f"/$apiVersion%s/applications/$appId%s/$resource"

    Future {
      val itemJson = Json.toJson(item)
      itemJson.toString.getBytes
    } flatMap {
      post(path, _)
    } flatMap { response =>
      val status = response.status
      val contentType = response.entity.contentType

      status match {
        case Created => contentType match {
          case ContentTypes.`application/json` => {
            Unmarshal(response.entity).to[T]
          }
          case _ => Future.failed(new IOException(f"Wrong Content Type: $contentType"))
        }
        case _ => Future.failed(new IOException(f"HTTP Error: $status"))
      }
    }
...
}

JsonSupprt Trait:

trait JsonSupport {
    implicit val accountFormat = Json.format[Account]
}

I'm only ever instantiating as RestModule[Account]("accounts",config) but I get the error

Error:(36, 32) No Json serializer found for type T. Try to implement an implicit Writes or Format for this type.
  val itemJson = Json.toJson(item)
                           ^

Why does the compiler think it needs a Writes for type T when T can only ever be of type Account? Is there any way to work around this?

Rag
  • 195
  • 1
  • 13

1 Answers1

1

The reason why the compiler doesn't like what you're doing is to do with how implicit parameters are resolved and more crucially when they are resolved.

Consider the snippet,

Object MyFunc {
   def test()(implicit s: String): String = s
}

The implicit parameter only gets resolved by the parameter when the function is called, and basically is expanded as,

MyFunc.test()(resolvedImplicit)

In your particular case you actually call the function requiring the implicit and hence it looks for an implicit of T at that point in time. Since it can't find one in scope it fails to compile.

In order to solve this issue, simply add the implicit parameter to the create method to tell the compiler to resolve it when you call create rather than toJson within create.

Furthermore we can use scala's implicit rules to get the behaviour that you want.

Let's take your trait Reads,

trait Reads[A] {
}

object MyFunc {
  def create[A](a: A)(implicit reads: Reads[A]): Unit = ???
}

as we said befeore you can call it if the implicit is in scope. However, in this particular case where you have predefined reads we can actually put it in the companion object,

object Reads {
  implicit val readsInt: Reads[Int] = ???
  implicit val readsString: Reads[String] = ???
}

This way when create is called, the user doesn't need to import or define any implicit vals when A is Int or String because scala automatically looks in the companion object for any implicit definitions if it can't find one in the current scope.

yw3410
  • 228
  • 3
  • 8
  • Thanks! What you say works but is there a way I can avoid changing the interface or having the caller be responsible for providing the implicit ```Reads``` and ```Writes```? That's what I was going for by putting them in the trait, ideally I'd like to make them available implicitly somewhere in the class without callers having to worry about putting them in scope. – Rag Feb 29 '16 at 20:38
  • Certainly. What you can do is to add the `implicit` parameter but add your own `implicit` `val`s of `Reads` and `Writes` within their own companion object. That way when someone uses `create` scala will look into your predefined `Reads` and `Writes` first. – yw3410 Feb 29 '16 at 21:06
  • Great thanks, I tested this in a separate project and see how it works now. Although in this specific case, ```Reads[A]``` and ```Writes[A]``` are provided by a library (play-json) along with a companion object, so I've made a companion object to go with ```RestModule``` instead which contains the required implicts which clients should import prior to calling ```create```. It's not as transparent as I'd like it to be but it works :) – Rag Mar 01 '16 at 09:21