DSLs in a Kotlin Way

DSLs in a Kotlin Way

Presentation about Kotlin machinery for DSLs grammars.

Presented at the following events

- Kotlin Night Cascavél (April / 2018)
- GDG-SP Android Meetup #58 (June / 2018)
- Online Hangout with GDGs Ourinhos + Foz + Divinópolis (June / 2018)

D4b7a3e2ed10f86e0b52498713ba2601?s=128

Ubiratan Soares

April 27, 2018
Tweet

Transcript

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

  2. None
  3. None
  4. INTERNAL LANGUAGE EXTERNAL LANGUAGE

  5. SELECT * FROM accounts WHERE id=12345

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

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

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

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

    } div { +"Ho!" span { +"Lets go!" } } } } https://github.com/Kotlin/kotlinx.html
  10. --- punctuation --- overhead +++ grammar +++ structure

  11. None
  12. None
  13. None
  14. TYPE ALIASES

  15. typealias UserId = String fun findUser(by: UserId) { TODO() }

  16. typealias Seconds = Int typealias Duration = Int fun ellapsed(amount:

    Seconds): Duration { TODO() }
  17. // Can be used with function literals typealias Factory =

    () !" User // Shine with Generics typealias StringTriplet = Triple<String, String, String> // Nice to abbreviate long names typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
  18. SEALED CLASSES

  19. typealias Fee = Float sealed class Tax { class FederalTax(val

    percent: Fee) : Tax() object Free : Tax() }
  20. typealias Fee = Float sealed class Tax { class FederalTax(val

    percent: Fee) : Tax() object Free : Tax() } fun evaluate(incoming: Tax) = when (incoming) { is Tax.FederalTax !" incoming.percent is Tax.Free !" 0.0f }
  21. typealias Fee = Float sealed class Tax class FederalTax(val percent:

    Fee) : Tax() object Free : Tax() fun evaluate(incoming: Tax) = when (incoming) { is FederalTax !" incoming.percent is Free !" 0.0f }
  22. INFIX NOTATION

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

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

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

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

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

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

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

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

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

    fun and(another: String) = Chainer(initials, another)
  33. class Chainer(…) { infix fun joinedWith(option: Joiner) = initials.reduce {

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

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

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

    previous, letter !" val next = when (option) { is noPunctuaction !" letter is dots !" "$letter." } "$previous$next" }
  37. sealed class Joiner object noPunctuaction : Joiner() object dots :

    Joiner()
  38. val dsl = acronymn from "Domain" and "Specific" and "Language"

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

    joinedWith dots //D.S.L
  40. EXTENSIONS

  41. fun Boolean.nor(another: Boolean) = or(another).not() val nored = true.nor(false)

  42. infix fun Boolean.nand(some: Boolean) = and(value).not()

  43. infix fun Boolean.nand(some: Boolean) = and(value).not() val nanded = false

    nand true
  44. val calendar by lazy { Calendar.getInstance() } fun Date.tomorrow(): Date

    { calendar.time = this calendar.add(Calendar.DATE, 1) return calendar.time }
  45. val calendar by lazy { Calendar.getInstance() } fun Date.tomorrow(): Date

    { calendar.time = this calendar.add(Calendar.DATE, 1) return calendar.time }
  46. fun Date.yesterday(): Date { … } fun Date.before(some: Date): Boolean

    { … } fun Date.after(some: Date): Boolean { … } fun Date.isHolyday(): Boolean { … } fun Date.iso8601String(): String { … }
  47. OPERATOR OVERLOADS

  48. typealias IntPair = Pair<Int, Int> operator fun Date.plus(args: IntPair): Date

    { calendar.time = this calendar.add(args.first, args.second) return calendar.time }
  49. typealias IntPair = Pair<Int, Int> operator fun Date.minus(args: IntPair): Date

    { calendar.time = this calendar.add(args.first, -args.second) return calendar.time }
  50. fun Int.months() = Calendar.MONTH to this val atPast = Date()

    - 2.months() fun Int.days() = Calendar.DAY_OF_YEAR to this val atFuture = Date() + 15.days() typealias IntPair = Pair<Int, Int>
  51. fun Int.days() = Calendar.DAY_OF_YEAR to this val Int.days: IntPair get()

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

    = days() fun Int.months() = Calendar.MONTH to this val Int.months: IntPair get() = months()
  53. val now = Date() val past = now - 2.months

    val future = now + 15.days NO MORE PARENTHESIS !!!
  54. LAMBDAS + RECEIVERS

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

    () !" Int) = lambda() val x: () !" Int = { TODO() } val result = x()
  56. 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)
  57. // Lambdas are just another representation for // functions; they

    are not DECLARED the same way // as function types val add = fun(a: Int, b: Int) = a + b val subtract: (Int, Int) !" Int = { a, b !" a - b } but passed / used as expressions as well fun report(operation: (Int, Int) !" Int) = println(operation.invoke(2, 2))
  58. 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 class val addOne: Receiver.() !" Int = { data + 1 } val two = addOne(Receiver(1)) val twoAgain = Receiver(1).addOne()
  59. fun receive(block: Receiver.() !" Unit) { val r = Receiver(data

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

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

  63. fun main(args: Array<String>) { val starWars = query { allFilms

    { films { title director releaseDate } } } }
  64. val starWars = query { #$ TODO }

  65. val starWars = query { #$ TODO } fun query():

    String { #$ Something here return "query { ${ #$ Something here } }" }
  66. val starWars = query { #$ TODO } fun query(build:

    FilmsConnection.() -> Unit): String { val connection = FilmsConnection() connection.build() return "query { ${connection.block()} }" }
  67. val starWars = query { #$ TODO } class FilmsConnection

    { fun block() = "allFilms { #$ Something here }" } fun query(build: FilmsConnection.() !" Unit): String { val connection = FilmsConnection() connection.build() return "query { ${connection.block()} }" }
  68. val starWars = query { allFilms { films { //

    TODO } } }
  69. class FilmsConnection { private val films = Films() fun allFilms(build:

    Films.() !" Unit) = films.build() fun block() = "allFilms { ${films.block()} }" }
  70. class FilmsConnection { private val films = Films() fun allFilms(build:

    Films.() -> Unit) = films.build() fun block() = "allFilms { ${films.block()} }" }
  71. class FilmsConnection { private val films = Films() fun allFilms(build:

    Films.() -> Unit) = films.build() fun block() = "allFilms { ${films.block()} }" } class Films { private val film = Film() fun films(build: Film.() -> Unit) = film.build() fun block() = "films { ${film.block()} }" }
  72. class Film { fun block() = TODO() }

  73. class Film { private val picked = mutableListOf<String>() fun block()

    = TODO() val title: String get() { picked.add(TITLE) return TITLE } private companion object { const val TITLE = "title" } }
  74. class Film { private val picked = mutableListOf<String>() fun block()

    = TODO() val title: String get() { picked.add(TITLE) return TITLE } private companion object { const val TITLE = "title" } }
  75. class Film { val title: String get() { … }

    val director: String get() { … } val releaseDate: String get() { … } private companion object { const val TITLE = "title" const val DIRECTOR = "director" const val RELEASE_DATE = "releaseDate" } }
  76. class Film { fun block() = picked.distinct().joinToString(separator = " ")

    val title: String get() { … } val director: String get() { … } val releaseDate: String get() { … } private companion object { … } }
  77. class Film { fun block() = picked.distinct().joinToString(separator = " ")

    val title: String get() { … } val director: String get() { … } val releaseDate: String get() { … } private companion object { … } }
  78. fun main(args: Array<String>) { val starWars = query { allFilms

    { films { title director releaseDate } } } println(starWars) // query { allFilms { films { title director releaseDate } } } }
  79. None
  80. // Plain old mockWebServer val server = MockWebServer() val response

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

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

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

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

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

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

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

    = with(MockWebServer()) { QueueBuilder().run { setup() mocks.forEach { enqueue(it) } } return this }
  88. val server = newServer { enqueueMock { code = 200

    response = "OK" } } server.start()
  89. None
  90. FINAL
 REMARKS

  91. 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 !
  92. CHECK THESE ONES ! https://github.com/kotlintest/kotlintest https://github.com/JetBrains/Exposed https://github.com/Kotlin/anko Anko Kotlintest Exposed

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

    Google Expert for Android and Kotlin Teacher, speaker, etc, etc
  94. https://speakerdeck.com/ubiratansoares

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