Static doesn’t mean giving up open classes

In Groovy (and other languages that support meta-programming), one can extend the functionality of existing classes to provide powerful functionality. Here’s a simple example in Groovy.

Integer.metaClass.daysAfterToday = { ->
  Calendar today = Calendar.instance
  today.add(Calendar.DAY_OF_MONTH, delegate)
  today.time
}

// now can use this syntax anywhere after the metaClass modification
5.daysAfterToday()

The above code adds a new method, getDaysAfterToday to an Integer. As you can see, it will return the date that is 5 days after today (hence the name).

Something similar can be accomplished in Scala via implicit conversions. Here’s the code.

class DateHelper(days:Int) {
  def daysAfterToday():Date = {
    val today = Calendar.getInstance
    today.add(Calendar.DAY_OF_MONTH, days)
    today.getTime
  }
}
implicit def Int2DateHelper(days: Int) = new DateHelper(days)

// now we can use this syntax anywhere the implicit conversion is defined
5.daysAfterToday

So, what does the Scala code do?

  • Defines a class that has a constructor that takes an Int. This will also create a property called days
  • Define a method daysAfterToday that returns a Date that is ‘days’ days after today.

The line beginning with implicit is the interesting one. It defines a conversion from Int to DateHelper.

So, the compiler will then see the 5.daysAfterToday. As 5 is an Int, it will try and find a method daysAfterToday on Int. Obviously it doesn’t exist. So, it will then start examining all implicit definitions that are in scope (as implicits have a lexical scope. They would generally be associated with a package, and imported accordingly).

The compiler will eventually ‘find’ the Int2DateHelper implicit definition, and modify the 5 to be a DateHelper object. Then when that line of code is executed, it will actually be calling daysAfterToday on the DateHelper object.

So, the same syntactic sugar can be used when defining a DSL or any other type of coding. Of course, you also now have the compiler doing some testing for you, as it will complain if it can’t find an implicit conversion.

In contrast, it’s up to the developer to test their code thoroughly to ensure that all places where daysAfterToday is used actually work correctly. For example, if I wrote the following in Groovy

Long someLong = 10:
someLong.daysAfterToday

The dev wouldn’t find out until run time (or hopefully in the test the dev wrote around their code) that daysAfterToday is not defined for a Long.

If a developer does something similar in Scala, the compiler will tell them that ‘daysAfterToday is not a member of Long’, and no explicit test is required.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply