Thursday, November 17, 2011

Scala FTW!

I've been working a bit with Groovy and Grails lately, and I'm finding them pretty awesome.  I figured I'd take it to the next level, and dive into Scala.

Scala has more powerful functional features than Groovy, but it seems to have kind of spotty framework support.  This turns out not to be such a bad thing.  The folks using Scala seem to care about making good solutions to things, and looking at GORM in Grails, it works fine, but it's really slow, and suffers the same problems as Hibernate.  Database access in Scala isn't so locked down yet.  There a few systems out there, so I picked on that looked interesting: ORBroker and ran with it.

My goal here was really just to get something working that could read data from a database and do something with it.

As per usual, I do testing with my EVE database, so I decided to start with something that could read a solar system and list the destinations.  Not too bad once I figured out how the ORBroker mapping worked, which took a bit of doing.  The documentation is a bit sparse, but with a bit of general ORM experience and a few years under the belt of development, I got it going.

I'm gonna put some code in here, then talk a bit more about it, and some of the fun with ORBroker:

object App extends Application {
  val ds = new PGSimpleDataSource();
  ...
  val builder = new BrokerBuilder(ds)
  FileSystemRegistrant(new java.io.File("src/main/sql/orbroker")).register(builder)
  val broker = builder.build()

  val selectDestinations = Token('selectDestinations, SolarSystemDestinationExtractor)

  val jitaDestinations = broker.readOnly() { session =>
    session.selectOne(selectDestinations, "solarSystemName"->"Jita")
  }

  def lf(session: QuerySession, y: List[SolarSystem]): List[SolarSystem] = {
    if (y.isEmpty)
      List()
    else {
      session.selectOne(selectDestinations, "solarSystemName"->y.head.name) match {
        case Some(p) => lf(session, y.tail).filterNot {p.destination.toList.contains _} ::: p.destination.toList
        case None => List()
      }
    }
  }

  broker.readOnly() { session =>
    lf(session, jitaDestinations match {
      case Some(p) => p.destination.toList
      case None => List()
    }).foreach(y => println(y.name))
  }
}

The code uses a SQL query defined in selectDestinations.sql to pull back a list of destination solar systems given a solar system.  This isn't quite as easy as it might first appear because in my incarnation of the EVE database, solar systems aren't joined directly, but via a Stargate relation.  Because ORBroker doesn't do ORM, it maps queries to objects via the extractors, this is much saner than it would be with an ORM, even if it is a bit more verbose.

We grab a list of destinations from the initial query, which returns a list of SolarSystem objects.  Then we send that list through the lf closure to retrieve the destinations for those destinations.  We are getting the list of systems reachable by making two jumps from the start point - which rather typically is Jita.  The cool thing that becomes easy with a functional language is the list construction piece.

It's all very nice to implement list iteration by recursion, it's first year computer science stuff in the UK, but I still love it, and, because of list comprehensions and filters, we can go one better with only a tiny bit more code; we can filter the list and eliminate systems that have already been added, or more accurately that are going to be added.  Now we're talking something vaguely useful.

I haven't tried the ternary operator yet in Scala, not sure if it exists, but if it did, the closure would be one line long, if it wasn't for the case situation.

This is one place where I greatly prefer Groovy's solution to the problem of null results, maybe I'm missing something Scala can do here because I only just cracked the book yesterday, but so far, this is kinda annoying.  To separate null returns from valid returns, we have to disambiguate an Option[] group.  There seem to be a couple of ways to do this, but some of then aren't appropriate here, and well, this worked.  The match/case operator pairs do a match on the Option group, and allow us to do something in both null and non-null situations without an ugly if operator, and matching can do a whole lot more, but for this situation, it seems a bit overkill.  The Groovy suffix ? does as much with less fuss.

We filter the return to we only concatenate elements in the return list that don't exist in the destination list, and because we are applying it recursively, it always filters to the latest concatenation, and we magically get the right result.  List comprehensions FTW.

The interesting _ parameter kind of feels a bit like Perl's $_, so I kinda have to hate it.  Honestly though, it beats Groovy's it parameter which is just kinda lame.

The ORBroker is supposed to be able to build Token objects through the Tokens thingie, but I haven't got it to work yet.

No comments:

Post a Comment