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

DSLs in a Kotlin Way - V2

Ubiratan Soares
PRO
July 21, 2018
140

DSLs in a Kotlin Way - V2

Presentation about Kotlin machinery for DSL grammars. Updated version for a previous talk.

Oferred at the following events

- The Developers Conference, Kotlin Track (July / 2018)

Ubiratan Soares
PRO

July 21, 2018
Tweet

Transcript

  1. DSLs IN A
    KOTLIN WAY
    Ubiratan Soares
    July / 2018
    VERSION
    2.0

    View Slide

  2. View Slide

  3. SELECT *
    FROM accounts
    WHERE id=12345

    View Slide

  4. SELECT *
    FROM accounts
    WHERE id=12345
    "
    "

    View Slide

  5. String sql = SQLiteQueryBuilder
    .select("*")
    .from("accounts")
    .where("id=12345")
    .toString();
    https://github.com/alexfu/SQLiteQueryBuilder

    View Slide




  6. Hey!!"h1>
    Ho!Lets go!!"span>
    !"div>
    !"body>
    !"html>

    View Slide

  7. val tree = createHTMLDocument().html {
    body {
    h1 { +"Hey" }
    div {
    +"Ho!" span { +"Lets go!" }
    }
    }
    }
    https://github.com/Kotlin/kotlinx.html

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. Disclaimer : Strategic jump

    View Slide

  12. Type Aliases
    Sealed Classes
    Extension Functions
    Extension Properties
    ETC

    View Slide

  13. VERSION
    1.0

    View Slide

  14. INFIX
    NOTATION

    View Slide

  15. class Extractor {
    fun firstLetter(target: String) =
    target[0].toString()
    }
    val k = Extractor().firstLetter("Kotlin")

    View Slide

  16. class Extractor {
    infix fun firstLetter(target: String) =
    target[0].toString()
    }
    val k = Extractor() firstLetter "Kotlin"

    View Slide

  17. object extract {
    infix fun firstLetterOf(target: String)
    = target[0].toString()
    }

    View Slide

  18. object extract {
    infix fun firstLetterOf(target: String)
    = target[0].toString()
    }
    val first = extract firstLetterOf "Awesome"

    View Slide

  19. View Slide

  20. object acronymn {
    infix fun from(target: String) = Chainer(
    initials = mutableListOf(),
    seed = target
    )
    }

    View Slide

  21. object acronymn {
    infix fun from(target: String) = Chainer(
    initials = mutableListOf(),
    seed = target
    )
    }

    View Slide

  22. class Chainer(
    private val initials: MutableList,
    seed: String) {
    init {
    initials += seed.first().toString()
    }

    View Slide

  23. class Chainer(…) {
    init {
    initials += seed.first().toString()
    }
    infix fun and(another: String) =
    Chainer(initials, another)

    View Slide

  24. class Chainer(…) {
    init {
    initials += seed.first().toString()
    }
    infix fun and(another: String) =
    Chainer(initials, another)

    View Slide

  25. sealed class Joiner
    object noPunctuaction : Joiner()
    object dots : Joiner()

    View Slide

  26. class Chainer(…) {
    infix fun joinedWith(option: Joiner) =
    initials.reduce { previous, letter !"
    val next = when (option) {
    is noPunctuaction !" letter
    is dots !" "$letter."
    }
    "$previous$next"
    }

    View Slide

  27. class Chainer(…) {
    infix fun joinedWith(option: Joiner) =
    initials.reduce { previous, letter !"
    val next = when (option) {
    is noPunctuaction !" letter
    is dots !" "$letter."
    }
    "$previous$next"
    }

    View Slide

  28. class Chainer(…) {
    infix fun joinedWith(option: Joiner) =
    initials.reduce { previous, letter !"
    val next = when (option) {
    is noPunctuaction !" letter
    is dots !" "$letter."
    }
    "$previous$next"
    }

    View Slide

  29. class Chainer(…) {
    infix fun joinedWith(option: Joiner) =
    initials.reduce { previous, letter !"
    val next = when (option) {
    is noPunctuaction !" letter
    is dots !" "$letter."
    }
    "$previous$next"
    }

    View Slide

  30. val dsl = acronymn from
    "Domain" and
    "Specific" and
    "Language" joinedWith
    noPunctuaction //DSL

    View Slide

  31. val dsl = acronymn from
    "Domain" and
    "Specific" and
    "Language" joinedWith
    dots //D.S.L

    View Slide

  32. OPERATOR
    OVERLOADS

    View Slide

  33. val items = mutableListOf(1, 2, 3)
    items += 4

    View Slide

  34. val items = mutableListOf(1, 2, 3)
    items += 4
    /#$
    * Adds the specified [element] to this mutable collection.
    %&
    @kotlin.internal.InlineOnly
    public inline operator fun MutableCollection.plusAssign(element: T) {
    this.add(element)
    }

    View Slide

  35. typealias IntPair = Pair
    val calendar by lazy {
    Calendar.getInstance()
    }

    View Slide

  36. operator fun Date.plus(args: IntPair): Date {
    calendar.time = this
    calendar.add(args.first, args.second)
    return calendar.time
    }
    operator fun Date.minus(args: IntPair): Date {
    calendar.time = this
    calendar.add(args.first, -args.second)
    return calendar.time
    }

    View Slide

  37. operator fun Date.plus(args: IntPair): Date {
    calendar.time = this
    calendar.add(args.first, args.second)
    return calendar.time
    }
    operator fun Date.minus(args: IntPair): Date {
    calendar.time = this
    calendar.add(args.first, -args.second)
    return calendar.time
    }

    View Slide

  38. fun Int.days() =
    Calendar.DAY_OF_YEAR to this
    fun Int.months() =
    Calendar.MONTH to this
    val atPast = Date() - 2.months()
    val atFuture = Date() + 15.days()

    View Slide

  39. val Int.days: IntPair
    get() = Calendar.DAY_OF_YEAR to this
    val Int.months: IntPair
    get() = Calendar.MONTH to this

    View Slide

  40. val past = Date() - 2.months
    val future = Date() + 15.days

    val Int.days: IntPair
    get() = Calendar.DAY_OF_YEAR to this
    val Int.months: IntPair
    get() = Calendar.MONTH to this

    View Slide

  41. A The problem
    like with spannables
    on Android

    View Slide

  42. val toSpan = SpannableStringBuilder()
    val start = toSpan.length
    toSpan.append("First Part")
    toSpan.setSpan(
    UnderlineSpan(),
    start,
    toSpan.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
    )
    toSpan.setSpan(
    StyleSpan(android.graphics.Typeface.BOLD),
    start,
    toSpan.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
    )
    toSpan.append("not bold or underlined")

    View Slide

  43. operator fun SpannableString.plus(s: String) =
    SpannableString(TextUtils.concat(this, s))
    operator fun SpannableString.plus(s: SpannableString) =
    SpannableString(TextUtils.concat(this, s))

    View Slide

  44. fun span(given: CharSequence, span: Any): SpannableString {
    val spannable =
    if (given is String) SpannableString(given)
    else given as? SpannableString ?: throw CannotBeSpanned
    return spannable.apply {
    setSpan(span, 0, length, SPAN_EXCLUSIVE_EXCLUSIVE)
    }
    }
    object CannotBeSpanned : IllegalArgumentException(
    "Cannot apply span. Should be String or SpannableString"
    )

    View Slide

  45. // Add more hooks to same span() function
    fun italic(given: CharSequence) =
    span(given, StyleSpan(Typeface.ITALIC))
    fun underline(given: CharSequence) =
    span(given, UnderlineSpan())
    fun bold(given: CharSequence) =
    span(given, StyleSpan(Typeface.BOLD))

    View Slide

  46. val spanned =
    "normal" +
    bold("bold") +
    italic("italic")
    label.setText(spanned)

    View Slide

  47. LAMBDAS +
    RECEIVERS

    View Slide

  48. fun x(lambda: () !" Unit) { lambda() }
    fun y(lambda: () !" Int) = lambda()
    val x: () !" Int = { TODO() }
    val result = x()

    View Slide

  49. val add = fun(a: Int, b: Int) = a + b
    fun calculate(func: (Int, Int) !" Int) {
    func.invoke(2,2)
    }
    // Trailling Notation
    calculate { a, b !" a + b }
    // Normal notation
    calculate(add)

    View Slide

  50. class Receiver(val data: Int)
    // Kotlin allows us to add an extension function
    // literal to a type. Such lambda acquires the
    // properties of non-static method in the context
    val addOne: Receiver.() !" Int = {
    data + 1
    }
    val two = addOne(Receiver(1))
    val twoAgain = Receiver(1).addOne()

    View Slide

  51. fun receive(block: Receiver.() !" Unit) {
    val r = Receiver(data = 1)
    block(r)
    // r.block() is exactly the same
    }
    receive {
    println(data) // 1
    }

    View Slide

  52. View Slide

  53. https://graphql.org/swapi-graphql/

    View Slide

  54. val starWars = query {
    allFilms {
    film { title director }
    }
    }
    println(starWars)
    > query { allFilms { film { title director } } }

    View Slide

  55. val starWars = query {
    '( TODO
    }

    View Slide

  56. val starWars = query {
    '( TODO
    }
    fun query(): String {
    return "query { ${ '( Something here } }"
    }

    View Slide

  57. val starWars = query {
    '( TODO
    }
    fun query(setup: AllFilmsBlock.() !" String) =
    "query { ${setup(AllFilmsBlock())} }"

    View Slide

  58. val starWars = query {
    '( TODO
    }
    fun query(setup: AllFilmsBlock.() !" String) =
    "query { ${setup(AllFilmsBlock())} }"

    View Slide

  59. val starWars = query {
    '( TODO
    }
    fun query(setup: AllFilmsBlock.() !" String) =
    "query { ${setup(AllFilmsBlock())} }"

    View Slide

  60. fun query(setup: AllFilmsBlock.() !" String) =
    "query { ${setup(AllFilmsBlock())} }"
    val starWars = query {
    allFilms { // TODO }
    }
    class AllFilmsBlock {
    fun allFilms(setup: FilmBlock.() !" String) =
    "allFilms { ${setup(FilmBlock())} }"
    }

    View Slide

  61. fun query(setup: AllFilmsBlock.() !" String) =
    "query { ${setup(AllFilmsBlock())} }"
    val starWars = query {
    allFilms { // TODO }
    }
    class AllFilmsBlock {
    fun allFilms(setup: FilmBlock.() !" String) =
    "allFilms { ${setup(FilmBlock())} }"
    }

    View Slide

  62. val starWars = query {
    allFilms {
    film {
    // TODO
    }
    }
    }

    View Slide

  63. val starWars = query {
    allFilms { film { } }
    }
    class FilmBlock {
    fun film(setup: FilmFields.() !" String) =
    with(FilmFields()) {
    setup()
    "film { ${fields()} }"
    }
    }

    View Slide

  64. val starWars = query {
    allFilms { film { } }
    }
    class FilmBlock {
    fun film(setup: FilmFields.() !" String) =
    with(FilmFields()) {
    setup()
    "film { ${fields()} }"
    }
    }

    View Slide

  65. val starWars = query {
    allFilms { film { } }
    }
    class FilmBlock {
    fun film(setup: FilmFields.() !" String) =
    with(FilmFields()) {
    setup()
    "film { ${fields()} }"
    }
    }

    View Slide

  66. class FilmFiels {
    private val picked = mutableListOf()
    fun fields() = TODO()
    val title: String
    get() {
    picked.add(TITLE)
    return TITLE
    }
    }

    View Slide

  67. class FilmFields {
    val title: String
    get() { … }
    val director: String
    get() { … }
    private companion object {
    const val TITLE = "title"
    const val DIRECTOR = "director"
    }
    }

    View Slide

  68. class FilmFields {
    fun fields() =
    picked.distinct().joinToString(separator = " ")
    val title: String
    get() { … }
    val director: String
    get() { … }
    private companion object { … }
    }

    View Slide

  69. class FilmFields {
    fun fields() =
    picked.distinct().joinToString(separator = " ")
    val title: String
    get() { … }
    val director: String
    get() { … }
    private companion object { … }
    }

    View Slide

  70. val starWars = query {
    allFilms {
    film {
    title
    director
    }
    }
    }
    println(starWars)

    View Slide

  71. View Slide

  72. // Plain old mockWebServer
    val server = MockWebServer()
    val response = MockResponse().apply {
    setResponseCode(503)
    setBody("Ops!")
    }
    server.enqueue(response)
    server.start()

    View Slide

  73. class MockResponseBuilder(
    '( Aliases
    var code: Int = 0,
    var response: String? = null) {
    fun mockResponse() = MockResponse().apply {
    setResponseCode(code)
    setBody(response)
    }
    }

    View Slide

  74. class QueueBuilder {
    val mocks = mutableListOf()
    fun enqueueMock(setup: MockResponseBuilder.() !" Unit) {
    val builder = MockResponseBuilder()
    builder.setup()
    mocks.add(builder.mockResponse())
    }
    }

    View Slide

  75. typealias BuildMock = MockResponseBuilder.() !" Unit
    class QueueBuilder {
    val mocks = mutableListOf()
    fun enqueueMock(setup: BuildMock) =
    MockResponseBuilder().run {
    setup()
    mocks.add(mockResponse())
    }
    }

    View Slide

  76. typealias BuildMock = MockResponseBuilder.() !" Unit
    class QueueBuilder {
    val mocks = mutableListOf()
    fun enqueueMock(setup: BuildMock) =
    MockResponseBuilder().run {
    setup()
    mocks.add(mockResponse())
    }
    }

    View Slide

  77. typealias BuildQueue = QueueBuilder.() !" Unit
    fun newServer(setup: BuildQueue): MockWebServer =
    with(MockWebServer()) {
    '( ????
    return this
    }

    View Slide

  78. typealias BuildQueue = QueueBuilder.() !" Unit
    fun newServer(setup: BuildQueue): MockWebServer =
    with(MockWebServer()) {
    QueueBuilder().run {
    setup()
    mocks.forEach { enqueue(it) }
    }
    return this
    }

    View Slide

  79. typealias BuildQueue = QueueBuilder.() !" Unit
    fun newServer(setup: BuildQueue): MockWebServer =
    with(MockWebServer()) {
    QueueBuilder().run {
    setup()
    mocks.forEach { enqueue(it) }
    }
    return this
    }

    View Slide

  80. // DSL based
    val server = newServer {
    enqueueMock {
    code = 200
    response = "OK"
    }
    }
    server.start()
    // Plain old mockWebServer
    val server = MockWebServer()
    val response = MockResponse().apply {
    setResponseCode(503)
    setBody("Ops!")
    }
    server.enqueue(response)
    server.start()

    View Slide

  81. View Slide

  82. FINAL

    REMARKS

    View Slide

  83. CONCLUSIONS
    • DSLs are fun (with no pun)
    • DSL-building offer great insights over Kotlin features!
    • DSLs should work to improve an existing domain, not replace it
    • Design your own DSLs for fun and profit !

    View Slide

  84. https://speakerdeck.com/ubiratansoares

    View Slide

  85. UBIRATAN
    SOARES
    Computer Scientist by ICMC/USP
    Software Engineer, curious guy
    Google Expert for Android and Kotlin
    Teacher, speaker, etc, etc

    View Slide

  86. THANK YOU
    @ubiratanfsoares
    ubiratansoares.github.io
    https://br.linkedin.com/in/ubiratanfsoares

    View Slide