Kotlin DSL

Kotlin DSL

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

Ee05733e142cceda61dc7b6319fa6d96?s=128

Birju Vachhani

January 22, 2019
Tweet

Transcript

  1. KOTLIN DSL LET’S BE CREATIVE.

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

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

    Domain Specific Language is and what it brings to the table. 3
  4. A domain-specific language (DSL) is a computer language specialized to

    a particular application domain. 4
  5. 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
  6. EXAMPLES DOMAIN SPECIFIC LANGUAGE ∎ SQL ∎ CSS ∎ CRON

    ∎ REGULAR EXPRESSIONS ∎ ETC GENERAL PURPOSE LANGUAGE ∎ JAVA ∎ C ∎ PYTHON ∎ KOTLIN ∎ ETC 6
  7. A DOMAIN SPECIFIC LANGUAGE IS… ∎ Expressive ∎ More human-readable

    ∎ Easier to understand for less-technical people too. 7
  8. 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
  9. 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
  10. INTERNAL DSL A DSL THAT RIDES ON AN EXISTING LANGUAGE

    10
  11. Leverages the language compiler and the language runtime itself to

    parse and execute DSL. NO NEED OF PARSER 11
  12. 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
  13. 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
  14. 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..
  15. 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
  16. KOTLIN Let’s see what makes kotlin special for internal DSL

    and how we can write one. DSL 16
  17. 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
  18. Drop brackets () and . using infix modifier on functions.

    INFIX FUNCTIONS 18
  19. 19 NORMAL KOTLIN FUNCTION class Car{ fun drive(distance: Int){ println("Driving

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

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

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

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

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

    Function call greet {name-> println(name) } World
  32. 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. 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
  34. Does it seem like code? 2 days ago 34

  35. 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. 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
  37. The helping hands when you want to create a complex

    hierarchical DSL structures. TYPE-SAFE DSL BUILDERS 37
  38. 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
  39. 39 LOOKS MESSY Person("abcdefg", Name("Birju","Vachhani"), "Male", 23, Contact("brvachhani@gmail.com","1234567890"))

  40. 40 LOOKS PRETTY val person = person{ id = "abcdef"

    gender = "Male" age = 23 name{ first = "Birju" last = "Vachhani" } contact { email = "brvachhani@gmail.com" phone = "1234567890" } }
  41. 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. 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() }
  43. Create DSL the right way! SCOPE CONTROL 43

  44. “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. 45 THE SCOPE PROBLEM val person = person{ ... name{

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

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

    ... name{ first = “Birju” last = "Vachhani" name{ // error: a member of outer receiver } ... }
  49. THANKS! Any questions? You can find me on Twitter at

    @birjuvachhani or mail me on brvachhani@gmail.com 49
  50. REFERENCES ➔ KotlinConf 2018 - Creating Internal DSLs in Kotlin

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