Brief review of Kotlin DSL
Make your own language
[ English | 한국어 ]
What is DSL? DSL is stands for “Domain Specific Language” and used in a particular domain only. There is a opposite concept called “General Purpose Language” and All the programming languages such as C, C++, Kotlin and Swift can be told as an example.
For the real-life example, “General Purpose Language” is usual language we use in our daily life and “Domain Specific Language” is used among professionals such as doctor, lawyer and developer like us. It is very hard or impossible that you live only using domain specific language but It is very efficient when you use it in the specific domain (field).
As a developer, you may have experiences using DSL when you use a framework or a 3rd party library. When you want to know the syntax of a DSL, you shouldn’t see the language guide but the official guide of the framework or the library. Because DSLs are not general purpose language.
As an Android developer, you have already used many kinds of DSL. For example, you can specify many kinds of android options in the build script,
or you can also specify many kinds of Coil(one of the famous image loading libraries) options for modifying loaded images.
You might be noticed the DSL way in the above example is simpler and easier to read than the usual builder or factory pattern for creating a complex object. If you use DSL appropriately when you make your library or module, the clients who use your library can write code easily and concisely. In addition, well-defined DSL could increase readability of the code because it is usually similar to the natural language.
You can define and use your own DSL as using some functionalities provided by Kotlin.
- Lambda Expression
- Higher-order Function
- Extension Function
Lambda Expression
It is a expression that expresses a function only with parameters and its return values. The form of the expression differs slightly from language to language and you can convert add
funtion to lambda expression like below.
Function Expression : fun add(num1: Int, num2: Int): IntLambda Expression : (Int, Int) -> Int
Higher-order Function
When a function takes one or more functions as parameters or returns a function, it is called a higher-order function. The functions are usually expressed as a lambda expression. If you declare calculate
function and pass particular operation function to the third parameter, you can make the function change its behavior dynamically.
For more detailed explanation about Higher-order function and lambda expression, see this official guide.
Extension function
Extension functions are a feature provided in Kotlin that allows you to define additional functions without inheritance to an already defined class or interface. For instance, if you want to add clear()
function to the Calculator class, you can define the extension function like below and use it like normal function.
fun Calculator.clear() { ... }
More information about extension functions can be found in the official guide.
Now, let’s define a simple DSL using aforementioned features. We eventually want to define a DSL that can make Http API calls like below (It’s not very useful but it’s good enough to understand DSL).
Above code snippet represents building and executing http request using get method including custom headers and additionally it retries when there are specified error occurred (500, 501 and 503).
Let’s start with the data class which has request data such as url, method, headers, etc in order to make the above API call possible.
HttpRequest data class in the code declares its properties as var
type and all of them has its default value. This is because, as you will see in a moment, the HttpRequest object is first created as an empty one and then the input value is bound by the lambda block.
All of the data types used in this data class is defined like below (MyResponse class is model class for binding http response data).
Now, Let’s define DSL to create HttpRequest object in earnest.
inline
and reified
keywords are used to infer the generic return type and parse http response data as the inferred type.
The most important part is the parameter of httpRequest
function in line number 1. You can see the lambda function which has no parameters and return value is declared as a extension function of HttpRequest class. In this case, HttpRequest class is called receiver class of () -> Unit
lambda function and the lambda function can access member properties and functions of receiver class because it is executed in the context of receiver class.
In line number 2, I make empty HttpRequest object and invoke lambda function with the HttpRequest object as its parameter so that the logic in the lambda function can access the member properties and functions using this
context.
I intentionally exclude http API calling and response parsing code for brevity.
Then, how is the retryPolicy DSL defined?
It’s very similar to the httpRequest DSL described above, so I don’t think it needs any further explanation.
When you see the actual usage of the DSL in IDE, you can tell which receiver is being used in a lambda block as seeing the receiver hint.
So far we have briefly covered how to write a Kotlin DSL. As an example I used in an actual project, I defined the DSL which supports declare API endpoint urls per build phase(alpha, beta, real) and has many other options. I could implement dynamic phase changing easily thanks to the DSL.
Once you know a new technology, it’s hard not to use it. I think it will make the code more readable and flexible if it is used properly where it is absolutely necessary without being excessive. 😀
The end.