Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin DSL

Kotlin DSL

Learn and understand what a DSL is and how one can write beautiful DSLs in Kotlin.

Birju Vachhani

January 22, 2019

More Decks by Birju Vachhani

Other Decks in Programming


  1. HELLO! I am Birju Vachhani I am here because I

    love to write Kotlin DSL. You can find me on Twitter at @birjuvachhani 2
  2. WHAT IS DSL EXACTLY? Let’s start by understanding what a

    Domain Specific Language is and what it brings to the table. 3
  3. 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

  5. A DOMAIN SPECIFIC LANGUAGE IS… ∎ Expressive ∎ More human-readable

    ∎ Easier to understand for less-technical people too. 7

    you get to define your own language, syntax and semantics. The beauty is you have the flexibility of the syntax. 8
  7. 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
  8. Leverages the language compiler and the language runtime itself to

    parse and execute DSL. NO NEED OF PARSER 11
  9. 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
  10. 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
  11. 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..
  12. 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
  13. 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
  14. 19 NORMAL KOTLIN FUNCTION class Car{ fun drive(distance: Int){ println("Driving

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

    $distance") } } val car = Car() // infix function call car drive 10 Driving 10
  16. Another Kotlin feature that provides fluency to the existing classes

    as well. Infix modifiers can also be applied to extension function. EXTENSIONS 21
  17. 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
  18. 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
  19. 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
  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
  21. 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
  22. 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
  23. Take an arbitrary function and turn it into a method

    of your class. IMPLICIT RECEIVERS 28
  24. 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
  25. In simple words , when you use ‘this’ keyword in

    a lambda, it refers to its context object. STILL CONFUSED? 30
  26. 31 NORMAL LAMBDA FUNCTION fun greet(func:(String)-> Unit){ func("World") } //

    Function call greet {name-> println(name) } World
  27. 32 IMPLICIT RECEIVER LAMBDA FUNCTION - 1 fun greet(func: String.(String)->

    Unit){ func("Hello”,”World") } // Function call greet {name-> println("$this $name") } Hello World

    drive(dist:Int){ println("Driving $dist") } } fun process(func:Car.()-> Unit){ Car().func() } process{ drive(30) } Driving 30
  29. 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
  30. 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
  31. The helping hands when you want to create a complex

    hierarchical DSL structures. TYPE-SAFE DSL BUILDERS 37
  32. 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
  33. 40 LOOKS PRETTY val person = person{ id = "abcdef"

    gender = "Male" age = 23 name{ first = "Birju" last = "Vachhani" } contact { email = "[email protected]" phone = "1234567890" } }
  34. 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() } } }
  35. 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() }
  36. “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?
  37. 45 THE SCOPE PROBLEM val person = person{ ... name{

    first = “Birju” last = "Vachhani" name{ // should be forbidden } ... }
  38. 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
  39. 47 SCOPE PROBLEM SOLUTION - 1 @DslMarker annotation class PersonDslMarker

    @PersonDslMarker class Person{...} @PersonDslMarker class Name{...} @PersonDslMarker class Contact{...}
  40. 48 SCOPE PROBLEM SOLUTION - 2 val person = person{

    ... name{ first = “Birju” last = "Vachhani" name{ // error: a member of outer receiver } ... }
  41. REFERENCES ➔ KotlinConf 2018 - Creating Internal DSLs in Kotlin

    by Venkat Subramaniam. ➔ KotlinConf 2018 - Next Level DSLs by Aaron Sarazan ➔ Type-safe Builders 50