web-dev-qa-db-fra.com

Scala - comment imprimer des classes de cas comme une arborescence (jolie)

Je fais un analyseur syntaxique avec Scala Combinators. C'est génial. Je termine avec une longue liste de classes de cas complexes, telles que: ClassDecl(Complex,List(VarDecl(Real,float), VarDecl(Imag,float))), juste 100 fois plus longtemps. Je me demandais s’il existait un bon moyen d’imprimer des classes de cas telles que celles-ci sous forme d’arborescence afin de faciliter la lecture ..? (ou une autre forme de Pretty Print)

ClassDecl
  name = Complex
  fields =
  - VarDecl
      name = Real
      type = float
  - VarDecl
      name = Imag
      type = float

^ Je veux finir avec quelque chose comme ça

edit: Question bonus  

Est-il également possible d'afficher le nom du paramètre ..? Comme: ClassDecl(name=Complex, fields=List( ... )?

39
kornfridge

Découvrez une petite bibliothèque d'extensions nommée sext . Il exporte ces deux fonctions à des fins similaires.

Voici comment cela peut être utilisé pour votre exemple:

object Demo extends App {

  import sext._

  case class ClassDecl( kind : Kind, list : List[ VarDecl ] )
  sealed trait Kind
  case object Complex extends Kind
  case class VarDecl( a : Int, b : String )


  val data = ClassDecl(Complex,List(VarDecl(1, "abcd"), VarDecl(2, "efgh")))
  println("treeString output:\n")
  println(data.treeString)
  println()
  println("valueTreeString output:\n")
  println(data.valueTreeString)

}

Voici le résultat de ce programme:

treeString output:

ClassDecl:
- Complex
- List:
| - VarDecl:
| | - 1
| | - abcd
| - VarDecl:
| | - 2
| | - efgh

valueTreeString output:

- kind:
- list:
| - - a:
| | | 1
| | - b:
| | | abcd
| - - a:
| | | 2
| | - b:
| | | efgh
29
Nikita Volkov
import Java.lang.reflect.Field
...

/**
  * Pretty prints case classes with field names.
  * Handles sequences and arrays of such values.
  * Ideally, one could take the output and paste it into source code and have it compile.
  */
def prettyPrint(a: Any): String = {
  // Recursively get all the fields; this will grab vals declared in parents of case classes.
  def getFields(cls: Class[_]): List[Field] =
    Option(cls.getSuperclass).map(getFields).getOrElse(Nil) ++
        cls.getDeclaredFields.toList.filterNot(f =>
          f.isSynthetic || Java.lang.reflect.Modifier.isStatic(f.getModifiers))
  a match {
    // Make Strings look similar to their literal form.
    case s: String =>
      '"' + Seq("\n" -> "\\n", "\r" -> "\\r", "\t" -> "\\t", "\"" -> "\\\"", "\\" -> "\\\\").foldLeft(s) {
        case (acc, (c, r)) => acc.replace(c, r) } + '"'
    case xs: Seq[_] =>
      xs.map(prettyPrint).toString
    case xs: Array[_] =>
      s"Array(${xs.map(prettyPrint) mkString ", "})"
    // This covers case classes.
    case p: Product =>
      s"${p.productPrefix}(${
        (getFields(p.getClass) map { f =>
          f setAccessible true
          s"${f.getName} = ${prettyPrint(f.get(p))}"
        }) mkString ", "
      })"
    // General objects and primitives end up here.
    case q =>
      Option(q).map(_.toString).getOrElse("¡null!")
  }
}
5
F. P. Freely

Tout comme les combinateurs d’analyseurs, Scala contient déjà de jolis combinateurs d’imprimantes dans la bibliothèque standard. Vous ne le dites pas clairement dans votre question si vous avez besoin de la solution qui fait "réflexion" ou si vous souhaitez construire l'imprimante explicitement. (bien que votre "question bonus" suggère que vous souhaitiez probablement une solution "réfléchissante")

Quoi qu'il en soit, si vous souhaitez développer une imprimante simple et jolie à l’aide de la bibliothèque standard Scala, la voici. Le code suivant est REPLable.

case class VarDecl(name: String, `type`: String)
case class ClassDecl(name: String, fields: List[VarDecl])

import scala.text._
import Document._

def varDoc(x: VarDecl) =
  nest(4, text("- VarDecl") :/:
    group("name = " :: text(x.name)) :/:
    group("type = " :: text(x.`type`))
  )

def classDoc(x: ClassDecl) = {
  val docs = ((empty:Document) /: x.fields) { (d, f) => varDoc(f) :/: d }
  nest(2, text("ClassDecl") :/:
    group("name = " :: text(x.name)) :/:
    group("fields =" :/: docs))
}

