Share
Embed
Kotlin DSL
Learn and understand what a DSL is and how one can write beautiful DSLs in Kotlin.
Birju Vachhani
January 22, 2019
Transcript
50 slides
-
HELLO! I am Birju Vachhani I am here because I
love to write Kotlin DSL. You can find me on Twitter at @birjuvachhani 2 -
WHAT IS DSL EXACTLY? Let’s start by understanding what a
Domain Specific Language is and what it brings to the table. 3 -
A domain-specific language (DSL) is a computer language specialized to
a particular application domain. 4 -
DSL is a language that focuses on just one particular
part of an application. This is in contrast to general-purpose language which is broadly applicable across domains. 5 -
EXAMPLES DOMAIN SPECIFIC LANGUAGE ∎ SQL ∎ CSS ∎ CRON
∎ REGULAR EXPRESSIONS ∎ ETC GENERAL PURPOSE LANGUAGE ∎ JAVA ∎ C ∎ PYTHON ∎ KOTLIN ∎ ETC 6 -
A DOMAIN SPECIFIC LANGUAGE IS… ∎ Expressive ∎ More human-readable
∎ Easier to understand for less-technical people too. 7 -
1 OF 2 TYPES OF DSL IS EXTERNAL DSL Where
you get to define your own language, syntax and semantics. The beauty is you have the flexibility of the syntax. 8 -
EXTERNAL DSL DISADVANTAGE You have to take burden of parsing
it as well. Writing parser is a tough job. DO NOT READ IT There is a very strong tradition of using external DSL in the Unix community. 9 -
Leverages the language compiler and the language runtime itself to
parse and execute DSL. NO NEED OF PARSER 11 -
This is not a disadvantage if the language is really
flexible and fluent. Otherwise you end up staying with the limitations of language. WORKS WITHIN THE CONFINES OF THE LANGUAGE 12 -
The more creative you are, more you can bend the
language.You need to ask yourself how do I make this language work this way. ABILITY TO BEND THE LANGUAGE 13 -
Helps to remove the noise from the code. For humans,
context provides ability to continue a conversation EXTREMELY IMPORTANT: CONTEXT When you have context, you need fewer words and you can be expressive. 14 One of the key things about DSL is they carry the context with them.. -
Because of fluency, code begins to feel like English. Fluency
depends on the fluentness of the host language. FLUENCY The whole point about fluency is that it doesn’t feel like code. 15 -
KOTLIN Let’s see what makes kotlin special for internal DSL
and how we can write one. DSL 16 -
Semicolon breaks the flow. When you are reading something, the
semicolon literally tells you it’s not like writing fluent. It is an advantage for Kotlin as semicolons are optional. OPTIONAL SEMICOLON 17 -
Drop brackets () and . using infix modifier on functions.
INFIX FUNCTIONS 18 -
19 NORMAL KOTLIN FUNCTION class Car{ fun drive(distance: Int){ println("Driving
$distance") } } val car = Car() car.drive(10) Driving 10 -
20 INFIX FUNCTION class Car{ infix fun drive(distance: Int){ println("Driving
$distance") } } val car = Car() // infix function call car drive 10 Driving 10 -
Another Kotlin feature that provides fluency to the existing classes
as well. Infix modifiers can also be applied to extension function. EXTENSIONS 21 -
Add functionalities to an existing class without extending it! That
means you don’t need to extend existing classes for adding small functionalities. You can use extension functions right away! WHAT ARE EXTENSIONS? 22 -
You can just a sacred java class and add methods
to it. The last time I checked we live in a free country right! So absolutely we can add methods to any java class we want. (As long as we can access the class) EXTENSIONS WORK ON JAVA CLASSES TOO 23 -
24 INFIX EXTENSION FUNCTION class Car{ infix fun drive(distance: Int){
println("Driving $distance") } } infix fun Car.accelerate(speed:Int){ println("accelerate $speed") } val car = Car() // infix function call car accelerate 20 accelerate 20 -
More the parenthesis, more the noise. Lesser the noise in
the code, more it is pleasant to read. When a lambda is a last parameter, it gets a special treatment. It can step outside of the function parentheses. NO BRACKETS FOR PASSING THE LAST LAMDA 25 -
26 NOISY FUNCTION /** * func param is a function
that takes Int as an argument and returns Unit */ fun process(func: (Int)->Unit, num: Int){ func(num * 2) } process({ num-> println(num) },2) 4 -
27 LESS NOISY FUNCTION /** * func param is a
function that takes Int as an argument and returns Unit */ fun process(num: Int, func: (Int)->Unit){ func(num * 2) } // the last lambda can be written outside the parentheses process(2) { num-> println(num) } 4 -
Take an arbitrary function and turn it into a method
of your class. IMPLICIT RECEIVERS 28 -
They are nothing but lambdas with context object. When this
type of lambdas execute, they are going to run in the context of the context object. Think of a normal lambda as an arbitrary function. Running lambda in the context of an object makes it execute like it is a method of that class of that context object. WHAT ARE IMPLICIT RECEIVERS ? 29 -
In simple words , when you use ‘this’ keyword in
a lambda, it refers to its context object. STILL CONFUSED? 30 -
31 NORMAL LAMBDA FUNCTION fun greet(func:(String)-> Unit){ func("World") } //
Function call greet {name-> println(name) } World -
32 IMPLICIT RECEIVER LAMBDA FUNCTION - 1 fun greet(func: String.(String)->
Unit){ func("Hello”,”World") } // Function call greet {name-> println("$this $name") } Hello World -
33 IMPLICIT RECEIVER LAMBDA FUNCTION - 2 class Car{ fun
drive(dist:Int){ println("Driving $dist") } } fun process(func:Car.()-> Unit){ Car().func() } process{ drive(30) } Driving 30 -
What my observation says: • 2 is an Integer. •
Days is probably be a function • If days is a function, ago must be an argument. Let’s code it. 2 days ago 35 -
36 2 days ago DSL val ago = "ago" infix
fun Int.days(tense:String){ when(tense){ ago-> println(LocalDateTime.now().minusDays(this.toLong())) } } 2 days ago 2019-01-05T18:29:28.711 -
The helping hands when you want to create a complex
hierarchical DSL structures. TYPE-SAFE DSL BUILDERS 37 -
DSL Builders are those who help create a complex DSL
structure. They could be classes, functions implicit lambda receiver. Their sole purpose is to help create a DSL syntax and build them on a particular class. WHAT’S A TYPE-SAFE BUILDER? 38 -
40 LOOKS PRETTY val person = person{ id = "abcdef"
gender = "Male" age = 23 name{ first = "Birju" last = "Vachhani" } contact { email = "[email protected]" phone = "1234567890" } } -
41 TYPE-SAFE BUILDER - 1 class Person{ var name =
Name() var id = "" var gender = "" var age = 0 var contact = Contact() fun name(func:Name.()->Unit){ name = Name().apply{ func() } } fun contact(func:Contact.()->Unit){ contact = Contact().apply{ func() } } } -
42 TYPE-SAFE BUILDER - 2 class Name{ var first =
"" var last = "" } class Contact{ var email = "" var phone = "" } fun person(func:Person.()->Unit):Person = Person().apply{ func() } -
“When using DSLs, one might have come across the problem
that too many functions can be called in the context. We can call methods of every available implicit receiver inside a lambda and therefore get an inconsistent result.” THE SCOPE PROBLEM 44 What does Kotlin docs say? -
45 THE SCOPE PROBLEM val person = person{ ... name{
first = “Birju” last = "Vachhani" name{ // should be forbidden } ... } -
To control the scope, create an annotation class with @DslMarker
annotation and annotate the types of all receivers used in the DSL with the same marker annotation class. @DslMarker TO THE RESCUE 46 -
47 SCOPE PROBLEM SOLUTION - 1 @DslMarker annotation class PersonDslMarker
@PersonDslMarker class Person{...} @PersonDslMarker class Name{...} @PersonDslMarker class Contact{...} -
48 SCOPE PROBLEM SOLUTION - 2 val person = person{
... name{ first = “Birju” last = "Vachhani" name{ // error: a member of outer receiver } ... } -
THANKS! Any questions? You can find me on Twitter at
@birjuvachhani or mail me on [email protected] 49 -
REFERENCES ➔ KotlinConf 2018 - Creating Internal DSLs in Kotlin
by Venkat Subramaniam. ➔ KotlinConf 2018 - Next Level DSLs by Aaron Sarazan ➔ Type-safe Builders 50