Kotlin DSLs demystified

If you want to write a DSL, or you’re just plain curious, this article will explore what DSLs are, why you would write one, and how to get started.

Mihaly

Android Engineer
Mihaly has been working on Web Applications written in PHP and all connected technologies. Developed Java Swing applications, Enterprise web applications, and currently developing Android apps.
My personal goal is to learn all I can about the technologies I work with and continuously improve my programming skills.

Latest posts by Mihaly

GPLs

Or General Purpose Languages are what we as programmers are used to dealing with on a daily basis. They can be object oriented, functional and imperative. The usage or the domain they can operate on is vast. To rephrase: we can come up with a set of instructions in any general purpose language that can solve almost any problem (I’m saying almost because you probably can’t write a piece of software to end world hunger, although it would be nice).

To name a few GPLs: Kotlin, Java, PHP, C, Hodor (yes there’s a programming language called Hodor)

DSLs

Or Domain Specific Languages are not so versatile, they also have a syntax but the domain of the problems they can solve is restricted to something more specific. This restriction helps making the syntax clearer, more focused, easier to learn, offers less room for errors and is optimised for that specific domain. If we think about a Chef’s Knife (DSL) and a Swiss Army Knife (GPL) both are good at cutting something, the Swiss army knife will be good at a lot of things besides cutting, but when you want to cut a raw stake it’s much easier with the Chef’s knife.

A widely popular DSL that most software engineers will know about is SQL, but there are many.

Kotlin DSLs

You might ask yourself: how can you say it’s a DSL when it’s written in Kotlin? A DSL should be a language. It’s in the name.

Normally I would agree, but as someone (I don’t remember now) from a popular English dictionary once said something along these lines: “the definition of a word will evolve and be updated in the dictionary based on how the majority of the population is using it”.

So when we say Kotlin DSL or we refer to a DSL within a language what we usually mean is that we have a construct that limits our choices based on a specific domain or context while still offering the syntax highlighting and code completion we’re so much in love with in our IDEs.

You might be wondering why you would write a DSL. There’s a big chance you are using more than a few DSLs but didn’t realise they were called that way.

I’m going to mention two main reasons you should write a DSL instead of relying on old school methods:

  1. The code is easier to understand if you have domain knowledge (sometimes it’s 0 coding skill).
  2. Modifying the code becomes easy since the compiler does the validation for you.

Kotlin Features

Before we dive into how we can make a DSL, I want to introduce you to some features of the Kotlin language that makes it possible for developers to write them.

Function references, higher order functions, lambdas

You always hear about certain programming languages that: “functions are first class citizens”. But what does this actually mean? It all boils down to the fact that you can pass a function by reference and then invoke it.

As you can see in the example above, we’re assigning a function to a val named myFunction and invoking it. The other term used constantly is referred to as “lambda,” meaning it is nothing else than the declaration of a function inline (not to be confused with Kotlin’s inline keyword). In the example above the right hand of the assignment is called a lambda. 

When you hear the expression “higher order function” you may know exactly what it’s referring to, but I’ve seen many people thinking about it like some kind of programming black magic. If you’re one of the latter, just think of it this way: if you can pass a function as a reference and assign it to a variable, it also means you can pass it as a function parameter. Functions that have parameters which take functions, are called higher order functions. There’s no magic, no tricks – just simple functionalities.

In Kotlin a higher order function looks like this:

As referenced, we call out the higher order function with a lambda directly. In Kotlin, if the last parameter is a lambda we can move it outside of the parentheses and if there’s no parameters left we can remove them entirely, which gives us the nice syntax on line 8 in the example.

The concepts discussed so far are present in most of the object oriented languages, some of them introduced later, and the others shipped with the first language specification. But, Kotlin has a special feature called “function receiver” which ads an interesting syntactic sugar to the mix.

Function receivers

It helps to first understand what we mean by a “receiver” in a more traditional context. Let’s review a simple class with a function.

There’s nothing special about the class above, but if we’re talking about the function test we can say that it can be called on a Foo object. This means that the function’s receiver is Foo, which only means that the function has a couple of consequences

  1. You can access members of Foo directly (without specifying an instance).
  2. The keyword refers to the instance the method was called on.

