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

DSL - The Kotlin Way

DSL - The Kotlin Way

Supporting slides from my talk about internal DSLs using Kotlin features. This is v3.0 of this talk!

Delivered in the following conferences / events

- DevFest Bucharest (November / 2019)

Ubiratan Soares

November 15, 2019
Tweet

More Decks by Ubiratan Soares

Other Decks in Programming

Transcript

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

    } div { +"Ho!" span { +"Lets go!" } } } } https://github.com/Kotlin/kotlinx.html
  2. class Extractor { infix fun firstLetter(target: String) = target[0].toString() }

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

    val k = Extractor() firstLetter "Kotlin" NO PARENTHESIS
  4. class Tinker( private val collected: MutableList<String>, incoming: String ) {

    init { collected += incoming.first().toString() } }
  5. class Tinker( private val collected: MutableList<String>, incoming: String ) {

    init { collected += incoming.first().toString() } infix fun and(another: String) = Tinker(collected, another) }
  6. class Tinker(#$%) { infix fun gluedWith(option: Joiner) = collected.reduce {

    previous, letter &' val next = when (option) { is nothing &' letter is dots &' ".$letter" } } }
  7. class Tinker(#$%) { infix fun gluedWith(option: Joiner) = collected.reduce {

    previous, letter &' val next = when (option) { is nothing &' letter is dots &' ".$letter" } "$previous$next" } }
  8. class InitialsTinker( private val collected: MutableList<String>, incoming: String ) {

    init { collected += incoming.first().toString() } infix fun and(another: String) = InitialsTinker(collected, another) infix fun gluedWith(option: Joiner) = collected.reduce { previous, letter &' val next = when (option) { is nothing &' letter is dots &' ".$letter" } "$previous$next" } }
  9. IN THE WILD https://github.com/kotlintest KotlinTest custom matchers class KotlinTestDemo :

    StringSpec({ "assert string length" { "DSL".length shouldBe 3 } })
  10. val list = mutableListOf(1, 2, 3) list += 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) }
  11. fun addMonths(target: Date, months: Int): Date { val calendar =

    Calendar.getInstance() calendar.time = target calendar.add(Calendar.MONTH, months) }
  12. fun addMonths(target: Date, months: Int): Date { val calendar =

    Calendar.getInstance() calendar.time = target calendar.add(Calendar.MONTH, months) return calendar.time }
  13. fun addMonths(target: Date, months: Int): Date { val calendar =

    Calendar.getInstance() calendar.time = target calendar.add(Calendar.MONTH, months) return calendar.time } val twoMonthsLater = addMonths(Date(), 2)
  14. fun subtractDays(target: Date, days: Int): Date { val calendar =

    Calendar.getInstance() calendar.time = target calendar.add(Calendar.DAY_OF_YEAR, -days) return calendar.time } val oneWeekAgo = subtractDays(Date(), 7)
  15. data class DateIncrement( val dateField : Int, val amount: Int

    ) val Int.days: DateIncrement get() = DateIncrement(Calendar.DAY_OF_YEAR, this) val Int.months: DateIncrement get() = DateIncrement(Calendar.MONTH, this) val Int.years: DateIncrement get() = DateIncrement(Calendar.YEAR, this)
  16. fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) {

    val (field, amount) = increment val translatedAmount = when(operation) { DECREASE &' -amount INCREASE &' amount } }
  17. fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) {

    val (field, amount) = increment val translatedAmount = when(operation) { DECREASE &' -amount INCREASE &' amount } time = this@applyIncrement }
  18. fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) {

    val (field, amount) = increment val translatedAmount = when(operation) { DECREASE &' -amount INCREASE &' amount } time = this@applyIncrement add(field, translatedAmount) }
  19. fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) {

    val (field, amount) = increment val translatedAmount = when(operation) { DECREASE &' -amount INCREASE &' amount } time = this@applyIncrement add(field, translatedAmount) time }
  20. operator fun Date.plus(increment: DateIncrement): Date = applyIncrement(increment, INCREASE) operator fun

    Date.minus(increment: DateIncrement): Date = applyIncrement(increment, DECREASE) val atPast = Date() - 2.days val atFuture = Date() + 2.years enum class IncrementOperation { INCREASE, DECREASE }
  21. val spannable = SpannableString("Pain is not enough!") spannable.setSpan( ForegroundColorSpan(Color.RED), 0,

    3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) spannable.setSpan( StyleSpan(BOLD), 12, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) // plain old SpannableString.java
  22. val spanned = buildSpannedString { bold { append("This") } append("

    is ") italic { append("fine") } } // Shiny new SpannableStringBuilder.kt
  23. ,- SomeActivity.kt val dsl = bold("DSLs") + " are "

    + italic("awesome") labelHello.text = dsl
  24. fun bold(given: CharSequence) = TODO() span(given, StyleSpan(Typeface.BOLD)) fun italic(given: CharSequence)

    = TODO() span(given, StyleSpan(Typeface.ITALIC)) val dsl = bold("DSLs") + " are " + italic("awesome")
  25. fun bold(given: CharSequence) = span(given, StyleSpan(Typeface.BOLD)) fun italic(given: CharSequence) =

    span(given, StyleSpan(Typeface.ITALIC)) val dsl = bold("DSLs") + " are " + italic("awesome")
  26. fun span(target: CharSequence, style: Any) : SpannableString = when (target)

    { is String &' SpannableString(target) is SpannableString &' target }
  27. fun span(target: CharSequence, style: Any) : SpannableString = when (target)

    { is String &' SpannableString(target) is SpannableString &' target else &' throw CannotBeSpanned }
  28. fun span(target: CharSequence, style: Any) : SpannableString = when (target)

    { is String &' SpannableString(target) is SpannableString &' target else &' throw CannotBeSpanned }.let { spannable &' }
  29. fun span(target: CharSequence, style: Any) : SpannableString = when (target)

    { is String &' SpannableString(target) is SpannableString &' target else &' throw CannotBeSpanned }.let { spannable &' spannable.apply { setSpan(style, 0, length, SPAN_EXCLUSIVE_EXCLUSIVE) } }
  30. fun span(target: CharSequence, style: Any) : SpannableString = when (target)

    { is String &' SpannableString(target) is SpannableString &' target else &' throw CannotBeSpanned }.let { spannable &' spannable.apply { setSpan(style, 0, length, SPAN_EXCLUSIVE_EXCLUSIVE) } } object CannotBeSpanned : IllegalArgumentException()
  31. operator fun SpannableString.plus(s: SpannableString) = SpannableString(TextUtils.concat(this, s)) operator fun SpannableString.plus(s:

    String) = SpannableString(TextUtils.concat(this, s)) val spanned = bold("DSLs") + " are " + italic("awesome")
  32. IN THE WILD https://github.com/ReactiveX/RxKotlin RxKotlin CompositeDisposable enhancement private val composite

    = CompositeDisposable() private val stream = Observable.just(1, 2, 3) composite += stream.subscribe()
  33. data class Person( var name: String, var married: Boolean )

    fun Person.freeAgain() { this.married = false }
  34. data class Person( var name: String, var married: Boolean )

    fun Person.freeAgain() { this.married = false } val changeToSingle = Person./freeAgain
  35. fun theEndOfHistory() { val unhappy = Person("Alice Rodriguez", true) val

    optimistic = Person("Bob Rodriguez", true) unhappy.freeAgain() println(unhappy) }
  36. fun theEndOfHistory() { val unhappy = Person("Alice Rodriguez", true) val

    optimistic = Person("Bob Rodriguez", true) unhappy.freeAgain() println(unhappy) changeToSingle(optimistic) println(optimistic) }
  37. val changeToSingle = Person./freeAgain val unhappy = Person("Alice Rodriguez", true)

    val optimistic = Person("Bob Rodriguez", true) marriedNoMore(unhappy) changeToSingle(optimistic) val marriedNoMore : Person.() &' Unit = { this.married = false } Called in the same way !!!
  38. fun theTrueEndForAlice(block : Person.() &' Unit) { val newPerson =

    Person("Alice", false) newPerson.block() println(newPerson) }
  39. fun theTrueEndForAlice(block : Person.() &' Unit) { val newPerson =

    Person("Alice", false) newPerson.block() println(newPerson) } fun main() { theTrueEndForAlice { name = "Alice Springs" } } Trailling Notation !!!!
  40. The secret sauce A Lambda extension used as the last

    argument of a high order function (plus trailling notation !)
  41. fun shareMyCatalog() { val myCatalog: Catalog = TODO() println(myCatalog) }

    data class Movie( val title: String, val rating: Int ) data class Catalog( val movies: List<Movie>, val updatedAt: Date )
  42. data class Catalog( val movies: List<Movie>, val updatedAt: Date )

    fun shareMyCatalog() { val myCatalog: Catalog = TODO() println(myCatalog) }
  43. data class Catalog( val movies: List<Movie>, val updatedAt: Date )

    class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun build() = Catalog(movies, updatedAt) } fun shareMyCatalog() { val myCatalog: Catalog = TODO() println(myCatalog) }
  44. data class Catalog( val movies: List<Movie>, val updatedAt: Date )

    class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun build() = Catalog(movies, updatedAt) } fun catalog(block: CatalogBuilder.() &' Unit) = CatalogBuilder().apply(block).build() fun shareMyCatalog() { val myCatalog = catalog { } println(myCatalog) }
  45. data class Catalog( val movies: List<Movie>, val updatedAt: Date )

    class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun build() = Catalog(movies, updatedAt) } fun catalog(block: CatalogBuilder.() &' Unit) = CatalogBuilder().apply(block).build() fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }
  46. data class Movie( val title: String, val rating: Int )

    fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }
  47. data class Movie( val title: String, val rating: Int )

    class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }
  48. data class Movie( val title: String, val rating: Int )

    class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun build() = Catalog(movies, updatedAt) } fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }
  49. data class Movie( val title: String, val rating: Int )

    class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { val movie = MovieBuilder().apply(block).build() movies += movie } fun build() = Catalog(movies, updatedAt) } fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }
  50. fun shareMyCatalog() { val myCatalog = catalog { updatedAt =

    Date() - 1.days } println(myCatalog) } data class Catalog( val movies: List<Movie>, val updatedAt: Date ) data class Movie( val title: String, val rating: Int ) class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }
  51. fun shareMyCatalog() { val myCatalog = catalog { updatedAt =

    Date() - 1.days movie { title = “TaxiDriver" rating = 9 } } println(myCatalog) } data class Catalog( val movies: List<Movie>, val updatedAt: Date ) data class Movie( val title: String, val rating: Int ) class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }
  52. fun shareMyCatalog() { val myCatalog = catalog { updatedAt =

    Date() - 1.days movie { title = “Taxi Driver" rating = 9 } movie { title = "The GoodFellas" rating = 10 } } println(myCatalog) } data class Catalog( val movies: List<Movie>, val updatedAt: Date ) data class Movie( val title: String, val rating: Int ) class MovieBuilder { var title: String = "" var rating: Int = 0 fun build() = Movie(title, rating) } class CatalogBuilder { var updatedAt: Date = Date() var movies: List<Movie> = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }
  53. IN THE WILD (I) Kotlinx.HTML val tree = createHTMLDocument().html {

    body { h1 { +"Hey" } div { +"Ho!" span { +"Lets go!" } } } } https://github.com/Kotlin/kotlinx.html
  54. IN THE WILD (II) Ktor val server = embeddedServer(Netty, port

    = 8080) { routing { get("/") { call.respondText("Hello World!", ContentType.Text.Plain) } } } server.start(wait = true) https://ktor.io/
  55. IN THE WILD (III) Spek object SetFeature: Spek({ Feature("Set") {

    Scenario("adding items") { When("adding foo") { set.add(“foo") } Then("it should have a size of 1") { assertEquals(1, set.size) } } } https://spekframework.org
  56. CALL TO ACTION ! • Learn more about invoking instances

    in Kotlin • Learn about scoping with @DSLMarker • Design your own DSLs for fun and profit !
  57. UBIRATAN SOARES Brazilian Computer Scientist Senior Software Engineer @ N26

    GDE for Android and Kotlin @ubiratanfsoares ubiratansoares.dev
  58. THANK YOU Do you want to write awesome Kotlin? N26

    is hiring! https://n26.com/en/careers