All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalaz.Cord.scala Maven / Gradle / Ivy

package scalaz

import java.lang.StringBuilder

import scala.annotation.tailrec

/**
 * A `Cord` is a purely functional data structure for efficiently creating a
 * `String` from smaller parts, useful for printing ADTs that must write out
 * their contents into a text format.
 *
 * A `z` interpolator is available for building `String` from literals, using
 * the `Show` typeclass to populate each "hole", and a `cord` interpolator
 * for building `Cord` instances.
 *
 * If a more efficient solution is required to write a large `String` to a
 * network socket or file, consider https://github.com/scalaz/scalaz/issues/1797
 *
 * If you require a general text manipulation data structure, consider using
 * `FingerTree` or creating a custom structure to resemble that used by the
 * popular text editors:
 *
 * - https://ecc-comp.blogspot.co.uk/2015/05/a-brief-glance-at-how-5-text-editors.html
 * - https://pavelfatin.com/typing-with-pleasure/
 */
sealed abstract class Cord {
  /** LEGACY: scalaz <= 7.2 users expect toString to return the underlying String */
  override final def toString: String = shows

  /** Evaluates and stores the String represented by this, returning an equivalent Cord. */
  final def reset: Cord = Cord.Leaf(shows)

  /** Explicitly construct the output String represented by this Cord */
  final def shows: String = this match {
    case Cord.Leaf(str) => str
    case _ =>
      val sb = new StringBuilder
      Cord.unsafeAppendTo(this, sb)
      sb.toString
  }

  // Before adding any methods to this class, please consider if they can be
  // reasonably implemented with future, optimised, representations of Cord. The
  // purpose of this class is to generate Strings as fast as possible, not a
  // general text manipulation structure. For example, a previous version used
  // FingerTree, and many methods were added. But FingerTree was very
  // inefficient at creating Strings yet we could not fix these performance
  // problems because of the need for backcompatibility.

  /**
   * Strict evaluation variant of `Monoid.append` that associates to the right,
   * therefore building more optimal data structures.
   */
  final def ::(o: Cord): Cord = Cord.Branch(o, this)

  /** Strict evaluation variant of `Monoid.append`. */
  final def ++(o: Cord): Cord = Cord.Branch(this, o)
}
object Cord {
  def apply(s: String): Cord = Leaf.apply(s)
  def apply(): Cord = Leaf.Empty

  private[scalaz] final class Leaf private (
    val s: String
  ) extends Cord
  private[scalaz] object Leaf {
    val Empty: Leaf = new Leaf("")
    def apply(s: String): Leaf =
      if (s.isEmpty) Empty
      else new Leaf(s)
    def unapply(l: Leaf): Some[String] = Some(l.s)
  }

  private[scalaz] final class Branch private (
    val leftDepth: Int,
    val left: Cord,
    val right: Cord
  ) extends Cord

  // Limiting the depth of a branch ensures we don't get stack overflows, at the
  // cost of forcing some intermediate strings. We could also have used a DList
  // structure for the left legs, but it would be very inefficient.
  //
  // However, repeated monoidic appends (e.g. foldLeft and fold) produce large
  // LEFT legs that we cannot tail recurse down. Prefer foldRight (or
  // intercalate), or foldLeft with `::` when creating Cord instances for
  // collections.
  private[scalaz] object Branch {
    val max: Int = 100
    def apply(a: Cord, b: Cord): Cord = {
      // avoid constructing a Branch with empty leafs, otherwise Monoid.zero is
      // degenerate over the empty string.
      if (a eq Leaf.Empty) b
      else if (b eq Leaf.Empty) a
      else a match {
        case _: Leaf =>
          new Branch(1, a, b)
        case a: Branch =>
          val branch = new Branch(a.leftDepth + 1, a, b)
          if (a.leftDepth >= max)
            branch.reset
          else
            branch
      }
    }
    def unapply(b: Branch): Some[(Int, Cord, Cord)] = Some((b.leftDepth, b.left, b.right))
  }

  implicit val monoid: Monoid[Cord] = new Monoid[Cord] {
    def zero: Cord                         = Leaf.Empty
    def append(f1: Cord, f2: =>Cord): Cord = Branch(f1, f2)
  }

  implicit val show: Show[Cord] = Show.show(identity)
  implicit val equal: Equal[Cord] = Equal.equal((a, b) => a.shows == b.shows)

  final class CordInterpolator(private val sc: StringContext) extends AnyVal {
    def cord(args: CordInterpolator.Cords*): Cord = {
      import StringContext.processEscapes
      val strings = IList.fromSeq(sc.parts).map(s => Cord(processEscapes(s)))
      val cords   = IList.fromSeq(args).map(_.cord)
      strings.interleave(cords).foldRight(Cord())((c, acc) => monoid.append(c, acc))
    }
  }
  object CordInterpolator {
    // the interpolator takes Cords (not Cord) which allows us to restrict the
    // implicit materialisation of Cord (from Show instances) to this usecase.
    final class Cords private (val cord: Cord) extends AnyVal
    object Cords {
      implicit def trivial(c: Cord): Cords   = new Cords(c)
      implicit def mat[A: Show](a: A): Cords = new Cords(Show[A].show(a))
    }
  }

  @tailrec private def unsafeAppendTo(c: Cord, sb: StringBuilder): Unit = c match {
    case Branch(_, a, b) =>
      unsafeAppendTo_(a, sb) // not tail recursive, left legs need to be capped
      unsafeAppendTo(b, sb) // tail recursive, right legs can be arbitrarily long
    case Leaf(s) =>
      val _ = sb.append(s)
  }
  // breaks out of the tail recursion
  private[this] def unsafeAppendTo_(c: Cord, sb: StringBuilder): Unit = unsafeAppendTo(c, sb)

  type GlorifiedBrick = Cord
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy