Sunday, February 1, 2015

Scala revisited: so much shorter!

I didn't even realize that I wrote about Scala before, until I logged into my blog and saw that it's apparently one of my most popular posts. My earlier flirt with Scala ebbed out rather quickly, and I think the reasons were twofold.

Firstly, back in 2010, IDE integration for Scala wasn't that great. While there's still much room for improvement, the situation is much better now. I only tried the Eclipse Scala IDE, and from what I heard, IntelliJ integration is nice as well.

The second reason is simply my lack of understanding of functional programming. I would say that I had a good concept of how functional programming is supposed to work back then, but I was surely lacking practice. My interest recently led me to a FP/Haskell class at university, and while it was only introductory level, I think it helped a lot.

As far as I know, the language itself has evolved quite a bit as well. There was a big cleanup of the collection API, and much more - it has been five years, after all. How time flies!

Anyway, I'm in love with that language! In the last three weeks, I converted Harmonic (and other projects) from Java to Scala - and from Maven to Gradle, by the way - and got from 1027 to 870 lines of code, about 15% less. Granted, that's not exclusively the language switch but also a partial redesign - hence the three weeks of work - but I don't think that lessens the message. It gets more impressive when I add that the Scala code includes about 300 lines of freshly written unit tests!

Scala gets rid of a lot of boilerplate code that bloats the line count of Java. For example, a typical property takes 7 lines of Java:

private int property;
public void setProperty(int property) {
    this.property = property;
}
public int getProperty() {
    return this.property;
}

Converted to Scala idioms, the code would look like this:

private var _property: Int = _
def property = _property
def property_=(property: Int) = _property = property

Well, that is a little shorter. Also note how def property omits the parentheses, and that methods ending in _= are called as if they were assignments. Now compare incrementing that property:

//Java
x.setProperty(x.getProperty() + 1);
//Scala
x.property = x.property + 1


That's just one example, I could go on: Instead of static members, Scala has objects, but they are actually singletons and can extend other classes and traits. Traits are like interfaces, can have method implementations like in Java 8, but are actually inherited as mixins. Case classes allow pattern matching, comparing that to switch is almost insulting given how much more flexible pattern matching is. Implicits allow the compiler to do even more of the work for you: implicit conversions are like user-defined autoboxing (another insult!), implicit parameters save you typing when a context variable is needed over and over in method calls. Need to pass a Game to every other method? Make it implicit!

I bet there are many more gems hidden in Scala, but I only had three weeks to look so far :P

Okay, one more thing. I won't even describe it, just show you code. This is how unit tests can look in Scala:

class EngineSpec extends FlatSpec with Matchers with GivenWhenThen {
  behavior of "An engine"

  it should "handle branches properly" in {
    Given("an engine")
    implicit val engine = new Engine()

    And("a PolybufIO for a custom action")
    engine.addIO(MyAction)

    {
      When("creating a branch at HEAD")
      val head = engine.head
      val branch = engine.Branches.createBranchHere("branch1")

      Then("the branch's tip should be the head")
      branch.tip should be(head)

      When("executing an action")
      engine.execute(new MyAction())

      Then("the branch's tip should still be the old head")
      branch.tip should be(head)
    }

    {
      When("creating another branch at HEAD")
      val head = engine.head
      val branch = engine.Branches.createBranchHere("branch2")

      Then("the branch's tip should be the head")
      branch.tip should be(head)

      When("making that branch current")
      engine.currentBranch = branch

      And("executing an action")
      engine.execute(new MyAction())

      Then("the branch's tip should be the new head")
      branch.tip should be(engine.head)

      When("moving that branch's tip")
      branch.tip = head

      Then("the new head should be the branch's tip")
      engine.head should be(branch.tip)
    }
  }
}

No comments: