Strong Maps and Lazy Properties in Kotlin
The Kotlin language has a large number of features that can be classed as syntactic sugar — they make the language just a bit nicer to use and read, mostly by eliminating repetitive code.
A common form of repetitive code comes when you want to work with Maps. Maps will almost always come up in some form or another, often when communicating with external systems (e.g. JSON data coming in). They’re popular for their flexibility and use in inter-language operations— keys can be strings and data can be any value. Every mainstream language has maps (some call them Dictionaries, Tables, Objects, Associative Arrays, Hashes — they’re all the same thing).
The same thing that makes them useful, though, makes them less than ideal for working in Kotlin. Maps don’t give your IDE or compiler any help with types. When you write
foo["bar"] you have no idea what type “bar” is, or if the property even exists in the map.
This leads to a lot of boilerplate code and repetition, especially when working on software that communicates via JSON. At each end of the communication we end up with a class being defined with all of the same properties as the map keys, and methods to convert it to and from a map. For example, if we have a class to hold 2D coordinates we might end up with something like this:
That’s a lot of code that is going to be repeated for every object. It’s boring to write (and therefore error prone). It’s boring to write tests for so developers skip them. You have to write it twice — once in each language for the two sides of the communication. It also loses information. If we change the code so that the maps now include an ID number for the point, this code will still succeed, but will quietly discard that ID number when
fromMap is called.
Kotlin provides a nice compromise between a map and a class using a feature called delegates. Delegates in general let you specify for a property that some other object is going to handle it — they delegate. A simple Point2D using delegates could look like this:
Our constructor now takes a (mutable) map as its only parameter — or fills in a default map. We set x and y as variable properties, type
Double nothing unusual there, and then we use the
by keyword and use the
map property as the parameter. This tells Kotlin that whenever a caller tries to get or set the value of x or y, instead, let the map deal with it. So the map’s content and the object are always aligned.
As well as dealing with JSON and dynamic, this allows us to effectively build “map interfaces”. For example, if we have an
Event type, that always have a type, but the exact details depend on that type, we can model it with a delegated class so that the map could be sent to and from external systems:
We can now load events and directly interact with the
type property, and use that to determine what further processing we do.
Delegates can cover more than just maps. Another common use of them is for Lazy properties. Lazy properties are those that are not calculated until the first time they’re accessed, and then that calculation is saved as the result.
For example, if we had a circle class, and we didn’t always use the area, we might want to skip calculating it until we need it. But once calculated it’s never going to change. So we’d set up a lazy property like so:
The first time we try to get the area of a circle the lazy lambda will be called and calculate it. After that, we’ll use the same result on each access.
Delegated properties have many other uses. It’s possible to define your own delegates if you need more than those provided by the language. I’ve found map and lazy to cover 99% of my usage of them though.