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

Kotlin DSLs and How to Build Them

Kotlin DSLs and How to Build Them

Domain Specific Language (DSL) using Kotlin are increasingly becoming a prominent part of the impact of the language. They can be used to write type-safe, structured code while keeping it highly readable.

In this talk, I go through what a DSL is, why Kotlin makes it relevant and the important Koltin features that allow us to write great DSLs. I also go through the process of creating a custom DSL and things to keep in mind while making one. By the end of this talk, the attendees will have a good understanding of DSL in Kotlin and how to create one for their use case.

Subhrajyoti Sen

August 02, 2019
Tweet

More Decks by Subhrajyoti Sen

Other Decks in Programming

Transcript

  1. Kotlin DSLs and How To Build
    Them
    Subhrajyoti Sen
    smallcase

    View Slide

  2. DSL??

    View Slide

  3. Domain Specific Language
    A DSL is a computer language specialized to a particular
    application domain. This is contrast to a general-purpose language
    (GPL), which is broadly applicable across domains.
    -Wikipedia

    View Slide

  4. Examples - HTML


    My First Heading
    My first paragraph.




    An Unordered List

    Coffee
    Tea



    View Slide

  5. Examples - CSS
    div {
    background-color: lightgrey;
    width: 300px;
    padding: 50px;
    }
    h1 {
    font-size: 40px;
    }
    h2 {
    font-size: 30px;
    }

    View Slide

  6. Examples - SQL
    SELECT * FROM Customers WHERE Last_Name=”Sen”
    SELECT First_Name, Nickname FROM Friends WHERE Nickname like ‘%brain%’

    View Slide

  7. Where’s my Kotlin?

    View Slide

  8. Examples - HTML
    val html = document.create.html {
    head {
    title("Hello world")
    }
    body {
    h1("h1Class"){
    +"My header1"
    }
    }
    }
    https://github.com/Kotlin/kotlinx.html

    View Slide

  9. Examples - CSS
    val css = Stylesheet {
    a {
    width = 10.px
    color = 0xffffff
    hover {
    color = 0xf2cacf
    }
    }
    }
    https://github.com/olegcherr/Aza-Kotlin-CSS

    View Slide

  10. Examples - SQL
    val row = from(Friends)
    .where { Nickname like "brian" }
    .select(Friends.First_name, Friends.Nickname)
    https://github.com/orangy/squash

    View Slide

  11. Examples - SQL
    https://github.com/arunkumar9t2/transition-x

    View Slide

  12. Advantages
    There must be a reason DSL
    exists and developers write
    customs DSLs

    View Slide

  13. Advantages
    There must be a reason DSL
    exists and developers write
    customs DSLs
    ● Expressive

    View Slide

  14. Advantages
    There must be a reason DSL
    exists and developers write
    customs DSLs
    ● Expressive
    ● Readable

    View Slide

  15. Advantages
    There must be a reason DSL
    exists and developers write
    customs DSLs
    ● Expressive
    ● Readable
    ● Concise - Less Boilerplate

    View Slide

  16. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }
    room {
    roomNumber = 2
    floor = 3
    speaker {
    name = "Sayantika Banik"
    talkTitle = "Open Source"
    }
    }
    }

    View Slide

  17. Why Kotlin?
    Kotlin provides certain features
    that let us develop expressive
    and structured DSL
    ● Lambda with Receiver
    ● Lambda as method parameter
    ● Extension function

    View Slide

  18. val speaker = speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }

    View Slide

  19. val speaker = speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    Function

    View Slide

  20. data class SpeakerModel(
    var name: String = “”,
    var talkTitle: String = “”
    )

    View Slide

  21. fun speaker() {
    }

    View Slide

  22. fun speaker(): SpeakerModel {
    }

    View Slide

  23. fun speaker(): SpeakerModel {
    val speaker = SpeakerModel()
    }

    View Slide

  24. fun speaker(): SpeakerModel {
    val speaker = SpeakerModel()
    return speaker
    }

    View Slide

  25. fun speaker(block: (SpeakerModel) -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    return speaker
    }

    View Slide

  26. Lambda

    View Slide

  27. Lambda
    Lambdas are code blocks enclosed in curly braces.

    View Slide

  28. Lambda
    Lambdas are code blocks enclosed in curly braces.
    val multiply: (Int, Int) -> Int

    View Slide

  29. Lambda
    Lambdas are code blocks enclosed in curly braces.
    val multiply: (Int, Int) -> Int
    val multiply = { a: Int, b: Int -> a*b }

    View Slide

  30. Lambda
    Lambdas are code blocks enclosed in curly braces.
    val multiply: (Int, Int) -> Int
    val multiply = { a: Int, b: Int -> a*b }
    println(multiply(3,4))

    View Slide

  31. fun speaker(block: (SpeakerModel) -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    return speaker
    }

    View Slide

  32. fun speaker(block: (SpeakerModel) -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    block(speaker)
    return speaker
    }

    View Slide

  33. val speaker = speaker({
    it.name = "Subhrajyoti Sen"
    it.talkTitle = "Intro to Kotlin DSL"
    })

    View Slide

  34. fun speaker(block: (SpeakerModel) -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    block(speaker)
    return speaker
    }

    View Slide

  35. fun speaker(block: SpeakerModel.() -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    block(speaker)
    return speaker
    }

    View Slide

  36. fun speaker(block: SpeakerModel.() -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    block(speaker)
    return speaker
    }
    Receiver Lambda

    View Slide

  37. fun speaker(block: SpeakerModel.() -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    speaker.block()
    return speaker
    }

    View Slide

  38. val speaker = speaker({
    this.name = "Subhrajyoti Sen"
    this.talkTitle = "Intro to Kotlin DSL"
    })

    View Slide

  39. val speaker = speaker({
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    })

    View Slide

  40. val speaker = speaker({
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    })
    Extra bracket?

    View Slide

  41. Lambda as a Parameter
    fun modify(a: Int, lambda: (Int) -> Int)

    View Slide

  42. Lambda as a Parameter
    fun modify(a: Int, lambda: (Int) -> Int)
    modify(3, someLambda)

    View Slide

  43. Lambda as a Parameter
    fun modify(a: Int, lambda: (Int) -> Int)
    modify(3, someLambda)
    modify(3) {
    }

    View Slide

  44. val speaker = speaker({
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    })

    View Slide

  45. val speaker = speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }

    View Slide

  46. room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }

    View Slide

  47. data class RoomModel(
    var roomNumber: Int = 0,
    var floor: Int = 99,
    var speaker: SpeakerModel? = null
    )

    View Slide

  48. fun room(block: RoomModel.() -> Unit): RoomModel {
    val room = RoomModel()
    room.block()
    return room
    }

    View Slide

  49. room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }

    View Slide

  50. But there’s a problem

    View Slide

  51. room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }

    View Slide

  52. Extension Function
    A function that extends the functionality without modifying it
    fun Banner.isVisible(): Boolean {
    return this.visibility == Banner.VISIBLE
    }

    View Slide

  53. Extension Function
    A function that extends the functionality without modifying it
    fun Banner.isVisible(): Boolean {
    return this.visibility == Banner.VISIBLE
    }
    Base class

    View Slide

  54. Extension Function
    A function that extends the functionality without modifying it
    fun Banner.isVisible(): Boolean {
    return this.visibility == Banner.VISIBLE
    }
    Base class Extension function

    View Slide

  55. fun speaker(block: SpeakerModel.() -> Unit): SpeakerModel {
    val speaker = SpeakerModel()
    speaker.apply(block)
    return speaker
    }

    View Slide

  56. fun speaker(block: SpeakerModel.() -> Unit) {
    val speaker = SpeakerModel()
    speaker.apply(block)
    return speaker
    }

    View Slide

  57. fun speaker(block: SpeakerModel.() -> Unit) {
    val speaker = SpeakerModel()
    speaker.apply(block)
    }

    View Slide

  58. fun RoomModel.speaker(block: SpeakerModel.() -> Unit) {
    val speaker = SpeakerModel()
    speaker.apply(block)
    }

    View Slide

  59. fun RoomModel.speaker(block: SpeakerModel.() -> Unit) {
    val speaker = SpeakerModel()
    speaker.apply(block)
    this.speaker = speaker
    }

    View Slide

  60. room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }

    View Slide

  61. val conference = conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }
    }

    View Slide

  62. data class ConferenceModel(
    var name: String = "",
    var location: String = "",
    var rooms: MutableList = mutableListOf()
    )

    View Slide

  63. data class ConferenceModel(
    var name: String = "",
    var location: String = "",
    var rooms: MutableList = mutableListOf()
    )
    Create an empty mutable list

    View Slide

  64. fun conference(block: ConferenceModel.() -> Unit):
    ConferenceModel {
    val conference = ConferenceModel()
    conference.apply(block)
    return conference
    }

    View Slide

  65. fun ConferenceModel.room(block: RoomModel.() -> Unit) {
    val room = RoomModel()
    room.apply(block)
    this.rooms.add(room)
    }

    View Slide

  66. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }
    room {
    roomNumber = 2
    floor = 3
    speaker {
    name = "Sayantika Banik"
    talkTitle = "Open Source"
    }
    }
    }

    View Slide

  67. Not yet done

    View Slide

  68. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    floor = 2
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    }
    }
    }

    View Slide

  69. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    floor = 2
    }
    }
    }

    View Slide

  70. This actually compiles
    and works

    View Slide

  71. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    floor = 2
    }
    }
    }

    View Slide

  72. conference {
    name = "DevConfIN 19"
    location = "Bengaluru"
    room {
    roomNumber = 1
    speaker {
    name = "Subhrajyoti Sen"
    talkTitle = "Intro to Kotlin DSL"
    floor = 2
    }
    }
    }
    Still inside the scope of room

    View Slide

  73. Scope Control - DSL Marker
    The lambda should be able to access only it’s nearest implicit receiver.

    View Slide

  74. Scope Control - DSL Marker
    The lambda should be able to access only it’s nearest implicit receiver.
    @DslMarker
    @Target(AnnotationTarget.TYPE)
    annotation class ConfDSL

    View Slide

  75. fun RoomModel.speaker(block: SpeakerModel.() -> Unit) {
    // ...
    }
    fun ConferenceModel.room(block: RoomModel.() -> Unit) {
    // ...
    }
    fun conference(block: ConferenceModel.() -> Unit): ConferenceModel {
    // ...
    }

    View Slide

  76. fun RoomModel.speaker(block: (@ConfDSL SpeakerModel).() -> Unit) {
    // ...
    }
    fun ConferenceModel.room(block: (@ConfDSL RoomModel).() -> Unit) {
    // ...
    }
    fun conference(block: (@ConfDSL ConferenceModel).() -> Unit):
    ConferenceModel {
    // ...
    }

    View Slide

  77. To conclude, DSLs are
    awesome

    View Slide

  78. Questions?
    http://bit.ly/DevConfDSL

    View Slide

  79. Thank you
    SubhrajyotiSen
    iamsubhrajyoti

    View Slide