This is the second part of Base knowledge to build a DSL in Kotlin. In the first part, I covered all the Kotlin features that you must know to build a DSL. These features are:

If you’re not familiar with any of these features, I suggest you to take a look at it before moving forward. In this second part, I’ll show you Kotlin features that allow us to write a more idiomatic DSL. These features are:

Infix notation

If you have experience in Kotlin, you probably have defined a Map collection with the key to value notation or maybe you have defined ranges and for loops like this for (i in 1 until 100 step 2). The words to, until and step are infix functions.

A function invocation in infix notation omits its parentheses and the dot. For example, a regular function call like a.until(b) can be written in infix notation like this a until b. To make a function able to be written in infix notation, all you have to do is mark it with the infix modifier:

In the previous example, the extension function remove is marked with the infix modifier. This allows us to invoke it through any String in either regular notation — Line 7 — or infix notation — Line 8 — . Basically, all this function does is remove the character passed in as input parameter in regular notation, or declared to the right side of the function name in infix notation.

The previous code outputs the following:

Ideally, infix notation is applied to let us write more idiomatic code. By using infix notation, our goal should be making our code seem like it was written in natural language. In order to accomplish that, we should not only use infix notation, but choosing meaningful and accurate names for our functions and variables. To illustrate this, take a look at the next example:

In this example, I defined a class named Car with a property named speed which corresponds to the speed the car moves at. Also, I defined an enum class named Direction with the 4 main cardinal directions. The idea is to make a Car object move through x-axis (EAST and WEST) and y-axis (NORTH and SOUTH) by invoking function driveTo which is marked with the modifier infix. Its property coordinates indicates where the car is at any moment. Lines 25, 27, 29, and 31 are expressed in infix notation.

The previous code outputs the following:

Finally, to define infix functions, you must take into account the following restrictions:

  • It must be either an extension function or a member function.
  • It must have one input parameter.
  • Its input parameter must not be marked with the vararg modifier and it cannot have a default value.

🔗 If you want to learn more about infix notation, take a look at the section Infix notation of Kotlin’s official documentation at https://kotlinlang.org/docs/functions.html#infix-notation

Operator overloading

In programming, operator overloading is a way to define a function with a reserved/special name according to the operator to be overloaded. Some operators are + , - , * , / , % , += , < , > , etc. When an operator is overloaded, we can use it to invoke its corresponding function.

In Kotlin, in order to overload an operator, a function has to be created and marked with the operator modifier. Since each operator has a reserved name, this function has to be named according to the operator to be overloaded and it has to be either a member function or an extension function.

Let’s see an example with the modulus or remainder operator %:

To overload the % operator, it is necessary to create a function and name it rem. In this case it’s an extension function with a receiver of type String which enables us to apply it on any text — line 4 — . It’s also possible to invoke the function as usual — line 6 — .

All rem function does is return true if the String length is a multiple of the number passed in as input parameter. Although I could have done anything I want and return whatever I want, the idea is to use our common sense and implement functions according to the operator in question. That is, for instance if I wanted to overload the + operator and apply it on 2 objects of type X, we would expect the result to be another object of type X whose properties are additions of their corresponding properties.

To illustrate this, take a look at the next code snippet where a class named LivingBeing is created with 2 properties: developmentCycle and sex. The main idea is to overload the + operator to combine 2 LivingBeing objects which will result in a new LivingBeing object.

Clearly my intention was to simulate the reproduction of living beings. When the + operator is applied on 2 objects of type LivingBeing, a new LivingBeing object is returned, but that’s not all. In order to “reproduce”, their property sex must be different from each other’s and their property developmentCycle must be at least equal to MIN_DEV_CYCLE_TO_REPRODUCE. Finally, if both conditions are met, a new LivingBeing object is created with its property developmentCycle set to 0 and its property sex randomly set.