These things are available in most OO languages, but Kotlin takes it to the next level. Separating the receiver from the class declaration allows the user to declare a function that has a certain receiver outside of the class (e.g. extension function).

Normally, you specify the type of a function reference like: (A) -> B , where A is the parameter type and B is the return type. To specify the receiver you prefix that notation with a dot: A.(B) -> C .

Let’s take a closer look at the example. We have a simple class Foo with a final member bar. We have an extension function called test that returns a String. Our higher order function takes one parameter that’s a function. This function needs to have Foo as a receiver. Inside the method we call this function on a Foo instance.

Our main starts to look interesting: we call our higher order function with a lambda. Because of Kotlin’s type inference, inside this lambda we have access to the members of Foo and call them directly.

I recommend reading more about functions, there’s all sorts of fun things you can do with them!

A tangential, but related story about dragons

I like dragons – all sorts! They are ancient, magical and majestic creatures. So, let’s try to build a DSL for a data-structure around that idea.

Let’s say we’re building a village. In this village there will be huts and in those huts will live some vikings with their best friends the dragons. The classes will look something like this:

Normally we would create the whole village structure like this: 

This is something we got used to in most programming languages, but it’s backwards. And, it needs a bunch of references to be pre-allocated so we can use them exactly once.

Another way of doing this without a DSL is: 

The reason I don’t prefer this method is that once the structure is a bit more nested it quickly gets bigger than my editor window. Also, it’s a bit hard to follow exactly which parentheses are pairs.

The DSL way

Every DSL has a function that starts the whole process. To create one we’re going to use the techniques mentioned in the first part of the article.

The entry function to our DSL will be the function village . This takes a function that takes no parameters, returns nothing and has VillageBuilder as a receiver. This means we can have the village { … } syntax that will build a village with the details specified inside the {}. Take a close look at what happens with the lambda inside the function body (we create a new builder instance, apply the function we’re passed and call build to create the object we need).

To create the huts we do almost the same thing, but now we declare the hut function as part of the VillageBuilder.

The HutBuilderclass structure is similar to the VillageBuilder and the hut function signature and body is similar to the village function signature and body. But the most important aspect here is that once we type village { we get code completion that tells us there’s a function called hut in that context. This is a consequence of having VillageBuilder as the function receiver for the lambda.

We can see how this method can be extrapolated to have more levels of nesting.

Let’s look at how we can make a property mandatory in a builder:

By making the function take the name as a parameter and passing it to the builder we made this field mandatory.

To add the vikings and dragons we create methods in the HutBuilder and some private fields to store the data until the object is actually built (like we would with the “Builder” design pattern).

Now we can add the address to the huts as an optional parameter by adding a field to the HutBuilder class. By also adding the address as a parameter with a default value to the hut method in VillageBuilder we made it possible to specify the address in 2 different ways (Look at both hut{} examples in the code snipped below).

As you can see this code is much better than what we started out with. It reads well: There’s a village called “Berk”, with 2 huts at the specified addresses and we can clearly see the structure of the objects.

Let’s look at the 3 variants again:

Try writing a new village, and note how easy it is with code completion. There’s just one problem with the code so far. We declared the method hut inside the VillageBuilder and we can still access it inside the context of a HutBuilder. The code in this case will just add another hut to the village, and it would be valid code, but in the context of a DSL it’s not really nice.

Don’t worry though, there’s a solution for this. Kotlin creators saw the power of DSLs and fixed this issue with an annotation that will limit the scope of methods. If you add the annotation, you will not be able to directly access methods from the parent “receiver” only if you manually specify it.

These are just the basic building blocks of how to create a DSL, but you can go crazy with infix functions and operator overloading to make some scenarios even nicer.

Take a look at the final DSL with some minor optimisations below:

Summary

Kotlin DSLs are advertised as “typesafe builders”, so whenever you want a builder, consider writing a DSL for it. A DSL will make your code more readable and easy to change.

Feel empowered to tame your dragons! Write your own DSL and see what you can create.

 

Share This Article


Mihaly

Android Engineer
Mihaly has been working on Web Applications written in PHP and all connected technologies. Developed Java Swing applications, Enterprise web applications, and currently developing Android apps.
My personal goal is to learn all I can about the technologies I work with and continuously improve my programming skills.

Latest posts by Mihaly

No Comments

Post A Comment