Scala - 如何打印像(漂亮打印)树一样的案例类

kor*_*dge 47 scala pretty-print case-class

我正在使用Scala Combinators创建解析器.太棒了.我最终得到的是一长串陷入困境的案例类,例如:ClassDecl(Complex,List(VarDecl(Real,float), VarDecl(Imag,float)))只需要100倍.我想知道是否有一种很好的方法以树状方式打印这样的案例类,以便更容易阅读..?(或其他形式的漂亮印刷品)

ClassDecl
  name = Complex
  fields =
  - VarDecl
      name = Real
      type = float
  - VarDecl
      name = Imag
      type = float
Run Code Online (Sandbox Code Playgroud)

我想结束这样的事情

编辑:奖金问题

是否还有一种方法来显示参数的名称..?喜欢:ClassDecl(name=Complex, fields=List( ... )

Nik*_*kov 33

查看一个名为sext的小扩展库.它完全出于这样的目的导出这两个函数.

以下是它如何用于您的示例:

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)

}
Run Code Online (Sandbox Code Playgroud)

以下是该计划的输出:

treeString output:

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

valueTreeString output:

- kind:
- list:
| - - a:
| | | 1
| | - b:
| | | abcd
| - - a:
| | | 2
| | - b:
| | | efgh
Run Code Online (Sandbox Code Playgroud)


Xav*_*hot 12

开始Scala 2.13case classes (这是 的实现Product)现在提供了一个productElementNames方法,该方法返回其字段名称上的迭代器。

结合Product::productIteratorwhich 提供案例类的值,我们有一种简单的方法来漂亮地打印案例类而无需反射

def pprint(obj: Any, depth: Int = 0, paramName: Option[String] = None): Unit = {

  val indent = "  " * depth
  val prettyName = paramName.fold("")(x => s"$x: ")
  val ptype = obj match { case _: Iterable[Any] => "" case obj: Product => obj.productPrefix case _ => obj.toString }

  println(s"$indent$prettyName$ptype")

  obj match {
    case seq: Iterable[Any] =>
      seq.foreach(pprint(_, depth + 1))
    case obj: Product =>
      (obj.productIterator zip obj.productElementNames)
        .foreach { case (subObj, paramName) => pprint(subObj, depth + 1, Some(paramName)) }
    case _ =>
  }
}
Run Code Online (Sandbox Code Playgroud)

对于您的特定场景:

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

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

pprint(data)
Run Code Online (Sandbox Code Playgroud)

产生:

