Wrapper around elements of Traversable, override foreach


#1

Hi. I am new to scala and I would like to get to know that language. Now I am implementing a little program that should be able to dump table of contents. A TOC can be seen as a tree, therefore I start with:

case class Tree(name: String, children: List[Tree] = List()) extends Traversable[Tree] {
  override def foreach[U](f: Tree => U): Unit = {
    f(this)
    children.foreach(_.foreach(f))
  }

  override def toString: String = name
}

Fine. I can initialize a TOC, e.g.

  val t = Tree("Introduction", List(
    Tree("Technical Background"),
    Tree("Methods", List(
      Tree("One Method"),
      Tree("Another Method"),
      Tree("Yet another Method"),
      )),
    Tree("Results"),
    Tree("Conclusion")
  ))

… and simply dump it

t.foreach(println)

But here comes the tricky part: when printing, I would like to have automated numbering and indentation. This would not be much of a problem, but I would like to seperate a) the logic of counting and b) the final output format from my tree. So I implemented a simple Counter:

case class Counter(count: List[Int] = List(1), depth: Int = 0) {
  def down: Counter = copy(count = transformLastElement(count, last => last + 1))

  def up: Counter = copy(count = transformLastElement(count, last => last - 1))

  def in: Counter = copy(count = count :+ 1, depth = depth + 1)

  def out: Counter = copy(count = count.dropRight(1), depth = depth - 1)

  private def transformLastElement(list: List[Int], f: Int => Int): List[Int] = list match {
    case Nil => Nil
    case xs :+ x => xs :+ f(x)
  }
}

and some Formatter objects:

object SimpleFormatter extends Formatter {
  def format(counter: Counter): String = f"${indent(counter)}${concatCount(counter)}"
}

object VerboseFormatter extends Formatter {
  override def format(counter: Counter): String = counter.count match {
    case _ :: Nil => f"${indent(counter)}Chapter ${concatCount(counter)}"
    case _ :: _ :: Nil => f"${indent(counter)}Section ${concatCount(counter)}"
    case _ :: _ :: _ :: Nil => f"${indent(counter)}Subsection ${concatCount(counter)}"
    case _ => f"${indent(counter)}${concatCount(counter)}"
  }
}

Apparently, I can use my counter like this:

  val f: Formatter = SimpleFormatter

  println(f.format(Counter()))
  println(f.format(Counter().in))
  println(f.format(Counter().in.in.in.in.out.out))
  println(f.format(Counter().down.down.down.in.in.in.in.out))

Now when initializing my tree, I would like to wrap some Object around it like this:

  val t = ENUMERATED(Tree("Introduction", List(
    Tree("Technical Background"),
    Tree("Methods", List(
      Tree("One Method"),
      Tree("Another Method"),
      Tree("Yet another Method"),
      )),
    Tree("Results"),
    Tree("Conclusion")
  )))

I would like to solve two problems here: first, the ENUMERATED object should automatically be wrapped around each child object of tree. Second, it should override foreach in some way such that it yields both, the current numbering (accessing a private val counter) and the current caption of my TOC. The foreach should therefore look somehow similiar to this (for trees):

  override def foreach[U](f: T => U): Unit = {
    counter.down
    f(this)
    if (children.nonEmpty) {
      counter.in
      children.foreach(_.foreach(f))
      counter.out
    }
  }

But I can’t get my head around a suitable solution. Is there any way to solve that? Yes, I know I could simply use inheritance but I like the thought that my ENUMERATED wrapper fits to any traversable in some way … I even think about generalizing it in such a way that it might count anything else than just integers, but one thing after the other :wink:

Any ideas?


#2

There’s no trivial answer, but I think the most important first point is that trying to extend the Standard Collections is an advanced technique at best – I’ve tried once or twice and given it up as an exercise in misery. I’ve never done it for real. And you’re trying to fit the round peg of your desired functionality into the very square hole of foreach().

So I would instead simplify this by writing a custom recursive method instead of foreach(), which takes the current depth and breadth and just increments those as appropriate while recursing. And pass in a function that takes that (depth, breadth) pair along with the Tree as a parameter.

That’s not the only way to do it, of course, but it’s the easiest. I think much of your pain is coming from trying to extend Traversable, and I just plain don’t recommend that…


#3

Well, I guess I kind of solved my problem. It was actually much easier than I thought. One tiny part of the problem remains though: I implement

case class Enumerator(tree: Tree, counter: Counter = Counter()) extends Traversable[Tree]
{
  override def foreach[U](f: Tree => U): Unit =
  {
    counter.down
    f(tree)
    if (tree.children.nonEmpty)
    {
      counter.in
      tree.children.foreach(_.foreach(f))
      counter.out
    }
  }
}

but when I call my foreach, the counter (which is an attribute of Enumerator) is not replaced by the new counter returned by calling either down, in or out. As counter is a val, I cannot simply replace it. But how can I solve that problem in this case? I guess I could reduce it to the following problem:

case class SimpleEnumerator(list: List[Int] = List(), var counter: Int = 1) extends Traversable[Int] {
  override def foreach[U](f: Int => U): Unit = {
    for (item <- list) {
      f(item)
      counter += 1
    }
  }
}

...

val x = SimpleEnumerator(List(10, 20, 30, 40, 50, 60))
x.foreach(item => println(f"${x.counter} $item"))

This works fine as long as counter is a var. But how can I do that using val?