SlideShare una empresa de Scribd logo
1 de 109
Descargar para leer sin conexión
@antonarhipov
Idiomatic Kotlin
from formatting to DSLs
Agenda
• Expressions

• Examples from standard library

• DSL
Anton Arhipov


@antonarhipov
Developer Advocate @ JetBrains
Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
In case of a programming language:

•Conforms to a commonly accepted style

•E
ff
ectively uses features of the programming language
Expressions
try, if, when
fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


result = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {


val result: Drive


result = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




val result: Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




val result: Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}


return result


}
fun adjustSpeed(weather: Weather): Drive {




return if (weather is Rainy) {


Safe()


} else {


Calm()


}


}
fun adjustSpeed(weather: Weather): Drive {




return if (weather is Rainy) {


Safe()


} else {


Calm()


}


}
fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) {


Safe()


} else {


Calm()


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()


fun adjustSpeed(weather: Weather): Drive {


var result: Drive


if (weather is Rainy) {


result = Safe()


} else {


result = Calm()


}




return result


}
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
abstract class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


/
/
else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


/
/
else
-
>
Calm()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


is Sunny
-
>
TODO()


}
sealed class Weather


class Sunny : Weather()


class Rainy : Weather()


fun adjustSpeed(weather: Weather) = when (weather) {


is Rainy
-
>
Safe()


is Sunny
-
>
TODO()


}


Use expressions!


Use when as expression body


Use sealed classes with when
Use try as expression body
fun tryParse(number: String) : Int? {


try {


return Integer.parseInt(number)


} catch (e: NumberFormatException) {


return null


}


}
Use try as expression body
fun tryParse(number: String) = try {


Integer.parseInt(number)


} catch (e: NumberFormatException) {


null


}
Use try as expression
fun tryParse(number: String) : Int? {


val n = try {


Integer.parseInt(number)


} catch (e: NumberFormatException) {


null


}


println(n)


return n


}
Use elvis operator
class Person(val name: String?, val age: Int?)


val p = retrievePerson()
?
:
Person()
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name


if (name
=
=
null)


throw IllegalArgumentException("Named required")


val age = person.age


if (age
=
=
null) return


println("$name: $age")


}
Use elvis operator as return and throw
class Person(val name: String?, val age: Int?)


fun processPerson(person: Person) {


val name = person.name
?
:


throw IllegalArgumentException("Named required")


val age = person.age
?
:
return


println("$name: $age")


}
Nullability
Consider using null-safe call
val order = retrieveOrder()


if (order
=
=
null
|
|
order.customer
=
=
null
|
|
order.customer.address
=
=
null){


throw IllegalArgumentException("Invalid Order")


}


val city = order.customer.address.city
Consider using null-safe call
val order = retrieveOrder()


val city = order
?
.
customer
?
.
address
?
.
city
Consider using null-safe call
val order = retrieveOrder()


val city = order
?
.
customer
?
.
address
?
.
city


?
:
throw IllegalArgumentException("Invalid Order")
Avoid not-null assertions !!
val order = retrieveOrder()


val city = order
!
!
.customer
!
!
.address
!
!
.city


“You may notice that the double exclamation mark looks a bit rude:
 

it’s almost like you’re yelling at the compiler. This is intentional.” - Kotlin in Action
Avoid not-null assertions !!
class MyTest {


class State(val data: String)


private var state: State? = null


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state
!
!
.data)


}


}
Avoid not-null assertions !!
class MyTest {


class State(val data: String)


private var state: State? = null


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state
!
!
.data)


}


}
class MyTest {


class State(val data: String)


private lateinit var state: State


@BeforeEach


fun setup() {


state = State("abc")


}


@Test


fun foo() {


assertEquals("abc", state.data)


}


}
- use lateinit
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
No need for an extra variable
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using ?.let for null-checks
val order = retrieveOrder()


