Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide better toString for case classes #3967

Closed
scabug opened this issue Oct 28, 2010 · 18 comments
Closed

Provide better toString for case classes #3967

scabug opened this issue Oct 28, 2010 · 18 comments

Comments

@scabug
Copy link

scabug commented Oct 28, 2010

scala> case class Foo(i: Int, j: Int)
defined class Foo

scala> Foo(1,2)
res50: Foo = Foo(1,2)

It would have been more readable for Foo's toString to print something like:

Foo(i=1,j=2)
@scabug
Copy link
Author

scabug commented Oct 28, 2010

Imported From: https://issues.scala-lang.org/browse/SI-3967?orig=1
Reporter: Ittay Dror (ittayd)

@scabug
Copy link
Author

scabug commented Mar 2, 2011

Ittay Dror (ittayd) said:
what does it mean that the owner is now scala_community? if it means it is up to he community to provide a patch, then i don't mind trying, but would love a few pointers to get started

@scabug
Copy link
Author

scabug commented Mar 2, 2011

@paulp said:
It means it isn't on anyone's radar. It's not as easy as providing a patch though, the real trick here is in the convincing. I wouldn't want this change and I doubt I'd be alone, so you would need both a patch and a bunch of support. Another plausible outcome has us giving the user some control over the generated methods: and that leads to ten times as many questions.

Also, we're still missing the reflection lib you need to write your desired toString method in a non-hack way (to get the parameter names.)

@scabug
Copy link
Author

scabug commented Mar 2, 2011

Ittay Dror (ittayd) said:
I didn't think in terms of reflection.

If I understand correctly, today toString is implemented by calling ScalaRunTime._toString(this) which accepts Product instances.

Instead, I would like to suggest the compiler will generate code for toString similar to how code is generated for the equals method (btw, why isn't there a ScalaRunTime._equals method?)

@scabug
Copy link
Author

scabug commented Mar 2, 2011

@paulp said:
Reflection is how martin wants the names obtained. If you look through the history you'll find I already implemented def productElementNames that way and then later pulled it. As to the last, you can't write a sufficiently general equals method outside the class given the method signature.

@scabug
Copy link
Author

scabug commented Mar 2, 2011

Ittay Dror (ittayd) said:
I tried looking back and found this: https://chara2.epfl.ch/r/515/

"The problem with the current implementation is that it adds a rather heavyweight method to every case class. This is a lot of additional code for not a lot of appreciable gain"

But I think toString has 2 differences:

  1. it is already a method implemented for the case class
  2. it is less heavyweight than productElementName (which switches according to an index).

So if I have:

case class Person(name: String, age: Int)

the toString can be:

public String toString() { return ScalaRunTime..MODULE$$._toString(this, "name", "age");}

where _toString is:

def _toString(p: Product, names: String*)

@scabug
Copy link
Author

scabug commented Mar 2, 2011

@paulp said:
Sorry, I have no time for this. Like I said I don't even prefer your version, so the other details are not so relevant to me. You should try implementing it if only for the thrill of modifying the compiler.

@scabug
Copy link
Author

scabug commented Sep 16, 2011

@SethTisue said:
I'd cut off my pinky for this. (I mean, like, if it would grow back in a few months.)

@scabug
Copy link
Author

scabug commented Sep 16, 2011

Tony Sloane (asloane) said:
Count me as a negative vote. The extra information from printing the field names is useful in some cases, but it makes the output much more verbose. Keep things simple by default.

@scabug
Copy link
Author

scabug commented Sep 16, 2011

@SethTisue said:
Incidentally, I'd be willing to do the drudge work of updating failing test cases. I imagine there would be many.

@scabug
Copy link
Author

scabug commented Sep 16, 2011

@paulp said:
./partest --update-check --failed

@scabug
Copy link
Author

scabug commented May 12, 2012

@retronym said:
This sounds like a good use case for macros.

@scabug
Copy link
Author

scabug commented Dec 5, 2012

@retronym said:
Closing. Even if we wanted to, we can't change this in a backwards compatible way.

With 2.10.0 you will be able to add:

  override def toString = myCaseClassToString(this)

Where myCaseClassToString is a macro that can see the parameter names.

If the research into macro annotations bears fruit, you could shorten this to:

@myCaseToString
case class Person(...)

But we can't make any promises about that feature.

Unfortunately that reflection/macro API is still marked as experimental for 2.10.x, but you might find this sort of use case compelling enough to live with the possibility of a little flux.

@scabug scabug closed this as completed Dec 5, 2012
@scabug
Copy link
Author

scabug commented Oct 12, 2014

@etorreborre said:
Here is a solution given by Jason on the mailing-list, using macros in Scala 2.11:

import scala.reflect.macros.blackbox._
import scala.language.experimental._

object ToString {
  def toStringWithNames: String = macro toStringWithNamesImpl
  def toStringWithNamesImpl(c: Context): c.Tree = {
    import c.universe._

    
// class for which we want to display toString

    val klass = c.internal.enclosingOwner.owner

    
// we keep the getter fields created by the user

    val fields: Iterable[c.Symbol] = klass.asClass.toType.decls
      .filter(sym => sym.isMethod && sym.asTerm.isParamAccessor) // we should do more filtering here


    // print one field as <name of the field>+"="+fieldName
    def printField(field: Symbol) = {
      val fieldName = field.name

      q"""${fieldName.decoded.toString}+${"="}+this.$field"""
    }
    val params = fields.foldLeft(q"${""}")((res, acc) => q"${printField(acc)} + $res")

    
// print the class and all the parameters with their values

    q"this.productPrefix + ${"("} + $params + ${")"}"
  }
}

==> sandbox/test.scala <==
case class Point(x: Int) {
  
override def toString = ToString.toStringWithNames
}

object Test extends App {
  println(Some(Point(1)).toString)
}
% scalac-hash v2.11.2 sandbox/macro.scala && scalac-hash v2.11.2  sandbox/test.scala && scala-hash v2.11.2 Test
warning: there was one deprecation warning; re-run with -deprecation for details
one warning found
Some(Point(x=1))

@scabug
Copy link
Author

scabug commented Nov 10, 2014

Dean Hiller (deanhiller) said:
could we turn this ticket into a request for toPrettyString() method on all case classes. That would be very nice and tons of people would use that method and this doesn't break any backwards compatibility.

@scabug
Copy link
Author

scabug commented Nov 10, 2014

Dean Hiller (deanhiller) said:
or maybe an implicit function we can add ourselves with an import? anything so we just don't need to pull in a library or roll our own would be so nice.

@scabug
Copy link
Author

scabug commented Feb 21, 2016

Pedro Larroy said:
Using reflection:

import scala.reflect.ClassTag
import scala.reflect.runtime.universe._

object CaseClassBeautifier {
def getCaseAccessors[T: TypeTag] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList

def nice[T:TypeTag](x: T)(implicit classTag: ClassTag[T]) : String = {
val instance = x.asInstanceOf[T]
val mirror = runtimeMirror(instance.getClass.getClassLoader)
val accessors = getCaseAccessors[T]
var res = List.empty[String]
accessors.foreach { z ⇒
val instanceMirror = mirror.reflect(instance)
val fieldMirror = instanceMirror.reflectField(z.asTerm)
val s = s"${z.name} = ${fieldMirror.get}"
res = s :: res
}
val beautified = x.getClass.getSimpleName + "(" + res.mkString(", ") + ")"
beautified
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants