a transitional suitcase for source
With the recent announcement of Scala 2.8.0 RC1, I finally decided it was time to start transitioning some of my projects. For more motivation, as 2.8 becomes main stream I’m sure some issues are bound to pop up.
does a cat’s pur make a sound if no one is around to hear it
I picked a smaller project to start out with, because low hanging fruit usually tastes better when transitioning to the next major release of a language.
Sbt’s cross building feature makes it stupid simple to add another supported Scala version to your project by cracking open your project/build.properties file and appending a new one.
build.scala.versions=2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0.Beta1
If that wasn’t easy enough, try compiling your project across all noted versions by executing the compile action with a +.
+compile
The + applies action against all versions noted under build.scala.versions.
A wave of sbt’s magic wand and… FAIL!
[info] Building project meow 0.1.0 against Scala 2.8.0.Beta1
[info] using MeowProject with sbt 0.7.1 and Scala 2.7.7
[info]
[info] == compile ==
[info] Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[error] [snip]/meow/src/main/scala/meow/Growl.scala:23: missing arguments for method getLines in class Source;
[error] follow this method with `_' if you want to treat it as a partially applied function
[error] Error occured in an application involving default arguments.
[error] Error occured in an application involving default arguments.
[error] val bin = Source.fromInputStream(which getInputStream).getLines.mkString("").trim
[error] ^
[error] one error found
[info] == compile ==
[error] Error running compile: Compilation failed
What’s this? Source#getLines and default arguments?
I hopped over the the scala doc online for this method and things looked fine according to the documentation.
So what happened here?
I cracked open the scala 2.8.0 beta library source jar to get the definition of its Source#getLines
/** returns an iterator who returns lines (NOT including newline character(s)).
* If no separator is given, the platform-specific value "line.separator" is used.
* a line ends in \r, \n, or \r\n.
*/
def getLines(separator: String = compat.Platform.EOL): Iterator[String] =
new LineIterator(separator)
Hrm, looks like we’ve got a bit of backwards compatibility problem here. Source#getLines will compile in anything < 2.8 but will not in anything > 2.8 and Source#getLines() will compile in anything > 2.8 but won’t in anything < 2.8.
a transitioning suitcase
After some thinking, I decided it might be best to wrap Source in some new clothes that would insulate me from the cold winds of this compatibility issue until I can come up with a better solution. I came up with a Transitioning module that adds a lines method to Source and which I can add other implicit wrappers to if I find other issues.
object Transitioning {
import scala.io.Source
class TransitionalSource(src: Source) {
class LineIterator(iter: BufferedIterator[Char], separator: String) extends Iterator[String] {
require(separator.length < 3, "Line separator may be 1 or 2 characters only.")
private[this] val isNewline: Char => Boolean =
separator.length match {
case 1 => _ == separator(0)
case 2 => {
_ == separator(0) && iter.hasNext && {
val res = iter.head == separator(1) // peek ahead
if (res) { iter.next } // incr iter
res
}
}
}
private[this] val builder = new StringBuilder
private def buildingLine() = iter.next match {
case nl if(isNewline(nl)) => false
case ch => {
builder append ch
true
}
}
def hasNext = iter.hasNext
def next = {
builder.clear
while (hasNext && buildingLine()) {}
builder.toString
}
}
def lines = new LineIterator(src.buffered, compat.Platform.EOL)
}
implicit def src2transitionalSrc(src: Source) = new TransitionalSource(src)
}
To test this, let’s say we have a file called test.txt that contains the contents:
hi
there
file
of lines
As an example let’s try the following.
import Transitioning
import scala.io.Source
import java.io.FileInputStream
Source.fromInputStream(new FileInputStream("test.txt")) lines // List(hi, there, file, of lines)
Sweet! It works.
As a reconfirmation, I added this module to my project, swapped Source#getLines with Source#lines in my own source code, re-waved sbt’s magic +compile wand and… WIN!
[info] Building project meow 0.1.0 against Scala 2.8.0.Beta1
[info] using MeowProject with sbt 0.7.1 and Scala 2.7.7
[info]
[info] == compile ==
[info] Source analysis: 2 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[info] Compilation successful.
[info] Post-analysis: 24 classes.
[info] == compile ==
[success] Successful.