def pprint(obj: Any, depth: Int = 0, paramName: Option[String] = None): Unit = {

  val indent = "  " * depth
  val prettyName = paramName.fold("")(x => s"$x: ")
  val ptype = obj match { case _: Iterable[Any] => "" case obj: Product => obj.productPrefix case _ => obj.toString }

  println(s"$indent$prettyName$ptype")

  obj match {
    case seq: Iterable[Any] =>
      seq.foreach(pprint(_, depth + 1))
    case obj: Product =>
      (obj.productIterator zip obj.productElementNames)
        .foreach { case (subObj, paramName) => pprint(subObj, depth + 1, Some(paramName)) }
    case _ =>
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我通过无耻地复制你的答案发布了一个[库](https://github.com/guardian/spy)。如果您介意我像这样使用您的代码,请告诉我,我将更改/删除它。 (3认同)

use*_*782 9

这是我的解决方案,它极大地改进了http://www.lihaoyi.com/PPrint/处理案例类的方式(参见https://github.com/lihaoyi/PPrint/issues/4)。

例如它打印这个: 在此处输入图片说明

对于这样的用法:

  pprint2 = pprint.copy(additionalHandlers = pprintAdditionalHandlers)

  case class Author(firstName: String, lastName: String)
  case class Book(isbn: String, author: Author)
  val b = Book("978-0486282114", Author("first", "last"))
  pprint2.pprintln(b)
Run Code Online (Sandbox Code Playgroud)

代码:

import pprint.{PPrinter, Tree, Util}
object PPrintUtils {
  // in scala 2.13 this would be even simpler/cleaner due to added product.productElementNames
  protected def caseClassToMap(cc: Product): Map[String, Any] = {
    val fieldValues = cc.productIterator.toSet
    val fields = cc.getClass.getDeclaredFields.toSeq
      .filterNot(f => f.isSynthetic || java.lang.reflect.Modifier.isStatic(f.getModifiers))
    fields.map { f =>
      f.setAccessible(true)
      f.getName -> f.get(cc)
    }.filter { case (k, v) => fieldValues.contains(v) }
      .toMap
  }

  var pprint2: PPrinter = _

  protected def pprintAdditionalHandlers: PartialFunction[Any, Tree] = {
    case x: Product =>
      val className = x.getClass.getName
      // see source code for pprint.treeify()
      val shouldNotPrettifyCaseClass = x.productArity == 0 || (x.productArity == 2 && Util.isOperator(x.productPrefix)) || className.startsWith(pprint.tuplePrefix) || className == "scala.Some"

      if (shouldNotPrettifyCaseClass)
        pprint.treeify(x)
      else {
        val fieldMap = caseClassToMap(x)
        pprint.Tree.Apply(
          x.productPrefix,
          fieldMap.iterator.flatMap { case (k, v) =>
            val prettyValue: Tree = pprintAdditionalHandlers.lift(v).getOrElse(pprint2.treeify(v))
            Seq(pprint.Tree.Infix(Tree.Literal(k), "=", prettyValue))
          }
        )
      }
  }

  pprint2 = pprint.copy(additionalHandlers = pprintAdditionalHandlers)
}

// usage
pprint2.println(SomeFancyObjectWithNestedCaseClasses(...))
Run Code Online (Sandbox Code Playgroud)


Dav*_*lla 7

使用com.lihaoyi.pprint库.

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

val data = ...

val str = pprint.tokenize(data).mkString
println(str)
Run Code Online (Sandbox Code Playgroud)

您还可以配置宽度,高度,缩进和颜色:

pprint.tokenize(data, width = 80).mkString
Run Code Online (Sandbox Code Playgroud)

文档:http://www.lihaoyi.com/PPrint/

  • 谢谢,但不幸的是它不尊重案例类字段名称。 (3认同)

Ale*_*rov 5

就像解析器组合器一样,Scala已经在标准库中包含了漂亮的打印机组合器.如果您需要解决方案"反射"或者您希望明确构建打印机,那么您在问题中并没有明确说明.(虽然你的"红利问题"暗示你可能想要"反思"解决方案)

无论如何,如果您想使用普通的Scala库开发简单漂亮的打印机,这里就是.以下代码是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)
))
Run Code Online (Sandbox Code Playgroud)

奖金问题:将打印机包装成类型类,以实现更好的可组合性.

  • 这个东西在Scala 2.11中已弃用; 请参阅https://groups.google.com/forum/#!topic/scala-language/e7CqLqlxLts (3认同)

met*_*sim 5

我发现的最好、最简洁的“开箱即用”体验是使用Kiama 漂亮的打印库。它不会在不使用其他组合器的情况下打印成员名称,但只有import org.kiama.output.PrettyPrinter._; pretty(any(data))您有一个良好的开端:

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)
Run Code Online (Sandbox Code Playgroud)

产生:

ClassDecl (
    Complex (),
    List (
        VarDecl (
            1,
            "abcd"),
        VarDecl (
            2,
            "efgh")))
Run Code Online (Sandbox Code Playgroud)

请注意,这只是最基本的示例。Kiama PrettyPrinter 是一个非常强大的库,具有一组丰富的组合器,专为智能间距、换行、嵌套和分组而设计。调整以满足您的需求非常容易。在这篇文章中,它在 SBT 中可用:

libraryDependencies += "com.googlecode.kiama" %% "kiama" % "1.8.0"
Run Code Online (Sandbox Code Playgroud)


F. *_*ely 5

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!")
  }
}
Run Code Online (Sandbox Code Playgroud)