For those of you who have ever done any Ruby development on Rails super-charged with ActiveRecord (as I recall back from 2008), you would have been blessed with the most intuitive date-arithmetic facility known to programming-man.
At that time in my life, Java was my main bread-and-butter and I longed for this simplicity… I am sure we all did.
Why it did not exist was simple: primitives such as int
and String
had these binary operators, as supported by the Java language, but of course Objects like java.util.Date
did not; to get them would obviously require a change to the language, but where would you draw the line? What would be the default java.lang.Object
behaviour in a obj1 + obj2 = ???
situation?
Then came a long Scala: read ‘…change in the language’ becomes ‘… change the language’
Writing Without Punctuation
Before getting into the main meat of this post, let’s digress for a moment to recall another cool and important feature of Scala.
We know binary operators in Scala are just single-parameter functions in disguise. So flipping that on its head, we can use single-parameter functions like operators:
"Scala" concat "Is" concat "The" concat "Bees" concat "Knees"
is exactly the same as
"Scala".concat("Is".concat("The".concat("Bees".concat("Knees"))))
Try them both in sbt console
There are some quirks and special steps to be taken if intermixing alphanumeric characters and symbols in your function names, but you get the gist.
So basically Scala allows us to remove punctuation around function invocations, when those functions are single-parameter functions; you can still remove the . (dot) in invocations of functions with multiple parameters, but the parentheses are still required (…why? Off topic!)
Now exposing binary operators for what they, it stands to reason they can be overriden… or more importantly, can be defined where once they were not.
Extension Methods a.k.a. Implicits
Extension methods are a language feature I discovered when consulting on a C# project Germany many years ago. I remember thinking how cool they were back then and still think they are awesome now in Scala.
Other languages support this feature in their own form too: Ruby has monkey-patching and JavaScript has prototype augmentation. I suppose if you are really desperate for this facility in Java, one can introduce Aspects.
Of course in Scala they are merely an application of implicits, or more accurately, the use of implicit type-conversion.
// Runnable in 'sbt console' import java.util.Date implicit class SuperfluousDate(date: Date) { def someSuperfluousFunction = date.toString } case class UnimaginableDate(date:Date) { def someUnimaginableFunction = date.toString } implicit def toTheUnimaginable(date:Date) = UnimaginableDate(date) val now = new Date now.someSuperfluousFunction now.someUnimaginableFunction
As with C# and Extension Methods, one needs to be careful with how implicit type-conversion is applied, so to preserve the semantic/conceptual/behavioural integrity of the class being extended.
Type Classes a.k.a. Implicits²
Type classes are a powerful feature that allows Scala developers to define an interface to capture the behaviour to be added to a class. That type-class can then be used to declare dependencies and expectations in other classes.
An out-of-the-box example of this is Scala’s Ordering[T]
trait: collections have both min()
and max()
functions, for example; they only work for the collection, say of type List[A]
, if there is an implementation of Ordering[A]
in implicit scope.
Note type-classes are typically defined as traits and are always parametric, that is, they are defined with a parameter type, [T]
; they typically have many - at least one, to be useful - concrete implementations that are case-objects and specify [T]
.
This is a key difference of the application of simple extension methods, which require a concrete class, as above, that is not a case-object and can work without parameter-types.
The function definitions therein also differ, where it is typical in type-classes to define the first parameter as the this object of the function.
// ...continued trait Chrono[T] { def now: T def toMillis(t: T): Long def fromMillis(millis: Long): T def -(t: T, duration: FiniteDuration): T def +(t: T, duration: FiniteDuration): T } implicit case object DateChrono extends Chrono[Date] { def now = fromMillis(System.currentTimeMillis()) def toMillis(date: Date) = date.getTime def fromMillis(millis: Long) = new Date(millis) def -(date: Date, duration: FiniteDuration): Date = new Date(date.getTime - duration.toMillis) def +(date: Date, duration: FiniteDuration): Date = new Date(date.getTime + duration.toMillis) }
As you can see, the type-class Chrono[T]
is parametric in [T]
and the concrete case-object implementation, DateChrono
, specifies [T]
to be [Date]
.
The usefulness of this is perhaps still not apparent
// ...continued import scala.concurrent.duration.{FiniteDuration, DAYS} val later = DateChrono.+(now, FiniteDuration(10, DAYS)) later.someUnimaginableFunction
Implicits * (1 + Implicits)
Now we combine the basic implicits, as used to implement extensions methods, with the advanced implicits, to define type-classes, to give us the ActiveRecord-style data-arithmetic sugary goodness
// ...continued implicit class ChronoArithmetic[T](t: T)(implicit ev: Chrono[T]) { def plus(duration: FiniteDuration) = ev.+(t, duration) def +(duration: FiniteDuration) = plus(duration) def minus(duration: FiniteDuration) = ev.-(t, duration) def -(duration: FiniteDuration) = minus(duration) } now + FiniteDuration(10, DAYS)
and more sugar
// ...continued val days20 = FiniteDuration(20, DAYS) now + days20
and yet more SUGGGAAAARRR
// ...continued import scala.concurrent.duration._ now + 30.days
How cool is that!
Perhaps there was a bit of a leap there: deciphering ChronoArithmetic
, it offers an implicit conversion of instances of type [T]
but only where there is an implementation of Chrono[T]
available in implicit scope.
It doesn’t end there…
Testing With ChronoTime
Now you may wonder how this applies to testing: have you ever been in the situation where you wanted to set when now is?
Say in a batch of time sensitive tests, by virtue of covering time sensitive production code, where you must have the test code and the production code reliably agree on when now is, down to the microsecond.
You can’t monkey around with the system time as that will break with parallelized testing… and probably break a whole bunch of other things.
Joda-Time offers a mechanism to do this with DateTimeUtils.setCurrentMillisFixed(long), made thread-safe with DateTimeUtils.setCurrentMillisProvider(DateTimeUtils.MillisProvider millisProvider) … which is cool if you are already using Joda-Time and can ensure that all past and future requests for the current time is made via DateTimeUtils.currentTimeMillis() and not the typical System.currentTimeMillis().
An old colleague of mine faced this exact problem and enhanced the implementation with a clock!
// ...continued trait Clock { def millis: Long }
Now this is essentially the same as DateTimeUtils.MillisProvider. Originally, the API was updated such that an instance of this Clock
was explicitly passed around wherever now was needed.
// ...continued... but hold off pasting this in trait Chrono[T] { def now(clock: Clock) = fromMillis(clock.millis) ... } def rightNow[T](clock: Clock)(implicit ev: Chrono[T]): T = ev.now(clock)
It seemed a shame to litter the API with the introduction of this new Clock
class, but that was easily remedied by making it implicit also.
// ...continued... go ahead and paste this in (update Chrono[T]) trait Chrono[T] { def now(implicit clock: Clock) = fromMillis(clock.millis) ... } def rightNow[T](implicit ev: Chrono[T], clock: Clock): T = ev.now
The implementation of now(...)
is now (ha) completed provided by Chrono[T]
; it is no longer an abstract method and can be removed from all type-classes, such as DateChrono
.
Notice how implicit parameters are automatically in implicit scope for invocations made inside the function body; this has the effect that implicit parameters get passed along.
Another quick digression to bring to mind another cool feature of Scala and application of it.
Have Your Slice of Cake
In Scala, it is very common to implement the Cake pattern and compose the execution environment with layers, whether that be the production execution environment or the test execution environment.
The Cake pattern is made possible by Scala’s Traits language feature.
In some ways Scala Traits are similar to Aspects in Java. That said, I have only ever seen Aspects used to augment behaviour; Scala Traits can mixin behaviour AND mixin state - the latter along with Scala’s self-type
are utilised in implementing the Cake pattern.
The Cake pattern and layering is really beneficial in that it provides an environment and object-graph that is type-checkable at compile-time; compare that to the dynamic nature of Spring-powered dependency-injection – you know if the Scala + Cake compiles, all dependencies are satisfied.
Execution Environments
Lets introduce a Clock
implementation that defers to the system time
// ...continued trait ClockProvider { implicit val clock: Clock } case object SystemClock extends Clock { def millis = { System.currentTimeMillis } } trait SystemClockProvider extends ClockProvider { implicit val clock = SystemClock }
This implementation can be used to compose an object graph in a production environment
// ...continued class Main extends SystemClockProvider { self: ClockProvider => val now: Date = rightNow[Date] } val then = new Main().now val later = then + 20.days
For testing, we can provide an alternate implementation that always returns the same value
// ...continued class Production(implicit clock: Clock) { println(clock.getClass) def get5SecondsLater = rightNow[Date] + 5.seconds } case object FixedClock extends Clock { def millis = { 0 // since Unix Epoch, 1st January 1970 } } class FixedClockTest extends FixedClockProvider { self: ClockProvider => val later5Seconds = rightNow[Date] + 5.seconds val prod = new Production assert(prod.get5SecondsLater == later5Seconds, "FAIL") } new FixedClockTest
Here’s a negative test using the SystemClock
// ...continued class SystemClockTest extends SystemClockProvider { self: ClockProvider => val later5Seconds = rightNow[Date] + 5.seconds println("Sleeping...") Thread.sleep(5000) val prod = new Production val prodLater = prod.get5SecondsLater assert(prodLater != later5Seconds, "FAIL") } new SystemClockTest
That’s about it.
Feel free to copy paste from here; there is now also a Gist including a ScalaTest spec available here
No comments:
Post a Comment