if (order
!
=
null){


processCustomer(order.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
let {


processCustomer(it.customer)


}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
Consider using safe cast for type checking
override fun equals(other: Any?) : Boolean {


val command = other as Command


return command.id
=
=
id


}
Consider using safe cast for type checking
override fun equals(other: Any?) : Boolean {


val command = other as Command


return command.id
=
=
id


}
override fun equals(other: Any?) : Boolean {


return (other as? Command)
?
.
id
=
=
id


}
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c
>
=
'A'
&
&
c
<
=
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c
>
=
'A'
&
&
c
<
=
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c in 'A'
.
.
'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =


c in 'A'
.
.
'Z'
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
for (i in args.indices) {


println("$i: ${args[i]}")


}
Ranges in loops
fun main(args: Array<String>) {




for (i in 0
.
.
args.size - 1) {


println("$i: ${args[i]}")


}




}


for (i in 0 until args.size) {


println("$i: ${args[i]}")


}
for (i in args.indices) {


println("$i: ${args[i]}")


}
for ((i, arg) in args.withIndex()) {


println("$i: $arg")


}
Classes and Functions
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}
Don’t create classes just to hold functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }
Use extension functions
class StringUtils {


companion object {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


}


object StringUtils {


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


}


fun isPhoneNumber(s: String) =


s.length
=
=
7
&
&
s.all { it.isDigit() }


fun String.isPhoneNumber() =


length
=
=
7
&
&
all { it.isDigit() }
Extension or a member?
https://kotlinlang.org/docs/coding-conventions.html#extension-functions
•Use extension functions liberally.
 

•If a function works primarily on an object, consider making it an
extension with that object as a receiver.
 

•Minimize API pollution, restrict the visibility.
 

•As necessary, use local extension functions, member extension
functions, or top-level extension functions with private visibility.
Use default values instead of overloading
class Phonebook {


fun print() {


print(",")


}


fun print(columnSeparator: String) {}


}


fun main(args: Array<String>) {


Phonebook().print("|")


}
Use default values instead of overloading
class Phonebook {


fun print() {


print(",")


}


fun print(columnSeparator: String) {}


}


fun main(args: Array<String>) {


Phonebook().print("|")


}


class Phonebook {


fun print(separator: String = ",") {}


fun someFun(x: Int) {}


}


fun main(args: Array<String>) {


Phonebook().print(separator = "|")


}
Return multiple values using data classes
fun namedNum(): Pair<Int, String> =


1 to "one"


/
/
same but shorter


fun namedNum2() = 1 to "one"


fun main(args: Array<String>) {


val pair = namedNum()


val number = pair.first


val name = pair.second


}
Return multiple values using data classes
fun namedNum(): Pair<Int, String> =


1 to "one"


/
/
same but shorter


fun namedNum2() = 1 to "one"


fun main(args: Array<String>) {


val pair = namedNum()


val number = pair.first


val name = pair.second


}
data class GameResult(


val rank: Int,


val name: String


)


fun namedNum() =


GameResult(1, "Player 1")


fun main(args: Array<String>) {


val (rank, name) = namedNum()


println("$name, rank $rank")


}
Return multiple values using data classes
data class GameResult(


val rank: Int,


val name: String


)


fun namedNum() =


GameResult(1, "Player 1")


fun main(args: Array<String>) {


val (rank, name) = namedNum()


println("$name, rank $rank")


}
GameResult var1 = namedNum();


int var2 = var1.component1();


String var3 = var1.component2();
Destructuring in loops
fun printMap(map: Map<String, String>) {


for (item in map.entries) {


println("${item.key}
-
>
${item.value}")


}


}
Destructuring in loops
fun printMap(map: Map<String, String>) {


for (item in map.entries) {


println("${item.key}
-
>
${item.value}")


}


}
fun printMap(map: Map<String, String>) {


for ((key, value) in map) {


println("$key
-
>
$value")


}


}
Destructuring in lists
data class NameExt(


val name: String,


val ext: String?


)


fun splitNameExt(filename: String): NameExt {


if ('.' in filename) {


val parts = filename.split('.', limit = 2)


return NameExt(parts[0], parts[1])


}


return NameExt(filename, null)


}


fun splitNameAndExtension(filename: String): NameExt {


if ('.' in filename) {


val (name, ext) = filename.split('.', limit = 2)


return NameExt(name, ext)


}


return NameExt(filename, null)


}
Use type aliases for functional types
class Event


class EventDispatcher {


fun addClickHandler(handler: (Event)
-
>
Unit) {}


fun removeClickHandler(handler: (Event)
-
>
Unit) {}


}
Use type aliases for functional types
class Event


class EventDispatcher {


fun addClickHandler(handler: (Event)
-
>
Unit) {}


fun removeClickHandler(handler: (Event)
-
>
Unit) {}


}
typealias ClickHandler = (Event)
-
>
Unit


class EventDispatcher {


fun addClickHandler(handler: ClickHandler) {


}


fun removeClickHandler(handler: ClickHandler) {


}


}
Standard Library
Verify parameters using require()
class Person(


val name: String?,


val age: Int


)


fun processPerson(person: Person) {


if (person.age < 18) {


throw IllegalArgumentException("Adult required")


}


}
Verify parameters using require()
class Person(


val name: String?,


val age: Int


)


fun processPerson(person: Person) {


if (person.age < 18) {


throw IllegalArgumentException("Adult required")


}


}


fun processPerson(person: Person) {


require(person.age
>
=
18) { "Adult required" }


}
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =


objects.filter { it is String }
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =


objects.filter { it is String }


fun findAllStrings(objects: List<Any>) =


objects.filterIsInstance<String>()
Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) : List<Any> =


objects.filter { it is String }


fun findAllStrings(objects: List<Any>) : List<String> =


objects.filterIsInstance<String>()
Apply operation to non-null elements mapNotNull
data class Result(


val data: Any?,


val error: String?


)


fun listErrors(results: List<Result>): List<String> =


results.map { it.error }.filterNotNull()


fun listErrors(results: List<Result>): List<String> =


results.mapNotNull { it.errorMessage }
compareBy compares by multiple keys
class Person(


val name: String,


val age: Int


)


fun sortPersons(persons: List<Person>) =


persons.sortedWith(Comparator<Person> { person1, person2
-
>


val rc = person1.name.compareTo(person2.name)


if (rc
!
=
0)


rc


else


person1.age - person2.age


})
compareBy compares by multiple keys
class Person(


val name: String,


val age: Int


)


fun sortPersons(persons: List<Person>) =


persons.sortedWith(Comparator<Person> { person1, person2
-
>


val rc = person1.name.compareTo(person2.name)


if (rc
!
=
0)


rc


else


person1.age - person2.age


})


fun sortPersons(persons: List<Person>) =


persons.sortedWith(compareBy(Person
:
:
name, Person
:
:
age))
groupBy to group elements
class Request(


val url: String,


val remoteIP: String,


val timestamp: Long


)


fun analyzeLog(log: List<Request>) {


val map = mutableMapOf<String, MutableList<Request
>
>
()


for (request in log) {


map.getOrPut(request.url) { mutableListOf() }


.add(request)


}


}
groupBy to group elements
class Request(


val url: String,


val remoteIP: String,


val timestamp: Long


)


fun analyzeLog(log: List<Request>) {


val map = mutableMapOf<String, MutableList<Request
>
>
()


for (request in log) {


map.getOrPut(request.url) { mutableListOf() }


.add(request)


}


}


fun analyzeLog(log: List<Request>) {


val map = log.groupBy(Request
:
:
url)


}
Use coerceIn to ensure numbers in range
fun updateProgress(value: Int) {


val actualValue = when {


value < 0
-
>
0


value > 100
-
>
100


else
-
>
value


}


}
fun updateProgress(value: Int) {


val actualValue = value.coerceIn(0, 100)


}
Initializing objects with apply
val dataSource = BasicDataSource(
)

dataSource.driverClassName = "com.mysql.jdbc.Driver"
dataSource.url = "jdbc:mysql://domain:3309/db"
dataSource.username = "username"
dataSource.password = "password"
dataSource.maxTotal = 40
dataSource.maxIdle = 40
dataSource.minIdle = 4
val dataSource = BasicDataSource().apply
{

driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://domain:3309/db"
username = "username"
password = "password"
maxTotal = 40
maxIdle = 40
minIdle = 4
}
Initializing objects with apply
final ClientBuilder builder = new ClientBuilder();


builder.setFirstName("Anton");


builder.setLastName("Arhipov");


final TwitterBuilder twitterBuilder = new TwitterBuilder();


twitterBuilder.setHandle("@antonarhipov");


builder.setTwitter(twitterBuilder.build());


final CompanyBuilder companyBuilder = new CompanyBuilder();


companyBuilder.setName("JetBrains");


companyBuilder.setCity("Tallinn");


builder.setCompany(companyBuilder.build());


final Client client = builder.build();


System.out.println("Created client is: " + client);
Initializing objects with apply
val builder = ClientBuilder()


builder.firstName = "Anton"


builder.lastName = "Arhipov"


val twitterBuilder = TwitterBuilder()


twitterBuilder.handle = "@antonarhipov"


builder.twitter = twitterBuilder.build()


val companyBuilder = CompanyBuilder()


companyBuilder.name = "JetBrains"


companyBuilder.city = "Tallinn"


builder.company = companyBuilder.build()


val client = builder.build()


println("Created client is: $client")
Initializing objects with apply
val builder = ClientBuilder()


builder.firstName = "Anton"


builder.lastName = "Arhipov"


val twitterBuilder = TwitterBuilder()


twitterBuilder.handle = "@antonarhipov"


builder.twitter = twitterBuilder.build()


val companyBuilder = CompanyBuilder()


companyBuilder.name = "JetBrains"


companyBuilder.city = "Tallinn"


builder.company = companyBuilder.build()


val client = builder.build()


println("Created client is: $client")


val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
Domain Specific Languages
“Domain Speci
fi
c”,
i.e. tailored for a speci
fi
c task
“Domain Speci
fi
c”,
i.e. tailored for a speci
fi
c task
Examples:

•Compose strings - stringBuilder

•Create HTML documents - kotlinx.html

•Con
fi
gure routing logic for a web app - ktor

•Generally, build any object graphs. See “type-safe builders”
buildString
//Java
String name = "Joe";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append("Hello, ");
sb.append(name);
sb.append("!n");
}
System.out.println(sb);
//Kotlin
val name = "Joe"
val s = buildString {
repeat(5) {
append("Hello, ")
append(name)
appendLine("!")
}
}
println(s)
kotlinx.html
System.out.appendHTML().html {


body {


div {


a("http:
/
/
kotlinlang.org") {


target = ATarget.blank


+"Main site"


}


}


}


}
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}


Ktor’s routing
Ktor
fun main() {


embeddedServer(Netty, port = 8080, host = "0.0.0.0") {


routing {


get("/html-dsl") {


call.respondHtml {


body {


h1 { +"HTML" }


ul {


for (n in 1
.
.
10) {


li { +"$n" }


}


}


}


}


}


}


}.start(wait = true)


}


kotlinx.html
Ktor’s routing
Lambda with receiver
T.() -> Unit
Build your vocabulary to abstract from scope functions
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
Build your vocabulary to abstract from scope functions
fun client(c: ClientBuilder.()
-
>
Unit): Client {


val builder = ClientBuilder()


c(builder)


return builder.build()


}


fun ClientBuilder.company(block: CompanyBuilder.()
-
>
Unit) {


company = CompanyBuilder().apply(block).build()


}


fun ClientBuilder.twitter(block: TwitterBuilder.()
-
>
Unit) {


twitter = TwitterBuilder().apply(block).build()


}
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
val client = client {


firstName = "Anton"


lastName = "Arhipov"


twitter {


handle = "@antonarhipov"


}


company {


name = "JetBrains"


city = "Tallinn"


}


}


println("Created client is: $client")


Build your vocabulary to abstract from scope functions
val client = ClientBuilder().apply {


firstName = "Anton"


lastName = "Arhipov"


twitter = TwitterBuilder().apply {


handle = "@antonarhipov"


}.build()


company = CompanyBuilder().apply {


name = "JetBrains"


city = "Tallinn"


}.build()


}.build()


println("Created client is: $client")
https://speakerdeck.com/antonarhipov
https://github.com/antonarhipov/idiomatic-kotlin
@antonarhipov

Más contenido relacionado

La actualidad más candente

La actualidad más candente (20)

Running Free with the Monads
Running Free with the MonadsRunning Free with the Monads
Running Free with the Monads
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
 
JDBC
JDBCJDBC
JDBC
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented Programming
 
JPA Best Practices
JPA Best PracticesJPA Best Practices
JPA Best Practices
 
Introduction to java 8 stream api
Introduction to java 8 stream apiIntroduction to java 8 stream api
Introduction to java 8 stream api
 
Collection v3
Collection v3Collection v3
Collection v3
 
Event-sourced architectures with Akka
Event-sourced architectures with AkkaEvent-sourced architectures with Akka
Event-sourced architectures with Akka
 
Resource Bundle
Resource BundleResource Bundle
Resource Bundle
 
스위프트, 코틀린과 모던언어의 특징 (Swift, Kotlin and Modern Languages)
스위프트, 코틀린과 모던언어의 특징 (Swift, Kotlin and Modern Languages)스위프트, 코틀린과 모던언어의 특징 (Swift, Kotlin and Modern Languages)
스위프트, 코틀린과 모던언어의 특징 (Swift, Kotlin and Modern Languages)
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
 
Java 8 Lambda and Streams
Java 8 Lambda and StreamsJava 8 Lambda and Streams
Java 8 Lambda and Streams
 
Javascript Design Patterns
Javascript Design PatternsJavascript Design Patterns
Javascript Design Patterns
 
LLVM Register Allocation (2nd Version)
LLVM Register Allocation (2nd Version)LLVM Register Allocation (2nd Version)
LLVM Register Allocation (2nd Version)
 
Hibernate
Hibernate Hibernate
Hibernate
 
Why The Free Monad isn't Free
Why The Free Monad isn't FreeWhy The Free Monad isn't Free
Why The Free Monad isn't Free
 
Asynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & PromisesAsynchronous JavaScript Programming with Callbacks & Promises
Asynchronous JavaScript Programming with Callbacks & Promises
 
Avoiding callback hell in Node js using promises
Avoiding callback hell in Node js using promisesAvoiding callback hell in Node js using promises
Avoiding callback hell in Node js using promises
 

Similar a Idiomatic kotlin

Similar a Idiomatic kotlin (20)

Scala vs Java 8 in a Java 8 World
Scala vs Java 8 in a Java 8 WorldScala vs Java 8 in a Java 8 World
Scala vs Java 8 in a Java 8 World
 
Exploring Koltin on Android
Exploring Koltin on AndroidExploring Koltin on Android
Exploring Koltin on Android
 
Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2
 
Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기
Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기
Swift - 혼자 공부하면 분명히 안할테니까 같이 공부하기
 
Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3Kotlin Advanced - Apalon Kotlin Sprint Part 3
Kotlin Advanced - Apalon Kotlin Sprint Part 3
 
Concurrent Application Development using Scala
Concurrent Application Development using ScalaConcurrent Application Development using Scala
Concurrent Application Development using Scala
 
Derping With Kotlin
Derping With KotlinDerping With Kotlin
Derping With Kotlin
 
Kotlinify Your Project!
Kotlinify Your Project!Kotlinify Your Project!
Kotlinify Your Project!
 
SDC - Einführung in Scala
SDC - Einführung in ScalaSDC - Einführung in Scala
SDC - Einführung in Scala
 
Introduction to Swift programming language.
Introduction to Swift programming language.Introduction to Swift programming language.
Introduction to Swift programming language.
 
Introduction to Scala
Introduction to ScalaIntroduction to Scala
Introduction to Scala
 
Parametricity - #cljsyd - May, 2015
Parametricity - #cljsyd - May, 2015Parametricity - #cljsyd - May, 2015
Parametricity - #cljsyd - May, 2015
 
Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
Ast transformations
Ast transformationsAst transformations
Ast transformations
 
Dip into Coroutines - KTUG Munich 202303
Dip into Coroutines - KTUG Munich 202303Dip into Coroutines - KTUG Munich 202303
Dip into Coroutines - KTUG Munich 202303
 
Swift-Programming Part 1
Swift-Programming Part 1Swift-Programming Part 1
Swift-Programming Part 1
 
Why Scala is the better Java
Why Scala is the better JavaWhy Scala is the better Java
Why Scala is the better Java
 
Kotlin
KotlinKotlin
Kotlin
 
ScalaBlitz
ScalaBlitzScalaBlitz
ScalaBlitz
 

Más de Anton Arhipov

JavaZone 2022 - Building Kotlin DSL.pdf
JavaZone 2022 - Building Kotlin DSL.pdfJavaZone 2022 - Building Kotlin DSL.pdf
JavaZone 2022 - Building Kotlin DSL.pdf
Anton Arhipov
 
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloadingRiga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Anton Arhipov
 
Devclub 01/2017 - (Не)адекватное Java-интервью
Devclub 01/2017 - (Не)адекватное Java-интервьюDevclub 01/2017 - (Не)адекватное Java-интервью
Devclub 01/2017 - (Не)адекватное Java-интервью
Anton Arhipov
 

Más de Anton Arhipov (20)

JavaZone 2022 - Building Kotlin DSL.pdf
JavaZone 2022 - Building Kotlin DSL.pdfJavaZone 2022 - Building Kotlin DSL.pdf
JavaZone 2022 - Building Kotlin DSL.pdf
 
TechTrain 2019 - (Не)адекватное техническое интервью
TechTrain 2019 - (Не)адекватное техническое интервьюTechTrain 2019 - (Не)адекватное техническое интервью
TechTrain 2019 - (Не)адекватное техническое интервью
 
Build pipelines with TeamCity
Build pipelines with TeamCityBuild pipelines with TeamCity
Build pipelines with TeamCity
 
Build pipelines with TeamCity
Build pipelines with TeamCityBuild pipelines with TeamCity
Build pipelines with TeamCity
 
Devoxx Ukraine 2018 - Kotlin DSL in under an hour
Devoxx Ukraine 2018 - Kotlin DSL in under an hourDevoxx Ukraine 2018 - Kotlin DSL in under an hour
Devoxx Ukraine 2018 - Kotlin DSL in under an hour
 
GeeCON Prague 2018 - Kotlin DSL in under an hour
GeeCON Prague 2018 - Kotlin DSL in under an hourGeeCON Prague 2018 - Kotlin DSL in under an hour
GeeCON Prague 2018 - Kotlin DSL in under an hour
 
Build pipelines with TeamCity and Kotlin DSL
Build pipelines with TeamCity and Kotlin DSLBuild pipelines with TeamCity and Kotlin DSL
Build pipelines with TeamCity and Kotlin DSL
 
Build pipelines with TeamCity
Build pipelines with TeamCityBuild pipelines with TeamCity
Build pipelines with TeamCity
 
JavaDay Kiev 2017 - Integration testing with TestContainers
JavaDay Kiev 2017 - Integration testing with TestContainersJavaDay Kiev 2017 - Integration testing with TestContainers
JavaDay Kiev 2017 - Integration testing with TestContainers
 
GeeCON Prague 2017 - TestContainers
GeeCON Prague 2017 - TestContainersGeeCON Prague 2017 - TestContainers
GeeCON Prague 2017 - TestContainers
 
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaOne 2017 - The hitchhiker’s guide to Java class reloadingJavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
 
JavaOne 2017 - TestContainers: integration testing without the hassle
JavaOne 2017 - TestContainers: integration testing without the hassleJavaOne 2017 - TestContainers: integration testing without the hassle
JavaOne 2017 - TestContainers: integration testing without the hassle
 
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaOne 2017 - The hitchhiker’s guide to Java class reloadingJavaOne 2017 - The hitchhiker’s guide to Java class reloading
JavaOne 2017 - The hitchhiker’s guide to Java class reloading
 
JavaZone 2017 - The Hitchhiker’s guide to Java class reloading
JavaZone 2017 - The Hitchhiker’s guide to Java class reloadingJavaZone 2017 - The Hitchhiker’s guide to Java class reloading
JavaZone 2017 - The Hitchhiker’s guide to Java class reloading
 
JUG.ua 20170225 - Java bytecode instrumentation
JUG.ua 20170225 - Java bytecode instrumentationJUG.ua 20170225 - Java bytecode instrumentation
JUG.ua 20170225 - Java bytecode instrumentation
 
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloadingRiga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
 
GeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassleGeeCON 2017 - TestContainers. Integration testing without the hassle
GeeCON 2017 - TestContainers. Integration testing without the hassle
 
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloadingJEEConf 2017 - The hitchhiker’s guide to Java class reloading
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
 
Devclub 01/2017 - (Не)адекватное Java-интервью
Devclub 01/2017 - (Не)адекватное Java-интервьюDevclub 01/2017 - (Не)адекватное Java-интервью
Devclub 01/2017 - (Не)адекватное Java-интервью
 

Último

Structuring Teams and Portfolios for Success
Structuring Teams and Portfolios for SuccessStructuring Teams and Portfolios for Success
Structuring Teams and Portfolios for Success
UXDXConf
 
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider  Progress from Awareness to Implementation.pptxTales from a Passkey Provider  Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
FIDO Alliance
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
panagenda
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
FIDO Alliance
 

Último (20)

Collecting & Temporal Analysis of Behavioral Web Data - Tales From The Inside
Collecting & Temporal Analysis of Behavioral Web Data - Tales From The InsideCollecting & Temporal Analysis of Behavioral Web Data - Tales From The Inside
Collecting & Temporal Analysis of Behavioral Web Data - Tales From The Inside
 
Structuring Teams and Portfolios for Success
Structuring Teams and Portfolios for SuccessStructuring Teams and Portfolios for Success
Structuring Teams and Portfolios for Success
 
TopCryptoSupers 12thReport OrionX May2024
TopCryptoSupers 12thReport OrionX May2024TopCryptoSupers 12thReport OrionX May2024
TopCryptoSupers 12thReport OrionX May2024
 
Intro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераIntro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджера
 
Overview of Hyperledger Foundation
Overview of Hyperledger FoundationOverview of Hyperledger Foundation
Overview of Hyperledger Foundation
 
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider  Progress from Awareness to Implementation.pptxTales from a Passkey Provider  Progress from Awareness to Implementation.pptx
Tales from a Passkey Provider Progress from Awareness to Implementation.pptx
 
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
 
Your enemies use GenAI too - staying ahead of fraud with Neo4j
Your enemies use GenAI too - staying ahead of fraud with Neo4jYour enemies use GenAI too - staying ahead of fraud with Neo4j
Your enemies use GenAI too - staying ahead of fraud with Neo4j
 
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdfLinux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
Linux Foundation Edge _ Overview of FDO Software Components _ Randy at Intel.pdf
 
Google I/O Extended 2024 Warsaw
Google I/O Extended 2024 WarsawGoogle I/O Extended 2024 Warsaw
Google I/O Extended 2024 Warsaw
 
Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptx
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?
 
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdfSimplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
Simplified FDO Manufacturing Flow with TPMs _ Liam at Infineon.pdf
 
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
 
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdfHow Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
 
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
 
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
 
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsContinuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
 

Idiomatic kotlin

