When I was learning to use Ruby on Rails, I was impressed by how the framework made expressing units of time so readble: "5.minutes" or "2.weeks". This was achieved by adding methods to the Numeric class, using Ruby's Open Classes.
As a developer new to Scala, I thought it would be an interesting exercise to implement this feature using The Scala Way: implicit conversions. I'm using the Java Time and Money Library which demonstrates the principles from the Domain-Driven Design book. I wrote the library test-first using the expressive ScalaCheck testing framework, but have yet to use the library in a real application.
I'm posting my work so far in case:
I've found the Scala community to be a very helpful and smart bunch of folks - thank you for your advice!
package com.danielwellman.timeandmoneysugar
import com.domainlanguage.time.Duration
object RichDurationUnits {
implicit def intToRichDurationUnits(value: Int) = new RichDurationUnits(value)
}
class RichDurationUnits(value: Int) {
def milliseconds = Duration.milliseconds(value)
def seconds = Duration.seconds(value)
def minutes = Duration.minutes(value)
def hours = Duration.hours(value)
def days = Duration.days(value)
def weeks = Duration.weeks(value)
def months = Duration.months(value)
def quarters = Duration.quarters(value)
def years = Duration.years(value)
}
object RichDuration {
implicit def intToRichDuration(value: Duration) = new RichDuration(value)
}
class RichDuration(value: Duration) {
/**
* Add two RichDurations using Duration's plus method.
*
* Note that it is a runtime error to add two Durations of unequal base units,
* such as seconds and months. Since Duration itself does not use types to
* guard against this problem, this library doesn't either.
*/
def +(other: Duration) = value.plus(other)
}
Here's an example of using the enhancements to Integers:
scala> import com.danielwellman.timeandmoneysugar._ import com.danielwellman.timeandmoneysugar._ scala> import RichDurationUnits._ import RichDurationUnits._ scala> 5.minutes res0: com.domainlanguage.time.Duration = 5 minutes scala> 2 weeks res1: com.domainlanguage.time.Duration = 14 days
Note also the example of "2 weeks" doesn't use a period at all, since the Scala compiler will try to insert a period for you.
Here's an example of using the + operator with Durations as well as the sugar for Integers:
scala> import RichDuration._ import RichDuration._ scala> 2.days + 5.days res2: com.domainlanguage.time.Duration = 7 days scala> 2.minutes + 2.weeks res3: com.domainlanguage.time.Duration = 14 days, 2 minutes scala> 2.minutes + 2.months java.lang.IllegalArgumentException: 2 months is not convertible to: 2 minutes at com.domainlanguage.time.Duration.assertNotConvertible(Unknown Source) at com.domainlanguage.time.Duration.plus(Unknown Source) at com.danielwellman.timeandmoneysugar.RichDuration.$plus(RichDurationUnits.scala:37) at .( :11) at . ( ) at RequestResult$. ( :3) ...
The last addition exposes a feature of the Time And Money library; you can't sensibly add two minutes and two months. I think it should be possible to use the Scala type system to prevent addition of two unrelated units, but haven't tried to do that yet.
I have found that using spaces can lead to some confusion. For example:
scala> 5 minutes + 2 minutes:11: error: timeandmoneysugar.this.RichDurationUnits.intToRichDurationUnits(5).minutes of type com.domainlanguage.time.Duration does not take parameters 5 minutes + 2 minutes ^
Here I believe the compiler is trying to parse the "+ 2 minutes" expression as an argument to the "minutes" method of RichDuration. In this case, you need to wrap some of the arguments to give the compiler a hint, like so:
scala> (5 minutes) + (2 minutes) res8: com.domainlanguage.time.Duration = 7 minutes
Here are some articles I found useful while learning about implicit conversions: