Slide 1

Slide 1 text

Kotlin DSL Vinh Nguyen @ntvinh11586 Saigon, October 2018

Slide 2

Slide 2 text

Agenda • More Kotlin features • Kotlin DSL • Resources • Q&A

Slide 3

Slide 3 text

More Kotlin features • Kotlin first-class function • OOP delegation & object receiver in Kotlin • inline function

Slide 4

Slide 4 text

Kotlin first-class function –Kotlinlang “Kotlin functions are first-class, which means that they can be stored in variables and data structures, passed as arguments to and returned from other higher-order functions. You can operate with functions in any way that is possible for other non- function values.”

Slide 5

Slide 5 text

Higher-order function –Kotlinlang “A higher-order function is a function that takes functions as parameters, or returns a function.”

Slide 6

Slide 6 text

Higher-order function class Benchmark { fun benchmark(block: () -> Unit): Long { val startTime = System.currentTimeMillis() block.invoke() return System.currentTimeMillis() - startTime } }

Slide 7

Slide 7 text

Function types • All function types have a parenthesized parameter types list and a return type: (A, B) -> C denotes a type that represents functions taking two arguments of types A and B and returning a value of type C. • The parameter types list may be empty, as in () -> A. • Function types can optionally have an additional receiver type A.(B) -> C, which represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. • Function type is just a syntactic sugar for an interface, but the interface cannot be used explicitly. Nevertheless we can use function types like interfaces, what includes using them as type arguments or implementing them.

Slide 8

Slide 8 text