  • 2. Agenda • Expressions • Examples from standard library • DSL
  • 4.
  • 5.
  • 6.
  • 7. Idiomatic - using, containing, or denoting expressions that are natural to a native speaker
  • 8. Idiomatic - using, containing, or denoting expressions that are natural to a native speaker In case of a programming language: •Conforms to a commonly accepted style •E ff ectively uses features of the programming language
  • 9.
  • 11. fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 12. fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 13. fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 14. fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 15. fun adjustSpeed(weather: Weather): Drive { val result: Drive result = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 16. fun adjustSpeed(weather: Weather): Drive { val result: Drive result = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 17. fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 18. fun adjustSpeed(weather: Weather): Drive { val result: Drive = if (weather is Rainy) { Safe() } else { Calm() } return result }
  • 19. fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }
  • 20. fun adjustSpeed(weather: Weather): Drive { return if (weather is Rainy) { Safe() } else { Calm() } }
  • 21. fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) { Safe() } else { Calm() }
  • 22. fun adjustSpeed(weather: Weather): Drive = if (weather is Rainy) { Safe() } else { Calm() }
  • 23. fun adjustSpeed(weather: Weather) = if (weather is Rainy) { Safe() } else { Calm() }
  • 24. fun adjustSpeed(weather: Weather) = if (weather is Rainy) { Safe() } else { Calm() }
  • 25. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 26. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm() fun adjustSpeed(weather: Weather): Drive { var result: Drive if (weather is Rainy) { result = Safe() } else { result = Calm() } return result }
  • 27. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 28. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
  • 29. abstract class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() else - > Calm() }
  • 30. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() / / else - > Calm() }
  • 31. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() / / else - > Calm() }
  • 32. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() is Sunny - > TODO() }
  • 33. sealed class Weather class Sunny : Weather() class Rainy : Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy - > Safe() is Sunny - > TODO() } Use expressions! Use when as expression body Use sealed classes with when
  • 34. Use try as expression body fun tryParse(number: String) : Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } }
  • 35. Use try as expression body fun tryParse(number: String) = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null }
  • 36. Use try as expression fun tryParse(number: String) : Int? { val n = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null } println(n) return n }
  • 37. Use elvis operator class Person(val name: String?, val age: Int?) val p = retrievePerson() ? : Person()
  • 38. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 39. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 40. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age = = null) return println("$name: $age") }
  • 41. Use elvis operator as return and throw class Person(val name: String?, val age: Int?) fun processPerson(person: Person) { val name = person.name ? : throw IllegalArgumentException("Named required") val age = person.age ? : return println("$name: $age") }
  • 43. Consider using null-safe call val order = retrieveOrder() if (order = = null | | order.customer = = null | | order.customer.address = = null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
  • 44. Consider using null-safe call val order = retrieveOrder() val city = order ? . customer ? . address ? . city
  • 45. Consider using null-safe call val order = retrieveOrder() val city = order ? . customer ? . address ? . city ? : throw IllegalArgumentException("Invalid Order")
  • 46. Avoid not-null assertions !! val order = retrieveOrder() val city = order ! ! .customer ! ! .address ! ! .city “You may notice that the double exclamation mark looks a bit rude: it’s almost like you’re yelling at the compiler. This is intentional.” - Kotlin in Action
  • 47. Avoid not-null assertions !! class MyTest { class State(val data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } }
  • 48. Avoid not-null assertions !! class MyTest { class State(val data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } } class MyTest { class State(val data: String) private lateinit var state: State @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state.data) } } - use lateinit
  • 49. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) }
  • 50. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 51. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } No need for an extra variable retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 52. Consider using ?.let for null-checks val order = retrieveOrder() if (order ! = null){ processCustomer(order.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . let { processCustomer(it.customer) } retrieveOrder() ? . customer ? . let { : : processCustomer } or
  • 53. Consider using safe cast for type checking override fun equals(other: Any?) : Boolean { val command = other as Command return command.id = = id }
  • 54. Consider using safe cast for type checking override fun equals(other: Any?) : Boolean { val command = other as Command return command.id = = id } override fun equals(other: Any?) : Boolean { return (other as? Command) ? . id = = id }
  • 55. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c > = 'A' & & c < = 'Z'
  • 56. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c > = 'A' & & c < = 'Z'
  • 57. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c in 'A' . . 'Z'
  • 58. Use range checks instead of comparison pairs fun isLatinUppercase(c: Char) = c in 'A' . . 'Z'
  • 59. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } }
  • 60. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } }
  • 61. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") }
  • 62. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") }
  • 63. Ranges in loops fun main(args: Array<String>) { for (i in 0 . . args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") } for ((i, arg) in args.withIndex()) { println("$i: $arg") }
  • 65. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } }
  • 66. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } }
  • 67. Don’t create classes just to hold functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() }
  • 68. Use extension functions class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } fun String.isPhoneNumber() = length = = 7 & & all { it.isDigit() }
  • 69. Extension or a member? https://kotlinlang.org/docs/coding-conventions.html#extension-functions •Use extension functions liberally. •If a function works primarily on an object, consider making it an extension with that object as a receiver. •Minimize API pollution, restrict the visibility. •As necessary, use local extension functions, member extension functions, or top-level extension functions with private visibility.
  • 70. Use default values instead of overloading class Phonebook { fun print() { print(",") } fun print(columnSeparator: String) {} } fun main(args: Array<String>) { Phonebook().print("|") }
  • 71. Use default values instead of overloading class Phonebook { fun print() { print(",") } fun print(columnSeparator: String) {} } fun main(args: Array<String>) { Phonebook().print("|") } class Phonebook { fun print(separator: String = ",") {} fun someFun(x: Int) {} } fun main(args: Array<String>) { Phonebook().print(separator = "|") }
  • 72. Return multiple values using data classes fun namedNum(): Pair<Int, String> = 1 to "one" / / same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second }
  • 73. Return multiple values using data classes fun namedNum(): Pair<Int, String> = 1 to "one" / / same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second } data class GameResult( val rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") }
  • 74. Return multiple values using data classes data class GameResult( val rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") } GameResult var1 = namedNum(); int var2 = var1.component1(); String var3 = var1.component2();
  • 75. Destructuring in loops fun printMap(map: Map<String, String>) { for (item in map.entries) { println("${item.key} - > ${item.value}") } }
  • 76. Destructuring in loops fun printMap(map: Map<String, String>) { for (item in map.entries) { println("${item.key} - > ${item.value}") } } fun printMap(map: Map<String, String>) { for ((key, value) in map) { println("$key - > $value") } }
  • 77. Destructuring in lists data class NameExt( val name: String, val ext: String? ) fun splitNameExt(filename: String): NameExt { if ('.' in filename) { val parts = filename.split('.', limit = 2) return NameExt(parts[0], parts[1]) } return NameExt(filename, null) } fun splitNameAndExtension(filename: String): NameExt { if ('.' in filename) { val (name, ext) = filename.split('.', limit = 2) return NameExt(name, ext) } return NameExt(filename, null) }
  • 78.
  • 79. Use type aliases for functional types class Event class EventDispatcher { fun addClickHandler(handler: (Event) - > Unit) {} fun removeClickHandler(handler: (Event) - > Unit) {} }
  • 80. Use type aliases for functional types class Event class EventDispatcher { fun addClickHandler(handler: (Event) - > Unit) {} fun removeClickHandler(handler: (Event) - > Unit) {} } typealias ClickHandler = (Event) - > Unit class EventDispatcher { fun addClickHandler(handler: ClickHandler) { } fun removeClickHandler(handler: ClickHandler) { } }
  • 82. Verify parameters using require() class Person( val name: String?, val age: Int ) fun processPerson(person: Person) { if (person.age < 18) { throw IllegalArgumentException("Adult required") } }
  • 83. Verify parameters using require() class Person( val name: String?, val age: Int ) fun processPerson(person: Person) { if (person.age < 18) { throw IllegalArgumentException("Adult required") } } fun processPerson(person: Person) { require(person.age > = 18) { "Adult required" } }
  • 84. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) = objects.filter { it is String }
  • 85. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) = objects.filter { it is String } fun findAllStrings(objects: List<Any>) = objects.filterIsInstance<String>()
  • 86. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) : List<Any> = objects.filter { it is String } fun findAllStrings(objects: List<Any>) : List<String> = objects.filterIsInstance<String>()
  • 87. Apply operation to non-null elements mapNotNull data class Result( val data: Any?, val error: String? ) fun listErrors(results: List<Result>): List<String> = results.map { it.error }.filterNotNull() fun listErrors(results: List<Result>): List<String> = results.mapNotNull { it.errorMessage }
  • 88. compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 - > val rc = person1.name.compareTo(person2.name) if (rc ! = 0) rc else person1.age - person2.age })
  • 89. compareBy compares by multiple keys class Person( val name: String, val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 - > val rc = person1.name.compareTo(person2.name) if (rc ! = 0) rc else person1.age - person2.age }) fun sortPersons(persons: List<Person>) = persons.sortedWith(compareBy(Person : : name, Person : : age))
  • 90. groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request > > () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } }
  • 91. groupBy to group elements class Request( val url: String, val remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request > > () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } fun analyzeLog(log: List<Request>) { val map = log.groupBy(Request : : url) }
  • 92. Use coerceIn to ensure numbers in range fun updateProgress(value: Int) { val actualValue = when { value < 0 - > 0 value > 100 - > 100 else - > value } } fun updateProgress(value: Int) { val actualValue = value.coerceIn(0, 100) }
  • 93. Initializing objects with apply val dataSource = BasicDataSource( ) dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url = "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4 val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
  • 94. Initializing objects with apply final ClientBuilder builder = new ClientBuilder(); builder.setFirstName("Anton"); builder.setLastName("Arhipov"); final TwitterBuilder twitterBuilder = new TwitterBuilder(); twitterBuilder.setHandle("@antonarhipov"); builder.setTwitter(twitterBuilder.build()); final CompanyBuilder companyBuilder = new CompanyBuilder(); companyBuilder.setName("JetBrains"); companyBuilder.setCity("Tallinn"); builder.setCompany(companyBuilder.build()); final Client client = builder.build(); System.out.println("Created client is: " + client);
  • 95. Initializing objects with apply val builder = ClientBuilder() builder.firstName = "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client")
  • 96. Initializing objects with apply val builder = ClientBuilder() builder.firstName = "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client") val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 98. “Domain Speci fi c”, i.e. tailored for a speci fi c task
  • 99. “Domain Speci fi c”, i.e. tailored for a speci fi c task Examples: •Compose strings - stringBuilder •Create HTML documents - kotlinx.html •Con fi gure routing logic for a web app - ktor •Generally, build any object graphs. See “type-safe builders”
  • 100. buildString //Java String name = "Joe"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append("Hello, "); sb.append(name); sb.append("!n"); } System.out.println(sb); //Kotlin val name = "Joe" val s = buildString { repeat(5) { append("Hello, ") append(name) appendLine("!") } } println(s)
  • 101. kotlinx.html System.out.appendHTML().html { body { div { a("http: / / kotlinlang.org") { target = ATarget.blank +"Main site" } } } }
  • 102. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) }
  • 103. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) } Ktor’s routing
  • 104. Ktor fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 . . 10) { li { +"$n" } } } } } } } }.start(wait = true) } kotlinx.html Ktor’s routing
  • 106. Build your vocabulary to abstract from scope functions val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 107. Build your vocabulary to abstract from scope functions fun client(c: ClientBuilder.() - > Unit): Client { val builder = ClientBuilder() c(builder) return builder.build() } fun ClientBuilder.company(block: CompanyBuilder.() - > Unit) { company = CompanyBuilder().apply(block).build() } fun ClientBuilder.twitter(block: TwitterBuilder.() - > Unit) { twitter = TwitterBuilder().apply(block).build() } val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  • 108. val client = client { firstName = "Anton" lastName = "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } } println("Created client is: $client") Build your vocabulary to abstract from scope functions val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")