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

Functional Fun in Kotlin

Functional Fun in Kotlin

Kotlin is great language to do modern functional programming, and in my opinion perhaps the best language to do modern mainstream (hardcore) functional programming. With the power of Kotlin DSLs we can make functional programming idiomatic, simple and elegant. This talk takes us through the different techniques that we can apply in Kotlin to achieve modern, and elegant functional patterns.

Simon Vergauwen

February 04, 2023
Tweet

More Decks by Simon Vergauwen

Other Decks in Programming

Transcript

  1. 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 }
  2. 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 }
  3. 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<Long>, db: Database, logger: Logger): List<User> = ids.map { id -> fetchUser(id, db, logger) }
  4. Dependency Injection fun <Context> 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 }
  5. Dependency Injection fun <Context> 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
  6. Dependency Injection fun <Context> 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> Context.fetchAll(ids: List<Long>): List<User> where Context : Database, Context : Logger = ids.map { id -> fetchUser(id) }
  7. 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 }
  8. 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<Long>): List<User> = ids.map { id -> fetchUser(id) }
  9. 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 }
  10. 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 }
  11. 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 }
  12. 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
  13. 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 }
  14. Suspend - Continuation Passing Style interface Continuation<in T> { val

    context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) }
  15. Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long,

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

    cont: Continuation<User>): 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) }) }
  17. Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long,

    cont: Continuation<User>): 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) }) }
  18. Suspend - Continuation Passing Style context(Database, Logger) fun fetchUser(id: Long,

    cont: Continuation<User>): 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) }) }
  19. 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 }
  20. 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<Long>): List<User> = ids.map { id -> fetchUser(id) }
  21. 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<UserAlreadyExists, User>
  22. Typed Errors data class UserAlreadyExists( val name: String, val email:

    String, val error: PSQLException ) context(Database, Logger, Raise<UserAlreadyExists>) suspend fun insertUser(name: String, email: String): User
  23. Typed Errors context(Database, Logger, Raise<UserAlreadyExists>) 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 }
  24. Monadic DSLs context(Database, Logger, Raise<UserAlreadyExists>) 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 }
  25. 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) }