Kotlin Explained: Custom Range Expressions
Traversing through dates using Kotlin range expressions
In order to make the syntax for loop iteration and control flow statements safe and natural to read, Kotlin standard library provides a concept of range. In this post, we are going to explore how to use range expressions in action. First, we are going to discover built-in range implementations for the integral types like Char
, Int
or Long
. Next, we are going to implement a custom progression for the LocalDate
class. Let’s get started 🚀!
Iterating through primitive types
The idea of a range itself can be seen as an abstract data type that models a closed scope of values or a set of objects that can be iterated through, in a concise way. An interface from kotlin.ranges package called ClosedRange
is a base declaration that models the idea of the range. It stores handle to first and last elements of the range and also provides contains(value: T): Boolean
and isEmpty(): Boolean
functions that check two conditions: if the given value belongs to the range and if the range is empty. There are built-in implementations of ranges for the integral primitive types, like Int
, Long
and Char
. To define a range for the primitive type we use rangeTo()
function. Let’s take a look at the following example 👇🏽:
The declaration above defines a range of integers of values from 0 to 1000. The rangeTo()
function has also its very own operator equivalent ..
that allows to declare a range in a more natural way 🦊:
Under the hood, the IntRange
implementation contains IntProgression
class that provides Iterator interface implementation required for iteration. Here is an example of using the progression of integers with the for loop:
As a result of executing the code above we would get the following values printed to the console:
0
100
200
…
1000
We could also achieve the same result implementing the iteration using while loop in the more imperative manner (but we live in the 21st century and we won’t 😛):
Traversing through non-primitive types
It turns out it’s also quite easy to implement a custom progression for any type implementing Comparable
interface. In this part, we will explore how to create a progression of LocalDate
type objects and discover how traverse dates the easy way.
To accomplish this task, first, we need to get familiar with the ClosedRange
and Iterator
interfaces. We will need to implement them in order to declare a progression for LocalDate
type.
The ClosedRange
interface exposes the minimum and maximum values of the range. It also provides contains(value: T): Boolean
and isEmpty(): Boolean
functions implementation that checks if the given value belongs to the range and if the range is empty. On the other hand, the Iterator
interface just provides information about the next value and its availability, as follows:
Let’s start with implementing the Iterator
instance for the LocalDate
type. We are going to create a custom class called DateIterator
that implements Iterator<LocalDate>
interface.
The DateIterator
class has 3 properties:currentDate
, endDateInclusive
and stepDays
. Inside the next()
function, we are returning the currentDate
value and updating it to the date value using a given stepDays
property interval.
Now, let’s move forward and implement the progression for LocalDate
type. We are going to create a new class called DateProgression
which is going to implement Iterable<LocalDate>
and ClosedRange<LocalDate>
interfaces.
The DateProgression
class merges functionalities declared by both Iterable<LocalDate>
and ClosedRange<LocalDate>
interfaces. To allow the iteration we need to provide an instance of DateIterator
class as the Iterator value required by Iterable
interface. We also need to override start
and endInclusive
properties of ClosedRange
interface directly in the constructor. There is also stepDays
property with a default value 1 assigned. As you can see, every time we are invoking the step function, a new instance of DateProgression
class is being created.
The last step needed to use LocalDate
type in for of range expression is to declare a custom rangeTo
operator for the LocalDate
class.
That’s it! 🎊Now, we can work with LocalDate
type using range expressions. Let’s see how to use our implementation in action. In the example below, we will use our rangeTo
operator declaration to iterate through the given dates range:
As a result, the lines of code in the example above would print the next dates with a week-long interval, as follows:
WEDNESDAY 2020–01–01
WEDNESDAY 2020–01–08
…
WEDNESDAY 2020–12–30
There is more…
Thanks to implementing theDateProgression
class we have managed to achieve an effective way of iterating through LocalDate
instances. But that’s not all. Our implementation provides a neat way of using ranges of LocalDate
objects in flow control conditions like this:
Another powerful feature of Kotlin that we have used in the LocalDate
iteration example is operator overloading possibility. It allows to replace functions calls with a concise syntax and helps to write code that is less boilerplate and natural to read at the same time 😀.
Kotlin Explained
The articles of the Kotlin Explained series, are focusing on the Kotlin language features and concepts which makes the daily programming useful.
Is there something specific you’d like to read in Kotlin Explained series? Write your idea in the comments and stay tuned!
If you’ve found this article useful please show it your love by clicking some claps 👏 on the left-hand side 👈. This way you can let me know my work is important for you. It motivates to write more. And don’t forget to follow kotlin-arsenal publication to be up-to-date. Thanks!