David Portabella David Portabella - 1 month ago 6
Scala Question

scala, I lose the type information when using pprint

I would expect that

toPrettyString1
and
toPrettyString2
get the same result, but it seems that
toPrettyString1
losses the type information, and
pprint.tokenize
cannot the fields and print it nicely.

Why is so, and how to solve it?

libraryDependencies += "com.lihaoyi" %% "pprint" % "0.4.1"

case class Entry(id: Int, text: String)
val entry = Entry(1, "hello")

def toPrettyString1[T](o: T) = pprint.tokenize(o).mkString

def toPrettyString2(o: Entry) = pprint.tokenize(o).mkString

println(toPrettyString1(entry)) // I get Entry(1,hello), not what I want
println(toPrettyString2(entry)) // I get Entry(1, "hello"), as expected

Answer

The pprint library uses a PPrint type class to determine how a value of a particular type should be printed. If you have a completely unconstrained type T, you'll get a PPrint instance that simply calls toString on the value, which means you won't get quotation marks or the nice escaping pprint provides.

This is really all pprint can reasonably do if it knows nothing about the type, and even that is arguably too much—many type class-based libraries simply do not provide default instances like this even when there is a possible default interpretation of what the type class operations should do.

toPrettyString2 works as expected because you're calling tokenize with a value that's statically typed as Entry, which PPrint can provide a better instance for. If you want this behavior with a generic type, you'll have to add a context bound:

def toPrettyString3[T: pprint.PPrint](o: T) = pprint.tokenize(o).mkString

And then:

scala> println(toPrettyString3(entry))
Entry(1, "hello")

Now you're calling tokenize not with some completely generic T, but specifically with a T that you have a PPrint instance for, and when you call toPrettyString3 with an Entry argument, the library will provide a PPrint instance with the quotation marks, escaping, etc.


The T: pprint.PPrint syntax is just sugar for an implicit argument:

def toPrettyString3[T](o: T)(implicit pp: pprint.PPrint[T]) =
  pprint.tokenize(o)(pp).mkString

You didn't define an implicit PPrint[Entry] instance, but the library is able to derive reasonable instances for case classes using a macro. You could also define your own instance if for some reason you wanted different behavior, though, and it would override the derived instance.