Pro Yearly is on sale from $80 to $50! »

DSLs in a Kotlin Way - V2

D4b7a3e2ed10f86e0b52498713ba2601?s=47 Ubiratan Soares
July 21, 2018
92

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)

D4b7a3e2ed10f86e0b52498713ba2601?s=128

Ubiratan Soares

July 21, 2018
Tweet

Transcript

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

    VERSION 2.0
  2. None
  3. SELECT * FROM accounts WHERE id=12345

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

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

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

  7. val tree = createHTMLDocument().html { body { h1 { +"Hey"

    } div { +"Ho!" span { +"Lets go!" } } } } https://github.com/Kotlin/kotlinx.html
  8. None
  9. None
  10. None
  11. Disclaimer : Strategic jump

  12. Type Aliases Sealed Classes Extension Functions Extension Properties ETC

  13. VERSION 1.0

  14. INFIX NOTATION

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

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

    val k = Extractor() firstLetter "Kotlin"
  17. object extract { infix fun firstLetterOf(target: String) = target[0].toString() }

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

    val first = extract firstLetterOf "Awesome"
  19. None
  20. object acronymn { infix fun from(target: String) = Chainer( initials

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

    = mutableListOf(), seed = target ) }
  22. class Chainer( private val initials: MutableList<String>, seed: String) { init

    { initials += seed.first().toString() }
  23. class Chainer(…) { init { initials += seed.first().toString() } infix

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

    fun and(another: String) = Chainer(initials, another)
  25. sealed class Joiner object noPunctuaction : Joiner() object dots :

    Joiner()
  26. class Chainer(…) { infix fun joinedWith(option: Joiner) = initials.reduce {

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

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

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

    previous, letter !" val next = when (option) { is noPunctuaction !" letter is dots !" "$letter." } "$previous$next" }
  30. val dsl = acronymn from "Domain" and "Specific" and "Language"

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

    joinedWith dots //D.S.L
  32. OPERATOR OVERLOADS

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

  34. val items = mutableListOf(1, 2, 3) items += 4 /#$

    * Adds the specified [element] to this mutable collection. %& @kotlin.internal.InlineOnly public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) { this.add(element) }
  35. typealias IntPair = Pair<Int, Int> val calendar by lazy {

    Calendar.getInstance() }
  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 }
  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 }
  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()
  39. val Int.days: IntPair get() = Calendar.DAY_OF_YEAR to this val Int.months:

    IntPair get() = Calendar.MONTH to this
  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
  41. A The problem like with spannables on Android

  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")
  43. operator fun SpannableString.plus(s: String) = SpannableString(TextUtils.concat(this, s)) operator fun SpannableString.plus(s:

    SpannableString) = SpannableString(TextUtils.concat(this, s))
  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" )
  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))
  46. val spanned = "normal" + bold("bold") + italic("italic") label.setText(spanned)

  47. LAMBDAS + RECEIVERS

  48. fun x(lambda: () !" Unit) { lambda() } fun y(lambda:

    () !" Int) = lambda() val x: () !" Int = { TODO() } val result = x()
  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)
  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()
  51. fun receive(block: Receiver.() !" Unit) { val r = Receiver(data

    = 1) block(r) // r.block() is exactly the same } receive { println(data) // 1 }
  52. None
  53. https://graphql.org/swapi-graphql/

  54. val starWars = query { allFilms { film { title

    director } } } println(starWars) > query { allFilms { film { title director } } }
  55. val starWars = query { '( TODO }

  56. val starWars = query { '( TODO } fun query():

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

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

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

    AllFilmsBlock.() !" String) = "query { ${setup(AllFilmsBlock())} }"
  60. fun query(setup: AllFilmsBlock.() !" String) = "query { ${setup(AllFilmsBlock())} }"

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

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

    TODO } } }
  63. val starWars = query { allFilms { film { }

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

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

    } } class FilmBlock { fun film(setup: FilmFields.() !" String) = with(FilmFields()) { setup() "film { ${fields()} }" } }
  66. class FilmFiels { private val picked = mutableListOf<String>() fun fields()

    = TODO() val title: String get() { picked.add(TITLE) return TITLE } }
  67. class FilmFields { val title: String get() { … }

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

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

    val title: String get() { … } val director: String get() { … } private companion object { … } }
  70. val starWars = query { allFilms { film { title

    director } } } println(starWars)
  71. None
  72. // Plain old mockWebServer val server = MockWebServer() val response

    = MockResponse().apply { setResponseCode(503) setBody("Ops!") } server.enqueue(response) server.start()
  73. class MockResponseBuilder( '( Aliases var code: Int = 0, var

    response: String? = null) { fun mockResponse() = MockResponse().apply { setResponseCode(code) setBody(response) } }
  74. class QueueBuilder { val mocks = mutableListOf<MockResponse>() fun enqueueMock(setup: MockResponseBuilder.()

    !" Unit) { val builder = MockResponseBuilder() builder.setup() mocks.add(builder.mockResponse()) } }
  75. typealias BuildMock = MockResponseBuilder.() !" Unit class QueueBuilder { val

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

    mocks = mutableListOf<MockResponse>() fun enqueueMock(setup: BuildMock) = MockResponseBuilder().run { setup() mocks.add(mockResponse()) } }
  77. typealias BuildQueue = QueueBuilder.() !" Unit fun newServer(setup: BuildQueue): MockWebServer

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

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

    = with(MockWebServer()) { QueueBuilder().run { setup() mocks.forEach { enqueue(it) } } return this }
  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()
  81. None
  82. FINAL
 REMARKS

  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 !
  84. https://speakerdeck.com/ubiratansoares

  85. UBIRATAN SOARES Computer Scientist by ICMC/USP Software Engineer, curious guy

    Google Expert for Android and Kotlin Teacher, speaker, etc, etc
  86. THANK YOU @ubiratanfsoares ubiratansoares.github.io https://br.linkedin.com/in/ubiratanfsoares