def prettyPrint(d: Document) = {
  val writer = new Java.io.StringWriter
  d.format(1, writer)
  writer.toString
}

prettyPrint(classDoc(
  ClassDecl("Complex", VarDecl("Real","float") :: VarDecl("Imag","float") :: Nil)
))

Question bonus : place les imprimantes dans des classes de types pour une composition encore plus grande.

5
Alexander Azarov

Utilisez la bibliothèque com.lihaoyi.pprint.

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

val data = ...

val str = pprint.tokenize(data).mkString
println(str)

vous pouvez également configurer la largeur, la hauteur, le retrait et les couleurs:

pprint.tokenize(data, width = 80).mkString

Docs: http://www.lihaoyi.com/PPrint/

5
David Portabella

L’expérience la plus concise «hors du commun» que j’ai trouvée concerne la bibliothèque d’impressions Kiama . Cela n'imprime pas les noms des membres sans utiliser de combinateurs supplémentaires, mais avec seulement import org.kiama.output.PrettyPrinter._; pretty(any(data)), vous avez un bon départ:

case class ClassDecl( kind : Kind, list : List[ VarDecl ] )
sealed trait Kind
case object Complex extends Kind
case class VarDecl( a : Int, b : String )

val data = ClassDecl(Complex,List(VarDecl(1, "abcd"), VarDecl(2, "efgh")))
import org.kiama.output.PrettyPrinter._

// `w` is the wrapping width. `1` forces wrapping all components.
pretty(any(data), w=1)

Produit:

ClassDecl (
    Complex (),
    List (
        VarDecl (
            1,
            "abcd"),
        VarDecl (
            2,
            "efgh")))

Notez que ceci est juste l'exemple le plus fondamental. Kiama PrettyPrinter est une bibliothèque extrêmement puissante dotée d’un riche ensemble de combinateurs spécialement conçus pour l’espacement intelligent, le retour à la ligne, l’emboîtement et le regroupement. Il est très facile d'ajuster à vos besoins. A partir de cette publication, il est disponible en SBT avec:

libraryDependencies += "com.googlecode.kiama" %% "kiama" % "1.8.0"
4
metasim

Utiliser la réflexion

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
  }
}
1
piotr

C'est un copier-coller sans scrupule de @F. P Librement, mais j'ai ajouté une fonctionnalité d'indentation et apporté quelques légères modifications afin que la sortie soit du style Scala correct (et compile pour tous les types primatifs)

import Java.lang.reflect.Field

def prettyPrint(a: Any, indentSize: Int = 0): String = {
    // Recursively get all the fields; this will grab vals declared in parents of case classes.
    def getFields(cls: Class[_]): List[Field] =
      Option(cls.getSuperclass).map(getFields).getOrElse(Nil) ++
        cls.getDeclaredFields.toList.filterNot(f =>
          f.isSynthetic || Java.lang.reflect.Modifier.isStatic(f.getModifiers))

    val indent = List.fill(indentSize)(" ").mkString

    (a match {
      // Make Strings look similar to their literal form.
      case s: String =>
        '"' + Seq("\n" -> "\\n", "\r" -> "\\r", "\t" -> "\\t", "\"" -> "\\\"", "\\" -> "\\\\").foldLeft(s) {
          case (acc, (c, r)) => acc.replace(c, r)
        } + '"'
      case xs: Seq[_] =>
        xs.map(prettyPrint(_, indentSize)).toString
      case xs: Array[_] =>
        s"Array(${xs.map(prettyPrint(_, indentSize)).mkString(", ")})"
      // This covers case classes.
      case None => "None"
      case Some(x) => "Some(" + prettyPrint(x, indentSize) + ")"
      case p: Product =>
        s"${p.productPrefix}(\n${
          getFields(p.getClass).map { f =>
            f.setAccessible(true)
            s"  ${f.getName} = ${prettyPrint(f.get(p), indentSize + 2)}"
          }
          .mkString(",\n")
        }\n)"
      // General objects and primitives end up here.
      case q =>
        Option(q).map(_.toString).getOrElse("null")
    })
      .split("\n", -1).mkString("\n" + indent)
  }

Par exemple.

case class Foo(bar: String, bob: Int)

case class Alice(foo: Foo, opt: Option[String], opt2: Option[String])

scala> prettyPrint(Alice(Foo("hello world", 10), Some("asdf"), None))
res6: String =
Alice(
  foo = Foo(
    bar = "hello world",
    bob = 10
  ),
  opt = Some("asdf"),
  opt2 = None
)
0
samthebest