Function types val greet: () -> Unit val calculateTwoInt: (Int, Int) -> Int val square: (Int) -> Int val producePrinter: () -> () -> Unit val sum: (Int, Int) -> Int = { a, b -> a + b } class MyFunction : () -> Unit { override fun invoke() { println("I am called") } } fun main(args: Array) { val function = MyFunction() println(sum(1, 2)) // Prints: 3 function() // Prints: I am called }

Slide 9

Slide 9 text

Function reference • Function reference is referencing to the actual function. • The simplest way to provide function

Slide 10

Slide 10 text

Function reference val sumTwoInt: (Int, Int) -> Int = { a, b -> a + b } fun greetFunction() { println("Hello") } val reference1 = ::sumTwoInt val reference2 = sumTwoInt val reference3 = ::greetFunction fun anotherSumTwoInt() = sumTwoInt fun anotherGreatFunction() = ::greetFunction

Slide 11

Slide 11 text

Function literal • Literal in programming is a syntactic sugar for representing values of some types the language considers particularly important. • Function literal is a special notation used to simplify how a function is defined. • Two types of function literals: Lambda expression & Anonymous function.

Slide 12

Slide 12 text

Lambda expression • Lambda expression is a short way to define a function. val greet: () -> Unit = { println("Hello") } val calculateTwoInt: (Int, Int) -> Int = { a, b -> a + b } val square: (Int) -> Int = { x -> x * x } val producePrinter: () -> () -> Unit = { { println("I am printing") } } val greet = { println("Hello") } val calculateTwoInt = { a, b -> a + b } val square = { x: Int -> x * x } val producePrinter = { { println("I am printing") } } greet() // Prints: Hello calculateTwoInt(2, 3) // Prints: 5 println(square(2)) // Prints: 4 producePrinter()() // Prints: I am printing

Slide 13

Slide 13 text

Anonymous function • Anonymous function is an alternative way to define a function. val greet: () -> Unit = fun() { println("Hello") } val calculateTwoInt: (Int, Int) -> Int = fun(a, b): Int { return a + b } val calculateTwoInt: (Int, Int) -> Int = fun(a, b) = a + b val square: (Int) -> Int = fun(x) = x * x val producePrinter: () -> () -> Unit = fun() = fun() { println("I am printing") } val greet = fun() { println("Hello") } val calculateTwoInt = fun(a: Int, b: Int): Int = a + b val square = fun(x: Int) = x * x val producePrinter = fun() = fun() { println("I am printing") }

Slide 14

Slide 14 text

Lambda Exp vs. Anon Function val getMessage = { response: Response -> if(response.code !in 200..299) { return "Error" // Error! Not allowed } response.message } val getMessage = lambda@ { response: Response -> if(response.code !in 200..299) { return@lambda “Error" // Return at labels } response.message } val getMessage = fun(response: Response): String { if(response.code !in 200..299) { return "Error" } return response.message }

Slide 15

Slide 15 text

OOP delegation –Wikipedia “In object-oriented programming, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender).”

Slide 16

Slide 16 text

OOP delegation class Car { private val engine = Engine() fun startEngine() { engine.run() } }

Slide 17

Slide 17 text

Object receiver • Explicit receiver • Implicit receiver • Extension receiver • Dispatch receiver

Slide 18

Slide 18 text

Explicit & implicit receiver • Receiver is explicit if it refers to an object when accessing its members. • Receiver is implicit if it don’t explicitly refer to object when accessing its members.

Slide 19

Slide 19 text

Explicit & implicit receiver val guitar = Guitar() guitar.playTone() // explicit receiver class Guitar { fun playTone() { } fun playSong() { this.playTone() // explicit receiver playTone() // implicit receiver } }

Slide 20

Slide 20 text

Explicit & implicit receiver interface A { fun doSomething() { println("A is doing something") } } interface B { fun doSomething() { println("B is doing something") } } class Test : A, B { override fun doSomething() { doSomething() // infinitely recursive call } } class Test : A, B { override fun doSomething() { super.doSomething() // explicit receiver super.doSomething() // explicit receiver } }

Slide 21

Slide 21 text

Extension & dispatch receiver • Extension receiver is the receiver that is closely related to Kotlin extensions. Extension receiver represents an object that we define an extension for. • Dispatch receiver is a special kind of receiver existing when the extension is declared a member. It represents an instance of the class in which the extension is declared in.

Slide 22

Slide 22 text

Extension & dispatch receiver class Ball(var name: String) fun Ball.bounce() { println("Receiver type is ${this.javaClass}, " + "Receiver object is ${this}") } val ball = Ball("Golf ball") ball.bounce()

Slide 23

Slide 23 text

Extension & dispatch receiver class Person { fun move() {} } class NetworkRepository(val person: Person) { fun loadData() {} fun move() {} fun doSomething() { person.uploadToBackend(); // We can access extension here } fun Person.uploadToBackend() { //method from extension dispatch receiver loadData() //method from extension receiver // calls method defined in Person class move() // calls method defined in NetworkRepository class [email protected]() } } val person = Person() person.uploadToBackend() // Compilation error

Slide 24

Slide 24 text

This Expression • To denote the current receiver, we use this expressions: • In a member of a class, this refers to the current object of that class. • In an extension function or a function literal with receiver this denotes the receiver parameter that is passed on the left-hand side of a dot. • If this has no qualifiers, it refers to the innermost enclosing scope. To refer to this in other scopes, label qualifiers are used => this@label

Slide 25

Slide 25 text

This Expression class A { // implicit label @A inner class B { // implicit label @B fun Int.foo() { // implicit label @foo val a = this@A // A's this val b = this@B // B's this val c = this // foo()'s receiver, an Int val c1 = this@foo // foo()'s receiver, an Int val funLit = fun String.() { val d = this // funLit's receiver } val funLit2 = { s: String -> // foo()'s receiver, since enclosing lambda expression // doesn't have any receiver val d1 = this } } } }

Slide 26

Slide 26 text

Function type with receiver & Function literal with receiver • Function types can optionally have an additional receiver type A.(B) -> C, which represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. class Person(var abc: String) val addNickName: Person.(nickName: String) -> String = { nickName -> this.abc + " is " + nickName } fun main(arg: Array) { println(addNickName(Person("Vinh"), "Vince")) }

Slide 27

Slide 27 text

Function type with receiver & Function literal with receiver val squareWithoutReceiver: (Int) -> Int = { num -> num * num } val squareWithoutReceiver1: (Int) -> Int = { it * it } val squareWithReceiver: Int.() -> Int = { this * this } val squareWithReceiverFunc: Int.() -> Int = fun Int.() = this * this val squareWithReceiverFunc1 = fun Int.() = this * this val squareFunc = { this, this } // compile error

Slide 28

Slide 28 text

run, with, apply, also, let fun T.run(block: T.() -> R): R = block() fun with(receiver: T, block: T.() -> R): R = receiver.block() fun T.apply(block: T.() -> Unit): T { block(); return this } fun T.also(block: (T) -> Unit): T { block(this); return this } fun T.let(block: (T) -> R): R = block(this) https://docs.google.com/spreadsheets/d/1P2gMRuu36pSDW4fdwE- fLN9fcA_ZboIU2Q5VtgixBNo/edit#gid=0

Slide 29

Slide 29 text

apply() example inline fun T.apply(block: T.() -> Unit): T { block(this) return this } val editor = preferences.edit() editor.apply { putString("key_String", "value") putInt("key_int", 0) apply() }

Slide 30

Slide 30 text

Inline function • Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure, i.e. those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead. • The inline modifier affects both the function itself and the lambdas passed to it: all of those will be inlined into the call site.

Slide 31

Slide 31 text

Inline function fun nonInlined(block: () -> Unit) { println("before") block() println("after") } public void nonInlined(Function block) { System.out.println("before"); block.invoke(); System.out.println("after"); } nonInlined { println("do something here") } nonInlined(new Function() { @Override public void invoke() { System.out.println("do something here"); } });

Slide 32

Slide 32 text

Inline function inline fun inlined(block: () -> Unit) { println("before") block() println("after") } inlined { println("do something here") } System.out.println(“before"); System.out.println("do something here”); System.out.println("after");

Slide 33

Slide 33 text

Noninline function inline fun higherOrderFunction( aLambda: () -> Unit, noinline dontInlineLambda: () -> Unit, aLambda2: () -> Unit ) { doSomething() aLambda() dontInlineLambda()//won't be inlined. aLambda2() doAnotherThing() }

Slide 34

Slide 34 text

Return at labels • With function literals, local functions and object expression, functions can be nested in Kotlin. Qualified returns allow us to return from an outer function. The most important use case is returning from a lambda expression. fun foo() { listOf(1, 2, 3, 4, 5).forEach { // non-local return directly to the caller of foo() if (it == 3) return print(it) } println("this point is unreachable") }

Slide 35

Slide 35 text

Return at labels fun foo() { listOf(1, 2, 3, 4, 5).forEach lit@ { // local return to the caller of the lambda, i.e. the forEach loop if (it == 3) return@lit print(it) } print("done with explicit label") } val getMessage = lambda@ { response: Response -> if(response.code !in 200..299) { return@lambda “Error" // Return at labels } response.message }

Slide 36

Slide 36 text

Crossinline • The crossinline marker is used to mark lambdas that mustn’t allow non-local returns, especially when such lambda is passed to another execution context such as a higher order function that is not inlined, a local object or a nested function. inline fun higherOrderFunction(crossinline lambda: () -> Unit) { normalFunction { lambda() } } fun normalFunction(func: () -> Unit) { return } fun callingFunction() { higherOrderFunction { return //Error. Can't return from here. } }

Slide 37

Slide 37 text

Kotlin DSL • DSL introduction • Build DSL in Kotlin • Kotlin DSL libraries

Slide 38

Slide 38 text

DSL introduction –Wikipedia A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains.

Slide 39

Slide 39 text

DSL introduction • DSL allows to write not imperative code (way how to solve the problem) but in more or less declarative way (just declare the task) in order to obtain the solution based on the given data. • External DSLs have their own custom syntax, have to write a full parser to process them. • Internal DSLs are particular ways of using a host language to give the host language the feel of a particular language.

Slide 40

Slide 40 text

DSL example • External DSL: CSS, HTML, SQL, Regex,… • Internal DSL • Java: jOOQ, AssertJ, Hamcrest • Gradle: build.gradle in Android • Kotlin: ???

Slide 41

Slide 41 text

Build DSL in Kotlin val person = person { name = "John" age = 25 address { street = "Main Street" number = 42 city = "London" } } data class Person( var name: String? = null, var age: Int? = null, var address: Address? = null ) data class Address( var street: String? = null, var number: Int? = null, var city: String? = null )

Slide 42

Slide 42 text

Build DSL in Kotlin Person( name = "John", age = 25, address = Address( street = "Main Street", number = 42, city = “London" ) ) val person = person { name = "John" age = 25 address { street = "Main Street" number = 42 city = "London" } }

Slide 43

Slide 43 text

Build DSL in Kotlin fun person(block: Person.() -> Unit): Person { val p = Person() p.block() return p } fun Person.address(block: Address.() -> Unit) { address = Address().apply(block) } val person = person { name = "John" age = 25 address { street = "Main Street" number = 42 city = "London" } }

Slide 44

Slide 44 text

Build DSL in Kotlin (More) • Builder pattern • Collections • Narrowing scope

Slide 45

Slide 45 text

Builder Pattern data class Person( val name: String, val dateOfBirth: Date, var address: Address? ) data class Address( val street: String, val number: Int, val city: String ) val person = person { name = "John" dateOfBirth = "1980-12-01" address { street = "Main Street" number = 12 city = "London" } }

Slide 46

Slide 46 text

Builder Pattern class PersonBuilder { var name: String = "" private var dob: Date = Date() var dateOfBirth: String = "" set(value) { dob = SimpleDateFormat("yyyy-MM-dd").parse(value) } private var address: Address? = null fun address(block: AddressBuilder.() -> Unit) { address = AddressBuilder().apply(block).build() } fun build(): Person = Person(name, dob, address) } class AddressBuilder { var street: String = “" var number: Int = 0 var city: String = "" fun build() : Address = Address(street, number, city) } fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build()

Slide 47

Slide 47 text

Collections data class Person( val name: String, val dateOfBirth: Date, val addresses: List
) data class Address( val street: String, val number: Int, val city: String ) val person = person { name = "John" dateOfBirth = "1980-12-01" addresses { address { street = "Main Street" number = 12 city = "London" } address { street = "Dev Avenue" number = 42 city = "Paris" } } }

Slide 48

Slide 48 text

Collections class PersonBuilder { // ... other properties private val addresses = mutableListOf
() fun address(block: AddressBuilder.() -> Unit) { addresses.add(AddressBuilder().apply(block).build()) } fun build(): Person = Person(name, dob, addresses) } val person = person { name = "John" dateOfBirth = "1980-12-01" address { street = "Main Street" number = 12 city = "London" } address { street = "Dev Avenue" number = 42 city = "Paris" } }

Slide 49

Slide 49 text

Collections (Improvement) class PersonBuilder { // ... other properties private val addresses = mutableListOf
() fun addresses(block: ADDRESSES.() -> Unit) { addresses.addAll(ADDRESSES().apply(block)) } fun build(): Person = Person(name, dob, addresses) } class ADDRESSES: ArrayList
() { fun address(block: AddressBuilder.() -> Unit) { add(AddressBuilder().apply(block).build()) } } val person = person { name = "John" dateOfBirth = "1980-12-01" addresses { address { street = "Main Street" number = 12 city = "London" } address { street = "Dev Avenue" number = 42 city = "Paris" } } }

Slide 50

Slide 50 text

Narrowing scope val person = person { name = "John" dateOfBirth = "1980-12-01" addresses { address { addresses { name = "Mary" // access outer scope lambda } street = "Dev Avenue" number = 42 city = "Paris" } } }

Slide 51

Slide 51 text

Narrowing scope @DslMarker annotation class PersonDsl @PersonDsl class PersonBuilder { //... } @PersonDsl class ADDRESSES: ArrayList
() { //... } @PersonDsl class AddressBuilder { //... } addresses { name = “Mary” // error } [email protected] = “Mary” https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-dsl-marker/index.html @DslMarker

Slide 52

Slide 52 text

Build DSL in Kotlin (Android) inline fun Context.linearLayout(block: LinearLayout.() -> Unit) = LinearLayout(this).apply(block) inline fun ViewGroup.view(block: View.() -> Unit) = View(context).apply(block) val layout = linearLayout { orientation = LinearLayout.HORIZONTAL addView(view { layoutParams.width = 100 layoutParams.height = 40 }) }

Slide 53

Slide 53 text

Kotlin DSL libraries • Anko • Koin • Gradle kotlin dsl • kotlinx.html • …

Slide 54

Slide 54 text

Anko • Anko is a Kotlin library which makes Android application development faster and easier. It makes your code clean and easy to read, and lets you forget about rough edges of the Android SDK for Java. • Anko supports: Andorid components (Intent, dialog, logging,…), UI layouts, SQLite, Coroutines • https://github.com/Kotlin/anko

Slide 55

Slide 55 text

Anko layout override fun createView(ui: AnkoContext) = with(ui) { verticalLayout { padding = dip(32) imageView(android.R.drawable.ic_menu_manage).lparams { margin = dip(16) gravity = Gravity.CENTER } val name = editText { hintResource = R.string.name } val password = editText { hintResource = R.string.password inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_PASSWORD } button("Log in") { onClick { ui.owner.tryLogin(ui, name.text, password.text) } } myRichView() }.applyRecursively(customStyle) }

Slide 56

Slide 56 text

Anko layout performance

Slide 57

Slide 57 text

Anko layout strength • Type-safe • Support tree structure • Simplicity • Easier to control UI elements in the code • Code reusability

Slide 58

Slide 58 text

Koin • KOIN - a pragmatic lightweight dependency injection framework for Kotlin • Written in pure Kotlin, using functional resolution only: no proxy, no code generation, no reflection. • https://insert-koin.io

Slide 59

Slide 59 text

Koin example class Controller(val service : BusinessService) class BusinessService() val myModule = module { single { Controller(get()) } single { BusinessService() } } class MyApplication : Application() { override fun onCreate(){ super.onCreate() startKoin(this, listOf(myModule)) } } class MyActivity() : AppCompatActivity() { val service : BusinessService by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val service : BusinessService = get() } }

Slide 60

Slide 60 text

Koin strength • No proxy, no code generation, no reflection • Lazy injection • Easy to learn • Simple to write

Slide 61

Slide 61 text

Demo • https://github.com/ntvinh11586/kotlin-dsl-demo

Slide 62

Slide 62 text

Reference • https://blog.kotlin-academy.com/ • https://kotlinlang.org/docs/reference/lambdas.html • https://kotlinlang.org/docs/reference/inline-functions.html • https://docs.google.com/presentation/d/ 12mkyGQZO22kf0_6kp2K6xyFdpg0nBLqGtNcVR-cV4M8/pub?slide=id.p • https://www.martinfowler.com/bliki/DomainSpecificLanguage.html • https://proandroiddev.com/writing-dsls-in-kotlin-part-1-7f5d2193f277 • https://proandroiddev.com/writing-dsls-in-kotlin-part-2-cd9dcd0c4715 • https://github.com/InsertKoinIO/koin • https://github.com/Kotlin/anko

Slide 63

Slide 63 text

Q&A

Slide 64

Slide 64 text

Thank you!