As you can see, I overloaded the + operator to make an operation analogous to an addition. That is, when 2 objects of type LivingBeing are “added”, the result is a new LivingBeing object. However, overloading the + operator doesn’t necessarily mean that the result has to be a new object. For instance, if we added 2 colors expressed in hexadecimal notation, we could overload the + operator to return the result of adding both numbers: Red + Blue = Magenta (#FF0000 + #0000FF = #FF00FF).

Make sure to apply common sense every time you overload an operator and, just like your variables and functions have meaningful and descriptive names, you overload it according to its meaning.

Now that you know how to overload an operator, you may now practice with the rest. Here’s an image with all operators that can be overloaded in Kotlin and their respective reserved/special function names:

Operators that can be overloaded in Kotlin

🔗 If you want to learn more about this topic, take a look at the section Operator overloading of Kotlin’s official documentation at https://kotlinlang.org/docs/operator-overloading.html

Inline functions

When we create a high-order function, the compiler turns its function types into instances of Function# interfaces. If it gets invoked many times, there would be an excessive and unnecessary use of resources because of constant allocations and functions invocations. To solve this issue, Kotlin provides us with the inline modifier.

By marking a function with the inline modifier, we can achieve a more efficient performance since the compiler will replace every invocation with its implementation. Furthermore, all of its function types will no longer be converted into objects, but they will become inline too.

Let’s illustrate it with a simple example:

The main function invokes executeBetweenPrints which prints out the phrase "First print" to the console, then it executes task: () -> Unit lambda and it finishes printing out the phrase "Last print". All task lambda does is print out "My task is executing..." to the console.

The previous code outputs the following:

If you compile and decompile the bytecode to Java code, you would get code similar to the following (although with much more noise):

Take a look at the function executeBetweenPrints. It receives an instance of Function0 interface which wraps the lambda’s implementation inside its invoke method.

If executeBetweenPrints was invoked just a few times, then marking it with the inline modifier wouldn’t make any difference in its general performance. However, if it was invoked repeatedly inside a loop or inside of a stream operator, each invocation would represent a new memory allocation and this would lead to an excessive use of resources.

Now let’s mark the function executeBetweenPrints with the inline modifier to see the difference:

If we compile it and decompile the bytecode to Java code, we’d get the following code:

The executeBetweenPrints invocation has been replaced with its implementation, avoiding memory allocations with anonymous classes.

At this point I guess you’re considering to mark every function with the inline modifier. Not so fast! As I mentioned before, if an inline function is invoked just a few times, there won’t be a significant performance improvement and it would be actually counterproductive. You should know that if an inline function is extensive, the generated bytecode will be too large. In general terms, inline functions larger than 4 lines are not recommended.

To have a better general idea about when it is convenient to mark a function with the inline modifier, you can take into account the following indicators:

— It is a high-order function.
— It is invoked repeatedly.
— It’s not larger than 4 lines of code.

If a function meets all those indicators, consider marking it as inline.

Finally, you should know that sometimes it’s not possible to mark a function with the inline modifier because of some restrictions, mainly because of the generated code, but that can be solved with modifiers like noinline, crossinline and reified; although it depends on each use case. Since we won’t need any of those modifiers, they are out of the scope of this series.

🔗 If you want to learn more about this topic, take a look at the section Inline functions of Kotlin’s official documentation at https://kotlinlang.org/docs/operator-overloading.html

🔗 You can also check out this video where Florina Muntenescu explains the inline modifier: https://www.youtube.com/watch?v=wAQCs8-a6mg

Singleton design pattern/antipattern

One of the most controversial design patterns is the Singleton pattern. I call it pattern/antipattern because in our community there are developers who consider that this pattern generates more harm than good.

A Singleton is just an object that can be instantiated only once. That is, there will be only 1 instance of that object during the application lifecycle. This is possibly the simplest design pattern and while it can be very powerful, it can certainly also be very damaging. To avoid misuse, this design pattern is usually applied correctly under circumstances such as the following:

— When a single access point to some source of information, such as a database or a web service, is required.
— When an object creation is very expensive and the system requires a more dynamic performance. This is usually very common in videogames development.
— When an object that never changes works as a special “message” or as an object of a type defined within a set of objects of the same class/category. This is usually common when the Observer design pattern is applied and different “messages” of the same class are required, commonly defined under a sealed class.

There might be other use cases and each of them should be considered carefully, but in general terms, this design pattern tends to coincide from project to project. After all, it’s a pattern for a reason.

Creating a Singleton in Kotlin is very similar to creating a class, with the only difference that instead of the class keyword, you should use the object keyword:

With the object keyword, a class and its instance is created at the same time. A Singleton is created lazily, that is, until it’s accessed for the first time. In order to access it, you have to call it by its name directly.

Just like a regular class, an object can inherit from a class and it can also implement interfaces:

🔗 If you want to learn more about this topic, take a look at the section Object expressions and declarations of Kotlin’s official documentation at https://kotlinlang.org/docs/object-declarations.html

We’re done with all the Kotlin features that allow us to write more idiomatic DSLs. In the next article I’ll present you the codebase before we start building a DSL on top of it.

Continue with the next article Codebase: Project Shapes-DSL

💬 If you enjoyed this article, you can show your appreciation by buying me a coffee at the link below. Thanks for reading and for your support.

--

--

Glenn Sandoval
Kotlin and Kotlin for Android

I’m a software developer who loves learning and making new things all the time. I especially like mobile technology.