Slide 1

Slide 1 text

DSLs IN A KOTLIN WAY Ubiratan Soares November / 2019

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

SELECT * FROM accounts WHERE id=12345

Slide 5

Slide 5 text

SELECT * FROM accounts WHERE id=12345 " "

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

TRAILLING NOTATION

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Trailling Lamba, at your service!

Slide 17

Slide 17 text

“A small step for the IDE, but a giant leap for DSLs” - Me, 2019

Slide 18

Slide 18 text

INFIX NOTATION

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

“PLAIN ENGLISH” STATEMENTS

Slide 26

Slide 26 text

initials from “Kotlin" and "Rocks" gluedWith dots !" K.R

Slide 27

Slide 27 text

object initials { infix fun from(target: String) = Tinker( collected = mutableListOf(), incoming = target ) }

Slide 28

Slide 28 text

object initials { infix fun from(target: String) = Tinker( collected = mutableListOf(), incoming = target ) }

Slide 29

Slide 29 text

class Tinker( private val collected: MutableList, incoming: String ) { }

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

class Tinker( private val collected: MutableList, incoming: String ) { init { collected += incoming.first().toString() } infix fun and(another: String) = Tinker(collected, another) }

Slide 32

Slide 32 text

class Tinker(#$%) { infix fun gluedWith(option: Joiner) = TODO() }

Slide 33

Slide 33 text

class Tinker(#$%) { infix fun gluedWith(option: Joiner) = collected.reduce { previous, letter &' } }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

class InitialsTinker( private val collected: MutableList, 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" } }

Slide 37

Slide 37 text

sealed class Joiner object nothing : Joiner() object dots : Joiner()

Slide 38

Slide 38 text

val dsl = initials from "Domain" and "Specific" and "Language" gluedWith nothing //DSL

Slide 39

Slide 39 text

val dsl = initials from "Domain" and "Specific" and "Language" gluedWith dots //D.S.L

Slide 40

Slide 40 text

IN THE WILD https://github.com/kotlintest KotlinTest custom matchers class KotlinTestDemo : StringSpec({ "assert string length" { "DSL".length shouldBe 3 } })

Slide 41

Slide 41 text

OPERATOR OVERLOADS

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

OPERATING WITH java.util.Date

Slide 44

Slide 44 text

fun addMonths(target: Date, months: Int): Date { // TODO }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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)

Slide 49

Slide 49 text

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)

Slide 50

Slide 50 text

fun evaluateDates() { val atPast = Date() - 2.days val atFuture = Date() + 2.years }

Slide 51

Slide 51 text

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)

Slide 52

Slide 52 text

fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) { // TODO }

Slide 53

Slide 53 text

fun Date.applyIncrement( increment: DateIncrement, operation: IncrementOperation): Date = with(Calendar.getInstance()) { val (field, amount) = increment }

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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 }

Slide 56

Slide 56 text

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) }

Slide 57

Slide 57 text

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 }

Slide 58

Slide 58 text

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 }

Slide 59

Slide 59 text

A PROBLEM LIKE ANDROID SPANNABLES

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

val spanned = buildSpannedString { bold { append("This") } append(" is ") italic { append("fine") } } // Shiny new SpannableStringBuilder.kt

Slide 63

Slide 63 text

,- SomeActivity.kt val dsl = bold("DSLs") + " are " + italic("awesome") labelHello.text = dsl

Slide 64

Slide 64 text

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")

Slide 65

Slide 65 text

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")

Slide 66

Slide 66 text

fun span(target: CharSequence, style: Any) : SpannableString =

Slide 67

Slide 67 text

fun span(target: CharSequence, style: Any) : SpannableString = when (target) { }

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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) } }

Slide 73

Slide 73 text

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()

Slide 74

Slide 74 text

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")

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

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()

Slide 77

Slide 77 text

LAMBDAS + RECEIVERS

Slide 78

Slide 78 text

THE END OF A HAPPY HISTORY

Slide 79

Slide 79 text

data class Person( var name: String, var married: Boolean )

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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 !!!

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

fun theTrueEndForAlice(block : Person.() &' Unit) { val newPerson = Person("Alice", false) newPerson.block() println(newPerson) } fun main() { theTrueEndForAlice { name = "Alice Springs" } } Trailling Notation !!!!

Slide 88

Slide 88 text

fun theTrueEndForAlice(block : Person.() &' Unit) { println(Person("Alice", false).apply(block)) } fun main() { theTrueEndForAlice { name = "Alice Springs" } }

Slide 89

Slide 89 text

fun theTrueEndForAlice(block : Person.() &' Unit) { println(Person("Alice", false).apply(block)) } fun main() { theTrueEndForAlice { name = "Alice Springs" } }

Slide 90

Slide 90 text

The secret sauce A Lambda extension used as the last argument of a high order function (plus trailling notation !)

Slide 91

Slide 91 text

TYPE-SAFE BUILDERS

Slide 92

Slide 92 text

fun shareMyCatalog() { val myCatalog: Catalog = TODO() println(myCatalog) } data class Movie( val title: String, val rating: Int ) data class Catalog( val movies: List, val updatedAt: Date )

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

data class Catalog( val movies: List, val updatedAt: Date ) class CatalogBuilder { var updatedAt: Date = Date() var movies: List = 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) }

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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) }

Slide 99

Slide 99 text

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 = mutableListOf() fun build() = Catalog(movies, updatedAt) } fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) }

Slide 100

Slide 100 text

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 = 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) }

Slide 101

Slide 101 text

fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days } println(myCatalog) } data class Catalog( val movies: List, 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 = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }

Slide 102

Slide 102 text

fun shareMyCatalog() { val myCatalog = catalog { updatedAt = Date() - 1.days movie { title = “TaxiDriver" rating = 9 } } println(myCatalog) } data class Catalog( val movies: List, 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 = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }

Slide 103

Slide 103 text

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, 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 = mutableListOf() fun movie(block: MovieBuilder.() &' Unit) { movies += MovieBuilder().apply(block).build() } fun build() = Catalog(movies, updatedAt) }

Slide 104

Slide 104 text

I finally understand !!

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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/

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

FINAL
 REMARKS

Slide 110

Slide 110 text

Kotlin + DSLs

Slide 111

Slide 111 text

CALL TO ACTION ! • Learn more about invoking instances in Kotlin • Learn about scoping with @DSLMarker • Design your own DSLs for fun and profit !

Slide 112

Slide 112 text

UBIRATAN SOARES Brazilian Computer Scientist Senior Software Engineer @ N26 GDE for Android and Kotlin @ubiratanfsoares ubiratansoares.dev

Slide 113

Slide 113 text

https://speakerdeck.com/ubiratansoares

Slide 114

Slide 114 text

THANK YOU Do you want to write awesome Kotlin? N26 is hiring! https://n26.com/en/careers