0

I have some code that streams a response by converting a Stream of case classes to json representations using spray.json. This works fine for a single case class, but I want to genericize it.

So I'm starting with case classes like this:

import spray.json._
import spray.json.DefaultJsonProtocol._
case class Item(foo: String, bar: Int)
case class Report(baz: String, stream: Stream[Item])
object Protocol { implicit val ItemFormat = jsonFormat2(Item) }

In my report streaming method I have code like this (highly simplified):

def streamReport(...) {
    import Protocol._

    val handler: PartialFunction[Try[Any], String] = {
        case Success(Report(_, stream)) =>
            stream.head.toJson.compactPrint
    }
}

What I'd like to do is genericize Report to support more than Item:

case class Report[T](baz: String, stream: Stream[T])

But now, of course, the streamReport method can't find a JsonWriter in scope for type Any.

I can do something close to what I want if I add a type parameter with context bound to streamReport, and pass in the Report directly:

def jsonStream[T : JsonWriter](report: Report[T]): String = 
    implicitly[JsonWriter[T]].write(report.stream.head).compactPrint

However, I cannot figure out how to get this to work with the PartialFunction. The following does not compile (and wouldn't fit exactly anyway as the signature of the partial function is different than above):

def handler[T : JsonWriter](): PartialFunction[T, String] = {
    case Success(Report(_, stream)) =>
        implicitly[JsonWriter[T]].write(report.stream.head).compactPrint
}

I'm not sure where it's going wrong. Does it have to do with type erasure, or a problem with having Try[Any] as the parameter type on my partial function? How can I get the implicit JsonWriter I need for the element type of the stream?

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
ryryguy
  • 610
  • 4
  • 15
  • 1
    Should `handler` really be `PartialFunction[Try[Report[T]], String]` ? – Michael Zajac Apr 30 '15 at 17:54
  • m-z: Perhaps, although I'm trying to work in existing code that is used for other stuff which uses other types, requiring the use of `Any`. Is it possible to somehow map between the two different `PartialFunction` signatures? – ryryguy Apr 30 '15 at 18:40
  • I'm not sure what you mean by your last sentence. However, if you're trying to match on other types that don't depend on the type parameter `T`, you're probably going to run into trouble with type erasure. Hard to say without more detail of what is being matched. – Michael Zajac May 01 '15 at 13:12
  • The code I'm trying to refactor is part of a framework where responses are returned by Actors, and this code (and other similar method) is returning a partial function that is used to build handlers for those responses. In this particular case (for this handler) the only thing that can possibly be returned is a `Report[T]`, but in the larger scope, there are other actors which return other things, so the partial function signature takes an `Any`. – ryryguy May 01 '15 at 16:51
  • I guess the thing is, regardless of what the partial function signature is,inside the actual match block we know it's got a `Report`. Even though we don't know `T` I'd hope we could use that `implicitly` trick to get the `JsonWriter`, but we can't. – ryryguy May 01 '15 at 16:54

1 Answers1

0

My guess is that Any is not the problem, the problem is trying to match case Success(Report(_, stream)), after the definition for Report is now Report[T].

Also, in order to find the right implicit conversion, the compiler needs to understand the type involved (in compile time). Try using something like this:

def streamReport[T](...) {
    import Protocol._

    val handler: PartialFunction[Try[Any], String] = {
        case Success(Report[T](_, stream)) =>
            stream.head.toJson.compactPrint
    }
}

That way, the compiler will know what is being matched, and can infer the right type.

Daniel Langdon
  • 5,899
  • 4
  • 28
  • 48