Internal DSL's and Kotlin
It’s safe to claim that programming languages, in a very broad sense, could be regarded as means to solve a myriad of problems, however sometimes it feels like some common issues are more specific than others, and that’s where a DSL fits in as it concerns addressing problems from a specific domain with simple and human readable code.
External and Internal DSL’s
External DSL’s have their own syntax and can be run independently, for instance SQL for relational data manipulation, Gherkin for testing and HTML for web development are considered external DSL’s. Internal DSL’s on the other hand are written on top of general purpose languages; examples include Spring Security configuration & Exposed, MyBatis and jOOQ support for working with SQL fluently. This topic focuses on internal DSL’s and how Kotlin allows to write these with a readable and clean syntax.
A Kotlin DSL can look like this simple HTTP security config:
|
|
Higher-order functions
High-order functions are similar to their regular counterpart except that they can take one or more functions as arguments or return a function, it is also worth noting that in Kotlin functions are first-class, which means they can be stored and assigned as a variable and as such passed as a regular parameter. This is a very important concept since we rely on passing lambda expressions as parameters when creating our DSL’s and so on.
|
|
Lambdas outside of parentheses
This feature allows us to write lambda expressions outside of parentheses, thus making such DSL syntax possible, if you take a look at the previous code snippet you can see that we already applied this.
It is valid to use this kind of syntax when the last argument of your function expects a lambda, and if that is your function single argument you can skip the parentheses altogether, which means that if our function fold
from the aforementioned snippet where to receive a single lambda we could do something like in the following example:
|
|
Lambdas & lambdas with receivers
Now let’s take a look at a simple function that receives a lambda as parameter:
|
|
Even though this syntax looks clean and concise it requires the use of the it
prefix inside the lambda in order to refer to the underlying attributes, to further improve readability and cast aside the need of the redundant it
identifier we can refactor our settings()
function to work with a lambda with receivers, this will allow access to our DSL attributes directly.
|
|
Receivers
If you take a look at the previous examples you’ll notice that we dropped the usage of (JwtAuthenticationSettingsBuilder) -> Unit
in favor of JwtAuthenticationSettingsBuilder.() -> Unit
, this way turning our ordinary lambda into a lambda with receiver, in this case the receiver type is identifier before .()
, and it’s basically the type on which we are trying to reach members without an explicit qualifier, this is very similar to the concept of extension functions.
Type-safe builders
When our DSL’s have more complex types such as lists, maps or objects from user defined classes we can make use of builders to help writing and using our DSL’s.
|
|
In this example we have PublicResourceBuilder
which is responsible to build our PublicResource
objects, PublicResources
collects PublicResourceBuilder
instances to a list. @DslMarker
is used to narrow the scope of our receivers and prevent then from accessing data from outer lambdas.
Code from this post can be found on GitHub
Thanks :)
Source: