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

KindOf<Polymorphism>

 KindOf<Polymorphism>

Suppose you are writing software and you have to choose how to model an async operation,

will you choose RxJava? Or maybe Arrow IO?

What if you can write your code based just on some behaviors and then let your future self choose the proper implementation for each scenario?

In this talk, we will use some concepts of functional programming that will enable us to do just that.

We will explore:

- Typeclasses

- Higher Kind

- Polymorphic Programs

Daniele Campogiani

November 30, 2019
Tweet

More Decks by Daniele Campogiani

Other Decks in Programming

Transcript

  1. SEMIGROUP interface Semigroup<A> { fun A.combine(b: A): A } interface

    StringSemigroup : Semigroup<String> { override fun String.combine(b: String): String = “${this}$b" } interface IntSemigroup : Semigroup<Int> { override fun Int.combine(b: Int): Int = this + b }
  2. MONOID interface Monoid<A> : Semigroup<A> { fun empty(): A fun

    Collection<A>.combineAll(): A = if (isEmpty()) empty() else reduce { a, b -> a.combine(b) } } interface StringMonoid : Monoid<String>, StringSemigroup { override fun empty(): String = "" } interface IntMonoid : Monoid<Int>, IntSemigroup { override fun empty(): Int = 0 }
  3. MONOID val result = String.monoid() .run { listOf("H", "e", "l",

    "l", "o").combineAll() } //"Hello" val result = Int.monoid() .run { listOf(1, 2, 3, 4, 5, 6, 7, 8).combineAll() } //36
  4. HIGHER KINDS ▸Kind<F,A> ▸F type of container ▸A type of

    content ▸List<Int> : ▸F -> List ▸A -> Int
  5. OPTION sealed class Option<out A> data class Some<out A>(val value:

    A) : Option<A>() object None : Option<Nothing>()
  6. OPTION - MAP sealed class Option<out A> { fun <B>

    map(f: (A) -> B): Option<B> = when (this) { is Some -> Some(f(value)) None -> None } }
  7. KIND<F,A> sealed class Option<out A> : Kind<ForOption, A> data class

    ListK<out A>( private val list: List<A> ) : Kind<ForListK, A>, List<A> by list inline fun <A> OptionOf<A>.fix(): Option<A> = this as Option<A> inline fun <A> ListKOf<A>.fix(): ListK<A> = this as ListK<A>
  8. FUNCTOR: TYPE CLASSES + HIGHER KINDS interface Functor<F> { fun

    <A, B> Kind<F, A>.map(f: (A) -> B): Kind<F, B> // more methods for free derived from map } interface OptionFunctor : Functor<ForOption> { override fun <A, B> OptionOf<A>.map(f: (A) -> B): Option<B> = fix().map(f) } interface ListKFunctor : Functor<ForListK> { override fun <A, B> Kind<ForListK, A>.map(f: (A) -> B): ListK<B> = fix().map(f) }
  9. FUNCTOR: TYPE CLASSES + HIGHER KINDS fun <F> parseInt( value:

    Kind<F, String>, functor: Functor<F> ): Kind<F, Int> = functor.run { value.map { it.toInt() } }
  10. FUNCTOR: TYPE CLASSES + HIGHER KINDS val input: Option<String> =

    Option("42") val output: Option<Int> = parseInt(input, Option.functor()).fix() //Some(42) val input: ListK<String> = listOf("42", "84").k() val output: List<Int> = parseInt(input, ListK.functor()).fix() //ListK(list=[42, 84]) val input = Observable.just("42").k() val output = parseInt(input, ObservableK.functor()).fix().observable output.subscribe { println(it) } //42
  11. APPLICATIVE: TYPE CLASSES + HIGHER KINDS interface Applicative<F> : Functor<F>

    { fun <A> just(a: A): Kind<F, A> fun <A, B> Kind<F, A>.ap(ff: Kind<F, (A) -> B>): Kind<F, B> // more methods for free derived }
  12. APPLICATIVE: TYPE CLASSES + HIGHER KINDS fun <F> multiply( amount:

    Kind<F, Float>, multiplier: Kind<F, Float>, applicative: Applicative<F> ): Kind<F, Float> = applicative.map(amount, multiplier) { it.a * it.b }
  13. APPLICATIVE: TYPE CLASSES + HIGHER KINDS val option = multiply(42f.some(),

    2f.some(), Option.applicative()).fix() //Some(84.0) val observable = multiply( 42f.observable(), 2f.observable(), ObservableK.applicative()).fix() observable.subscribe { println("$it") } //84.0
  14. APPLICATIVE ERROR AND ASYNC interface ApplicativeError<F, E> : Applicative<F> {

    fun <A> raiseError(e: E): Kind<F, A> //others } interface Async<F> : ApplicativeError<F, Throwable> { fun <A> async(callback: ((Either<Throwable, A>) -> Unit) -> Unit): Kind<F, A> //others }
  15. GET BITCOIN FOR A USER private val data = mapOf(

    "Bam" to 0.2f, "Daniele" to 0f ) fun <F> getBitcoinForUser( username: String, AE: ApplicativeError<F, Throwable> ): Kind<F, Float> { val dataFromMap = data[username] return if (dataFromMap != null) { AE.just(dataFromMap) } else { AE.raiseError(RuntimeException("No user found")) } }
  16. GET REALTIME PRICE fun <F> getPriceInEUR(api: Api, async: Async<F>): Kind<F,

    InternalResponse> { return async.async<InternalResponse> { callback: (Either<Throwable, InternalResponse>) -> Unit -> api.currentPrice("EUR").enqueue( onResponse = { callback(it.right()) }, onFailure = { callback(it.left()) }) } }
  17. GET REALTIME PRICE fun <F> parseResponse( response: Kind<F, InternalResponse>, functor:

    Functor<F> ): Kind<F, Float> { return functor.run { response.map { it.bpi.EUR.rate_float } } }
  18. DO LOGIC fun <F> calculate( quantity: Kind<F, Float>, price: Kind<F,

    Float>, applicative: Applicative<F>): Kind<F, Float> { return applicative.map(quantity, price) { it.a * it.b } }
  19. BUILD USECASE fun <F> useCase( username: String, api: Api, A:

    Async<F>): Kind<F, Float> { val bitcoinForUser = getBitcoinForUser(username, A) val priceResponse = getPriceInEUR(api, A) val price = parseResponse(priceResponse, A) return calculate(bitcoinForUser, price, A) }
  20. EXECUTE IO SUCCESS useCase("Bam", api, IO.async()).fix() .unsafeRunAsync { result ->

    result.fold( { println("Error: $it") }, { println("Success :$it”) }) } //Success :1604.45
  21. EXECUTE IO FAILURE useCase("Donald90", api, IO.async()).fix() .unsafeRunAsync { result ->

    result.fold( { println("Error: $it") }, { println("Success :$it") }) } //Error: java.lang.RuntimeException: No user found
  22. EXECUTE RX FAILURE useCase("Donald90", api, ObservableK.async()).fix().observable .subscribe( { println("OnNext: $it")

    }, { println("OnError: $it") } ) //OnError: java.lang.RuntimeException: No user found //OnError: java.net.UnknownHostException: api.coindesk.com
  23. RECAP ▸ Each type class has its logic ▸ We

    can write less code combining existing methods ▸ We can ignore the type of container