Slide 1

Slide 1 text

KOTLIN DSL LET’S BE CREATIVE.

Slide 2

Slide 2 text

HELLO! I am Birju Vachhani I am here because I love to write Kotlin DSL. You can find me on Twitter at @birjuvachhani 2

Slide 3

Slide 3 text

WHAT IS DSL EXACTLY? Let’s start by understanding what a Domain Specific Language is and what it brings to the table. 3

Slide 4

Slide 4 text

A domain-specific language (DSL) is a computer language specialized to a particular application domain. 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

EXAMPLES DOMAIN SPECIFIC LANGUAGE ∎ SQL ∎ CSS ∎ CRON ∎ REGULAR EXPRESSIONS ∎ ETC GENERAL PURPOSE LANGUAGE ∎ JAVA ∎ C ∎ PYTHON ∎ KOTLIN ∎ ETC 6

Slide 7

Slide 7 text

A DOMAIN SPECIFIC LANGUAGE IS… ∎ Expressive ∎ More human-readable ∎ Easier to understand for less-technical people too. 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

INTERNAL DSL A DSL THAT RIDES ON AN EXISTING LANGUAGE 10

Slide 11

Slide 11 text

Leverages the language compiler and the language runtime itself to parse and execute DSL. NO NEED OF PARSER 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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..

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

KOTLIN Let’s see what makes kotlin special for internal DSL and how we can write one. DSL 16

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Drop brackets () and . using infix modifier on functions. INFIX FUNCTIONS 18

Slide 19

Slide 19 text

19 NORMAL KOTLIN FUNCTION class Car{ fun drive(distance: Int){ println("Driving $distance") } } val car = Car() car.drive(10) Driving 10

Slide 20

Slide 20 text

20 INFIX FUNCTION class Car{ infix fun drive(distance: Int){ println("Driving $distance") } } val car = Car() // infix function call car drive 10 Driving 10

Slide 21

Slide 21 text

Another Kotlin feature that provides fluency to the existing classes as well. Infix modifiers can also be applied to extension function. EXTENSIONS 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Take an arbitrary function and turn it into a method of your class. IMPLICIT RECEIVERS 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

In simple words , when you use ‘this’ keyword in a lambda, it refers to its context object. STILL CONFUSED? 30

Slide 31

Slide 31 text

31 NORMAL LAMBDA FUNCTION fun greet(func:(String)-> Unit){ func("World") } // Function call greet {name-> println(name) } World

Slide 32

Slide 32 text

32 IMPLICIT RECEIVER LAMBDA FUNCTION - 1 fun greet(func: String.(String)-> Unit){ func("Hello”,”World") } // Function call greet {name-> println("$this $name") } Hello World

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Does it seem like code? 2 days ago 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

The helping hands when you want to create a complex hierarchical DSL structures. TYPE-SAFE DSL BUILDERS 37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

39 LOOKS MESSY Person("abcdefg", Name("Birju","Vachhani"), "Male", 23, Contact("[email protected]","1234567890"))

Slide 40

Slide 40 text

40 LOOKS PRETTY val person = person{ id = "abcdef" gender = "Male" age = 23 name{ first = "Birju" last = "Vachhani" } contact { email = "[email protected]" phone = "1234567890" } }

Slide 41

Slide 41 text

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() } } }

Slide 42

Slide 42 text

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() }

Slide 43

Slide 43 text

Create DSL the right way! SCOPE CONTROL 43

Slide 44

Slide 44 text

“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?

Slide 45

Slide 45 text

45 THE SCOPE PROBLEM val person = person{ ... name{ first = “Birju” last = "Vachhani" name{ // should be forbidden } ... }

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47 SCOPE PROBLEM SOLUTION - 1 @DslMarker annotation class PersonDslMarker @PersonDslMarker class Person{...} @PersonDslMarker class Name{...} @PersonDslMarker class Contact{...}

Slide 48

Slide 48 text

48 SCOPE PROBLEM SOLUTION - 2 val person = person{ ... name{ first = “Birju” last = "Vachhani" name{ // error: a member of outer receiver } ... }

Slide 49

Slide 49 text

THANKS! Any questions? You can find me on Twitter at @birjuvachhani or mail me on [email protected] 49

Slide 50

Slide 50 text

REFERENCES ➔ KotlinConf 2018 - Creating Internal DSLs in Kotlin by Venkat Subramaniam. ➔ KotlinConf 2018 - Next Level DSLs by Aaron Sarazan ➔ Type-safe Builders 50