Slide 1

Slide 1 text

Functional Fun in Kotlin 47deg.com

Slide 2

Slide 2 text

Who am I?

Slide 3

Slide 3 text

Functional Fun in Kotlin Dependency Injection Side effects Typed Errors

Slide 4

Slide 4 text

Dependency Injection interface Database { fun query(sql: String, vararg params: String): ResultRow } interface Logger { fun log(message: String): Unit fun error(throwable: Throwable, message: String): Unit }

Slide 5

Slide 5 text

Dependency Injection fun fetchUser(id: Long, db: Database, logger: Logger): User { val user = db.query("SELECT * FROM users WHERE id = $1", id).toUser() logger.log("Query result: $user") return user }

Slide 6

Slide 6 text

Dependency Injection fun fetchUser(id: Long, db: Database, logger: Logger): User { val user = db.query("SELECT * FROM users WHERE id = $1", id).toUser() logger.log("Query result: $user") return user } fun fetchAll(ids: List, db: Database, logger: Logger): List = ids.map { id -> fetchUser(id, db, logger) }

Slide 7

Slide 7 text

Dependency Injection fun Context.fetchUser(id: Long): User

Slide 8

Slide 8 text

Dependency Injection fun Context.fetchUser(id: Long): User where Context : Database, Context : Logger

Slide 9

Slide 9 text

Dependency Injection fun Context.fetchUser(id: Long): User where Context : Database, Context : Logger { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user }

Slide 10

Slide 10 text

Dependency Injection fun Context.fetchUser(id: Long): User where Context : Database, Context : Logger { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user } class Module(db: Database, logger: Logger): Database by db, Logger by logger

Slide 11

Slide 11 text

Dependency Injection fun Context.fetchUser(id: Long): User where Context : Database, Context : Logger { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user } class Module(db: Database, logger: Logger): Database by db, Logger by logger fun Context.fetchAll(ids: List): List where Context : Database, Context : Logger = ids.map { id -> fetchUser(id) }

Slide 12

Slide 12 text

Dependency Injection context(Database, Logger) fun fetchUser(id: Long): User

Slide 13

Slide 13 text

Dependency Injection context(Database, Logger) fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user }

Slide 14

Slide 14 text

Dependency Injection context(Database, Logger) fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user } context(Database, Logger) fun fetchAll(ids: List): List = ids.map { id -> fetchUser(id) }

Slide 15

Slide 15 text

Side effects Compile time tracked effects Composable Reason about code that performs side effects

Slide 16

Slide 16 text

Side effects interface Database { fun query(sql: String, vararg params: String): ResultRow } interface Logger { fun log(message: String): Unit fun error(throwable: Throwable, message: String): Unit }

Slide 17

Slide 17 text

Side effects interface Database { suspend fun query(sql: String, vararg params: String): ResultRow } interface Logger { suspend fun log(message: String): Unit suspend fun error(throwable: Throwable, message: String): Unit }

Slide 18

Slide 18 text

Side effects context(Database, Logger) fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user }

Slide 19

Slide 19 text

Side effects context(Database, Logger) fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user } Suspend function 'query' should be called only from a coroutine or another suspend function Suspend function 'log' should be called only from a coroutine or another suspend function

Slide 20

Slide 20 text

Side effects context(Database, Logger) suspend fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user }

Slide 21

Slide 21 text

Suspend - Continuation Passing Style interface Continuation { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }

Slide 22

Slide 22 text

Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long, cont: Continuation): Unit { query("SELECT * FROM users WHERE id = $1", id, Continuation(cont.context) { res -> }) }

Slide 23

Slide 23 text

Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long, cont: Continuation): Unit { query("SELECT * FROM users WHERE id = $1", id, Continuation(cont.context) { res -> res.fold({ resultRow -> val user = resultRow.toUser() log2("Query result: $user", Continuation(cont.context) { res -> }) }, cont::resumeWithException) }) }

Slide 24

Slide 24 text

Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long, cont: Continuation): Unit { query("SELECT * FROM users WHERE id = $1", id, Continuation(cont.context) { res -> res.fold({ resultRow -> val user = resultRow.toUser() log2("Query result: $user", Continuation(cont.context) { res -> res.fold({ cont.resume(user) }, cont::resumeWithException) }) }, cont::resumeWithException) }) }

Slide 25

Slide 25 text

Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long, cont: Continuation): Unit { query("SELECT * FROM users WHERE id = $1", id, Continuation(cont.context) { res -> res.fold({ resultRow -> val user = resultRow.toUser() log2("Query result: $user", Continuation(cont.context) { res -> res.fold({ cont.resume(user) }, cont::resumeWithException) }) }, cont::resumeWithException) }) }

Slide 26

Slide 26 text

Side effects context(Database, Logger) suspend fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user }

Slide 27

Slide 27 text

Side effects context(Database, Logger) suspend fun fetchUser(id: Long): User { val user = query("SELECT * FROM users WHERE id = $1", id).toUser() log("Query result: $user") return user } context(Database, Logger) suspend fun fetchAll(ids: List): List = ids.map { id -> fetchUser(id) }

Slide 28

Slide 28 text

Compile time tracking of expected errors Explicit in type signature Typed Errors

Slide 29

Slide 29 text

Typed Errors context(Database, Logger) suspend fun fetchUser(id: Long): User?

Slide 30

Slide 30 text

Typed Errors data class UserAlreadyExists(val error: PSQLException) context(Database, Logger) suspend fun insertUser(name: String, email: String): User

Slide 31

Slide 31 text

Typed Errors data class UserAlreadyExists( val name: String, val email: String, val error: PSQLException ) context(Database, Logger) suspend fun insertUser(name: String, email: String): Either

Slide 32

Slide 32 text

Typed Errors data class UserAlreadyExists( val name: String, val email: String, val error: PSQLException ) context(Database, Logger, Raise) suspend fun insertUser(name: String, email: String): User

Slide 33

Slide 33 text

Typed Errors context(Database, Logger, Raise) suspend fun insertUser(name: String, email: String): User = catch({ query(""" INSERT INTO users VALUES ($1, $2) RETURNING user_id; """.trimIndent()).toUser() }) { error: PSQLException -> if (error.isUniqueViolation()) raise(UserAlreadyExists(name, email, error)) else throw error }

Slide 34

Slide 34 text

Monadic DSLs context(Database, Logger, Raise) suspend fun insertUser(name: String, email: String): User = catch({ query(""" INSERT INTO users VALUES ($1, $2) RETURNING user_id; """.trimIndent()).toUser() }) { error: PSQLException -> if (error.isUniqueViolation()) raise(UserAlreadyExists(name, email, error)) else throw error }

Slide 35

Slide 35 text

Arrow DSL based functional programming Idiomatic Kotlin syntax Kotlin Multiplatform ready

Slide 36

Slide 36 text

Saga pattern context(SagaScope) suspend fun sagaOperation(): Result = saga({ action() }) { compensation() }

Slide 37

Slide 37 text

Resource Safety context(ResourceScope, Logger) suspend fun custom(): CustomResource = install({ CustomResource. acquire() }) { res, exitCase -> log("Releasing $res due to $exitCase") res.release() } context(ResourceScope) suspend fun dataSource(config: HikariConfig): HikariDataSource = autoCloseable { HikariDataSource(config) }

Slide 38

Slide 38 text

Thanks!