6

I really like Tie::File, which allows you to tie an array to a file's lines. You can modify the array in any way, and when you're done with it, you untie it, and the file's content modifies accordingly.

I'd like to reimplement such behaviour in Scala, and this is what I have so far:

class TiedBuffer(val file:File) extends ArrayBuffer[String] {

  tieFile

  def untie = {
      val writer = new PrintStream(new FileOutputStream(file))
      this.foreach(e => writer.println(e))
      writer.close
      this
  }

  private def tieFile = this ++= scala.io.Source.fromFile(file).getLines()
}

However, the "operators" defined on the ArrayBuffer return various classes, different than my own, for example:

println((new TiedBuffer(somefile) +: "line0").getClass)

gives me a immutable.Vector. I could limit the class to a very small set of predefined methods, but I thought it would be nice if I could offer all of them ( foreach/map/... ).

What should I inherit from, or how should I approach this problem so that I have a fluid array-like interface, which allows me to modify a file's contents?

BOUNTY: to win the bounty, can you show a working example that makes use of CanBuildFrom to accomplish this task?

Senthess
  • 17,020
  • 5
  • 23
  • 28
  • Just so you know, Tie::File is actually more complex than you show here. It doesn't simply read the file when you tie and write it when you untie. It reads and writes the file as you work with the array (with some optimizations to improve performance). But even a basic implementation like this would still be useful. – cjm Jul 14 '11 at 23:06
  • @cjm, I know, of course. Tie::File is much more complex than what I show here. But I wanted the people that are reading this to have a clear understanding of what happens and why this module is useful. – Senthess Jul 15 '11 at 06:15

2 Answers2

4

The methods ending with colon are right associative so in your example you are calling +: of String with a TiedBuffer as parameter. If you want to test +: from ArrayBuffer you can do:

println((new TiedBuffer(somefile).+:("line0")).getClass)

or

println(("line0" +: new TiedBuffer(somefile)).getClass)

EDIT

I missed the point in your question, see John's answer to return TiedBuffer objects instead of ArrayBuffer.

EDIT2

Here is an example with CanBuildFrom. You will have to call tie manually though to prevent the file to be tied every time the builder create a new TiedBuffer instance. There is still a lot of room for improvement, for instance ++ will not work but it should get you started.

import collection.generic.CanBuildFrom
import collection.mutable._
import java.io.{PrintStream, FileOutputStream, File}

class TiedBuffer(val file: File) extends ArrayBuffer[String]
                                 with BufferLike[String, TiedBuffer]
                                 with IndexedSeqOptimized[String, TiedBuffer] {

  def tie = {
    clear
    this ++= scala.io.Source.fromFile(file).getLines()
  }

  def untie = {
    val writer = new PrintStream(new FileOutputStream(file))
    this.foreach(e => writer.println(e))
    writer.close
    this
  }

  override def newBuilder: Builder[String, TiedBuffer] =
    new ArrayBuffer mapResult {
      x: Seq[String] => (new TiedBuffer(file) ++= x)
    }
}

object TiedBuffer {
  implicit def canBuildFrom: CanBuildFrom[TiedBuffer, String, TiedBuffer] =
    new CanBuildFrom[TiedBuffer, String, TiedBuffer] {
      def apply(): Builder[String, TiedBuffer] =
        throw new RuntimeException("Cannot create a new TiedBuffer from scratch")

      def apply(from: TiedBuffer): Builder[String, TiedBuffer] = from.newBuilder
    }
}
Community
  • 1
  • 1
Mark Jayxcela
  • 985
  • 1
  • 7
  • 17
4

Extending existing collection requires defining a builder in a companion object such as

object TiedBuffer {
  implict def canBuildFrom[T] = new CanBuildFrom[TiedBuffer[T],T,TiedBuffer[T]] { ... }
}

This is fully explained here:

http://www.scala-lang.org/docu/files/collections-api/collections-impl.html

As noted by Marx Jayxcela, the reason you are getting a Vector is that you are using a right associative operators, otherwise an implicit builder would be selected and you would get an ArrayBuffer

John McCrae
  • 487
  • 2
  • 5
  • 12