asoft
sea

~ tiny vessels of code slowly floating out to sea

Powered by a motorboat called the S.S.Tumblr

3 years ago

@12:10am

1 note
Comments

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.
  1. coderspiel reblogged this from asoftsea
  2. asoftsea posted this
blog comments powered by Disqus