At Applover we strive to always have the newest and best solutions in our toolset. We keep a close eye on promising libraries and frameworks that can help us with our software development. That’s why we endeavour to be among the first to adopt novel products in our daily work. Sometimes, however, being on the cutting edge is not good enough. In some cases, it is preferred to be the ones to push the limits and invent new technologies. Even though the competition is fierce and there are many talented programmers working to get their libraries out, we believe it is still worth it to pursue such goals. The best way to do it is to start an open-source initiative and spread it among the developers.

 

Open-source at Applover

We have found that allowing our programmers to take a break once in a while and focus on a side project is beneficial in multiple ways. Firstly, it is greatly motivating to be able to work on something important to oneself when between tasks or during a slow sprint. It can help someone get out of the rut after being involved in one product for too long. On top of that, contributing to open-source initiatives enables your employees to direct their self-development in the desired direction. Instead of being told what to learn next, they can choose to practice the stuff they will benefit from the most. Seems like a win-win situation. Finally, some people tend to enjoy the feeling of giving back to community. Many developers enjoy the satisfaction of developing a tool used by their peers so the opportunity to create an effective library seems inviting.

 

Let’s get down to the nitty-gritty

As we all know, the C-level executives and management are usually too busy to read more than a couple of paragraphs of tech articles. Therefore, if you’re still reading, I’m going to assume that you’re a developer. Let’s get to the meaty part of the post and talk about why and how we’ve developed ThreeTenDsl — our newest open-source library. To begin with, let’s discuss the term DSL — or Domain Specific Language — itself. One of the definitions you might find is

 

DSL — A small programming language specifically designed to communicate solutions for a particular domain of problems.

 

In simpler terms, you might think of it as a set of syntax features and tools aimed to make the code more readable in a given context. In case of ThreeTenDsl we set out to provide a natural language to define dates, times of day, durations, etc. There aren’t many situations while writing production code where you would use date/time literals so the most prominent use case would be in tests.

 

As most Android applications tend to avoid the Calendar API for handling dates, a decision was made to design the DSL to support ThreeTenABP library by Jack Wharton. It is an Android version of the ThreeTenBP Java library created to backport the new Java 8 Time API (JSR-310) to the older JDK versions. The goal was to enable a natural, human-readable, flexible way to instantiate Time-related objects. This application of DSLs is often referred to as type-safe builders

 

LocalDateTime.of(2000, 1, 31, 12, 40, 59)
//transforms into
dateTime { 31 January 2000 at 12 h 40 m 59 }

//it is flexible and readable
period { 3.months }
durationOrNull { 1 years 0 months 0 days 12 h 40 m 20 }
timeOrNull { 16 h 40 }?.let {
            dateTime { it on date { 2 December 1990 } }
}

 

 

The implementation

To make our DSL work within Kotlin we used its functional features and syntactic sugar. Before we dive into the implementation itself, let’s recap some Kotlin-specific constructs referenced further.

 

First off, Kotlin ships with a great solution to Java’s dreaded utility classes with the concept of extensions. Extension functions and properties allow us to define expanded functionalities to existing classes. Consider the following snippet.

 

fun Int.h(m: Int) = LocalTime.of(this, m)

 

This way we declare a function that can be called on an Int value like so 12.h(42). The exact mechanism that describes its inner workings is out of scope of the article but what matters is that with Kotlin we can easily create new ways to interact between values.

 

Moreover, a slight modification to the code above can mitigate the need of the dot (.) operator and parentheses in some functions. We can transform 12.h(42) into 12 h 42 simply by marking the function as infix as shown below.

 

infix fun Int.h(m: Int) = LocalTime.of(this, m)

 

Last concept we need to have a grasp on to fully understand the library is the lambda notation in Kotlin. In this language functions are first-class citizens, in other words they can be passed as a parameter to another function.

 

fun time(build: (TimeDsl) -> LocalTime)

 

This code defines a function called time that accepts a single parameter named build. Build is a function that accepts an argument of type TimeDsl and returns a LocalTime value. It turns out we can also pass extension functions as parameters. More strictly known as function types with receiver, they allow us to build type-safe builders which is exactly what we’re looking for.

 

fun time(build: TimeDsl.() -> LocalTime)

 

OK, so with that taken care of, let’s go through a typical way to implement a DSL in Kotlin with ThreeTenDsl as an example. There are three things that make it work. The contracts, the builder functions, and the singleton implementations.

The contracts

In order to assure type-safety of our builders, we need to define a set of interfaces that list the operations possible within a context.

 

interface TimeDsl {
   infix fun LocalTime.m(s: Int): LocalTime
   infix fun Int.h(m: Int): LocalTime
}

 

This way we make sure that the users can’t use our DSL methods outside of the time{ } blocks. Using interfaces for this task allows us to use inheritance to avoid repeating ourselves within related contexts. As a case in point, let’s look at DateTimeDsl. LocalDateTime is a class that combines both LocalDate and LocalTime information into a single type.

 

interface DateTimeDsl : TimeDsl, DateDsl {
   //some methods
}

 

By making the DateTimeDsl extend the other two contracts we make sure that we don’t write duplicate code while allowing DateTimeDsl to use TimeDsl’s methods.

The builders

The previously mentioned contexts are an intuitive way to think about builder functions such as the following one.

 

fun time(build: TimeDsl.() -> LocalTime) = InstantDsl.build()

 

The time function simply lets us execute a set of TimeDsl methods and returns their result. Because Kotlin supports writing the single functional parameter in just curly braces we can use the time method to create blocks utilising the members from the given contract such as the one below.

 

time({ 15 h 40 m 12 })
//equivalent to
time { 15 h 40 m 12 }

 

For convenience ThreeTenDsl ships with a number of these builder functions for different purposes. We can create time, date, dateTime, duration and period objects. There is also the possibility to explicitly get null value if the literal is incorrect with the orNull functions, e.g. timeOrNull.

The singletons

The last missing piece of the puzzle is the meaning of InstantDsl in the previous snippet. Because we call the build function on it, we know it has to implement the TimeDsl interface. That’s where the actual implementation of the methods come into play.

 

internal object InstantDsl : Contract.DateTimeDsl {
   override infix fun LocalTime.m(s: Int) = withSecond(s)
   override infix fun Int.h(m: Int) = LocalTime.of(this, m)
   //some more methods
}

 

To actually tell the library how to create the objects, we need to implement the methods defined in our contracts. We’ve decided to go for the Singleton pattern and define a set of objects that help us build the instances described with our DSL. They are used as a stateless environment ensuring the proper context for the DSL methods.

 

Recap

 

To quickly sum up the implementation of ThreeTenDsl, let’s walk through what happens when you call, e.g.

 

date { 14 September 2019 }

 

date is a function that returns a LocalDate and accepts another function as an argument. The argument function can use the methods defined in the DateDsl contract and returns a LocalDate object. September is an extension function of an Int that needs another Int as a parameter. It is marked as infix, so we can call it without the dot operator and parentheses.

 

Depending on how accustomed you are with Kotlin’s functional features, the structure of the library might seem more or less understandable. I recommend spending some time playing with the DSL in order to improve your Kotlin fluency.

 

More importantly though, if there’s one thing I want you to take away from this post is to give open-source side projects a chance. If you’re a developer, try talking to your management to give you some time to self-educate this way. It is the easiest and most fun way to deepen your understanding of the tools at your disposal. And who knows, maybe you’ll come up with the new Dagger, Retrofit or RxJava!

___
Julian Jurec
The project described in this post can be found on our Github