Case Class toString new behavior proposal (with implementation)

@joshlemer The idea is for the string representation to be exactly the code typed to create the instance, I would there fore expect expect the string to contain the escape. If I were to implement it, I would probably look at how it’s done in https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#escapeJava(java.lang.String) and start from there.

3 Likes

I recommend you check out pprint as it works pretty much as you’re describing http://www.lihaoyi.com/PPrint/

2 Likes

The PR https://github.com/scala/scala/pull/6951 adding productElementNames has been reviewed by Jason Zaugg from the compiler team with actionable feedback if someone wants to pick this up.

1 Like

thanks for the pointer, since this thread was originally about changing the toString behaviour, I wish it would behave like pprint :slight_smile:

but I will look into integrating pprint for my projects thought that won’t help with teaching scala classes.

You can get pretty close to case classes a la carte in current Scala: see my Scala eXchange talk from 2014 and an example in the shapeless tree. It would probably make sense to think about this as a target for generic programming in Scala 3.

1 Like

The PR https://github.com/scala/scala/pull/6972 has been merged adding the method productElementName to case classes. This means that it’s possible to implement the functionality propsed here without macros or runtime reflection

  def pretty(p: Product): String =
    p.productElementNames.zip(p.productIterator)
     .map { case (name, value) => s"$name=$value" }
     .mkString(p.productPrefix + "(", ", ", ")")

pretty(User("Susan", 42))
// User(name=Susan, age=42)

I started a discussion on adding such a pretty-printing function to the standard library https://github.com/scala/scala/pull/6972#issuecomment-411360323

7 Likes

and they lived happily ever after

:slight_smile: The end :slight_smile:

1 Like

I found myself coming back to this thread a few times to copy-paste the pretty function by @olafurpg. For anyone else doing similar, here is that function wrapped up in a trait for easier consumption

trait PrettyProduct extends Product {
  override def toString: String = {
    this
      .productElementNames
      .zip(this.productIterator)
      .map { case (name, value) => s"$name=$value" }
      .mkString(this.productPrefix + "(", ", ", ")")
  }
}
final case class Apple(a: Int, b: String) extends PrettyProduct

println(Apple(a=3, b="hello")); // Apple(a=3, b=hello)
7 Likes

Just stumbled on a draft PR by som-snytt that implements pretty printing of case classes with field names https://github.com/scala/scala/pull/8885

scala> val a = Sum("A", "B")
val a: Sum = Sum(exp = "A", exp2 = "B")

Personally I think this feature would greatly help with debugging and reading logs.

4 Likes

For me, in some cases it’s worthy, in others it’s too wordy :wink:
I would liked it better if the REPL just provided a Show type class that we can configure.

1 Like

Ammonites allows you to configure the pretty-printer, and with a bit of work could show field names too [WIP] Include field names in rendering of Product (Scala 2.13 only) by cb372 · Pull Request #38 · com-lihaoyi/PPrint · GitHub

1 Like

Is it thinkable, to change the behavior of generated toString to include both field names and properly quoted strings, at least in Scala 3, if it is too big of a change for Scala 2.x?

3 Likes

https://github.com/lihaoyi/PPrint gives you properly quoted strings, colors, nice indentation, streaming/incremental printing (e.g. for the first N lines of huge data structures) and a bunch of other things. If you’re still using println debugging, it’s definitely worth giving pprint.log a try

1 Like

Ideally with a way to easily create either a concise or verbose default implementation, as appropriate for the data structure.

PPrint 0.6.0 implemented support in Include field names in rendering of Product (Scala 2.13 only) #38, for example

case class Address(planet: String, city: String)
case class User(name: String, age: Int, address: Address)
pprint.pprintln(User("Picard", 75, Address("Earth", "San Francisco")), width = 80, height = 80)

outputs

User(
  name = "Picard",
  age = 75,
  address = Address(planet = "Earth", city = "San Francisco")
)
4 Likes

Yep! I was about the post here, but you beat me to it.

There’s a field you can override on PPrinter to revert to the old behavior, but the new behavior is useful enough i turned it on by default for Scala 2.13

5 Likes

Any chance that’ll get backported to 2.12?

From the PR:
“This is only available in Scala 2.13 because it makes use of productElementNames. The behaviour is unchanged in Scala 2.11/2.12.”

I’m aware, just wondering if that might change as this feature gets more traction

You mean adding the productElementNames method to case classes in 2.12?