Skip to content

Instantly share code, notes, and snippets.

@kiumars
Forked from marc0der/README.md
Created February 18, 2022 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kiumars/f6eb8eded38dbbe2513c8581c69142cc to your computer and use it in GitHub Desktop.
Save kiumars/f6eb8eded38dbbe2513c8581c69142cc to your computer and use it in GitHub Desktop.

Functional error handling in Kotlin

Agenda:

  • About the (simple) exercise
  • Higher Order Functions
  • Throwing exceptions: here be dragons!!
  • Sentinel values: better but not great
  • The Option: error as an ADT
  • Functional combinators: add some sugar
  • Trapping exceptions
  • Defer exception handling
  • Monad comprehensions with Arrow.kt

Functional concepts in this session:

  • HOFs (Higher Order Functions)
  • Monad
  • Functor
  • Comprehension

The problem domain

Provide a function that performs a simple arithmetic calculation:

fun calculate(a: Int, b: Int): Result<Int, Int>
  1. Multiply the two numbers (product)
  2. Add the two numbers (sum)
  3. Divide the product (dividend) by the sum (divisor)
  4. Calculate the quotient and remainder as Result<Int, Int>

All calculation steps to be performed over a dodgy network!!!

Example

a = 11
b = 5

product = 55
sum = 16

quotient = 3
remainder = 7
package fpinkotlin
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.computations.option
import arrow.core.getOrElse
import java.io.IOException
/*
sealed class Option<out A> { companion object }
data class Some<out A>(val get: A): Option<A>()
object None : Option<Nothing>()
// functor
fun <A, B> Option<A>.map(f: (A) -> B): Option<B> =
when(this) {
is Some -> Option.unit(f(this.get))
is None -> None
}
// monad
fun <A, B> Option<A>.flatMap(f: (A) -> Option<B>): Option<B> =
this.map { a -> f(a) }.getOrElse { None }
fun <A> Option.Companion.unit(a: A): Option<A> = Some(a)
// extra
fun <A> Option<A>.getOrElse(f: () -> A): A =
when(this) {
is Some -> this.get
is None -> f()
}
*/
fun <A> remote(f: () -> A): A = if (Math.random() > 0.2) f() else throw IOException("boom!")
fun <A> trap(f: () -> A): Option<A> =
try {
Some(f())
} catch (e: Throwable) {
None
}
typealias Result = Pair<Int, Int>
fun calculate1(a: Int, b: Int): Option<Result> =
multiply(a, b).flatMap { product ->
add(a, b).flatMap { sum ->
divide(product, sum).flatMap { quotient ->
modulo(product, sum).map { rem ->
quotient to rem
}
}
}
}
suspend fun calculate2(a: Int, b: Int): Option<Result> =
option {
val product = multiply(a, b).bind()
val sum = add(a, b).bind()
val quotient = divide(product, sum).bind()
val rem = modulo(product, sum).bind()
quotient to rem
}
private fun multiply(a: Int, b: Int) = trap { remote { a * b } }
private fun add(a: Int, b: Int) = trap { remote { a + b } }
private fun divide(product: Int, sum: Int) = trap { remote { product / sum } }
private fun modulo(product: Int, sum: Int) = trap { remote { product % sum } }
suspend fun main() {
println(calculate1(11, 5).getOrElse { "computer says no!" })
println(calculate2(11, 5).getOrElse { "computer says no!" })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment