$30 off During Our Annual Pro Sale. View Details »

Introduction to KΛTEGORY

Introduction to KΛTEGORY

http://kategory.io/

KΛTEGORY is a library that started as a port of Typelevel Scala cats for the Kotlin Programming Language and is now evolving to bring the best of typed FP to Kotlin. This talk covers some of the main data types and abstractions that make typed Functional Programming in Kotlin Possible. Kategory features techniques such as monadic comprehensions, laws, applicative builders, emulated higher kinds, free monads, and global typeclass instances for the increasingly popular Kotlin programming language. We discuss some pragmatic applications of KΛTEGORY and some of the different styles you can adopt when working with typed FP in Kotlin alogn with a proposal to introduce type classes and higher kinds as part of the language.

https://github.com/47deg/kategory-intro

Raúl Raja Martínez

October 27, 2017
Tweet

More Decks by Raúl Raja Martínez

Other Decks in Programming

Transcript

  1. KΛTEGORY
    Functional Data Types & Abstractions for Kotlin
    1

    View Slide

  2. About Us
    Raul Raja
    CTO @47deg
    Paco
    Mistake-driven learning
    Simon Vergauwen
    Optometrist
    @raulraja @pacoworks @vergauwen_simon
    2

    View Slide

  3. What started as...
    Learning Exercise to learn FP over Slack
    3

    View Slide

  4. ...Ended in
    Solution for Typed FP in Kotlin
    4

    View Slide

  5. What is KΛTEGORY?
    A library for typed FP in Kotlin with the traditional type classes and data types
    5

    View Slide

  6. Data types
    Many data types to cover general use cases.
    Error Handling Option , Try , Validated , Either , Ior
    Collections ListKW , SequenceKW , MapKW , SetKW
    RWS Reader , Writer , State
    Transformers ReaderT , WriterT , OptionT , StateT , EitherT
    Evaluation Eval , Trampoline , Free , FunctionN
    Effects IO , Free , ObservableKW
    Others Coproduct , Coreader , Const , ...
    6

    View Slide

  7. A few syntax examples
    import kategory.Option
    Option(1).map { it + 1 }
    //Option(2)
    7

    View Slide

  8. A few syntax examples
    import kategory.Try
    Try { throw RuntimeException("BOOM!") }.map { it + 1 }
    //Failure(RuntimeException("BOOM!"))
    8

    View Slide

  9. A few syntax examples
    import kategory.*
    import kategory.Either.*
    val x = Right(1)
    val y = 1.right()
    x == y
    //true
    9

    View Slide

  10. Applicative Builder
    import kategory.*
    data class Profile(val id: Long, val name: String, val phone: Int)
    fun profile(val maybeId: Option,
    val maybeName: Option,
    val maybePhone: Option): Option =
    Option.applicative().map(id, name, phone, { (a, b, c) ->
    Profile(a, b, c)
    })
    profile(1L.some(), "William Alvin Howard".some(), 555555555.some())
    //Some(Profile(1L, "William Alvin Howard", 555555555)
    10

    View Slide

  11. Comprehensions - Vanilla
    Generalized to all monads
    fun profile(val maybeId: Option,
    val maybeName: Option,
    val maybePhone: Option): Option =
    Option.monad().binding { // <-- `coroutine starts`
    val id = maybeId.bind() // <-- `suspended`
    val name = maybeName.bind() // <-- `suspended`
    val phone = maybePhone.bind() // <-- `suspended`
    yields(Profile(id, name, phone))
    } // <-- `coroutine ends`
    profile(2L.some(), "Haskell Brooks Curry".some(), 555555555.some())
    11

    View Slide

  12. Comprehensions - Exception Aware
    Automatically captures exceptions for instances of MonadError
    Try.monadError().bindingE {
    val name = profileService().bind()
    val phone = phoneService().bind()
    throw RuntimeException("BOOM") // <-- `raises errors to MonadError`
    val addresses = addressService().bind()
    yields(Profile(name, phone, addresses))
    }
    //Failure(RuntimeException("BOOM"))
    12

    View Slide

  13. Comprehensions - Stack-Safe
    Stack-Safe comprehensions for Stack-Unsafe data types
    fun stackSafeTestProgram(M: Monad, n: Int = 0, stopAt: Int = 1000000): Free =
    M.bindingStackSafe {
    val v = pure(n + 1).bind() // <-- auto binds on `Free`
    val r = if (v < stopAt) stackSafeTestProgram(M, v, stopAt).bind()
    else pure(v).bind()
    yields(r)
    }
    val M = Id.monad()
    stackSafeTestProgram(M).run(M).ev()
    //Id(1000000)
    13

    View Slide

  14. Monad Comprehensions - Cancellable
    Supports cancelling tasks initiated inside the coroutine
    val (binding: IO>, unsafeCancel: Disposable) =
    ioMonadError.bindingECancellable {
    val userProfile = bindAsync(ioAsync) { getUserProfile("123") }
    val friendProfiles = userProfile.friends().map { friend ->
    bindAsync(ioAsync) { getProfile(friend.id) }
    }
    yields(listOf(userProfile) + friendProfiles)
    } // <- returns `Tuple2>, Disposable>`
    unsafeCancel() //the disposable instance can cancel all operations inside the coroutine
    14

    View Slide

  15. Monad Comprehensions - Context Aware
    Allows switching contexts in which bind/flatMap takes place
    ioMonad.binding {
    val user = bindAsync(ioAsync) { getUserProfile("123") } //<-- binds on IO's pool
    bindIn(DatabaseContext) { storeUser(user) } //<-- binds on DB's pool
    bindIn(UIContext) { toastMessage("User cached!") } //<-- binds on UI thread
    yields(user)
    }
    15

    View Slide

  16. Monad Comprehensions - 3rd party integrations
    Allows seamless integration with existing libraries like RxJava
    observableMonad.binding {
    val ticks = runAsync(observableAsync) {
    videoPlayer.getCurrentMilliseconds()
    }.subscribeOn(mainThread())
    val initialTick = ticks.bind()
    val timer = Observable.interval(100, MilliSeconds).k().bind()
    val currentTick = ticks.bind()
    yields(currentTick - initialTick)
    }
    16

    View Slide

  17. Transforming immutable data
    KΛTEGORY includes an optics library that make working with immutable data a breeze
    data class Street(val number: Int, val name: String)
    data class Address(val city: String, val street: Street)
    data class Company(val name: String, val address: Address)
    data class Employee(val name: String, val company: Company)
    val employee = Employee("John Doe",
    Company("Kategory",
    Address("Functional city",
    Street(23, "lambda street"))))
    employee
    //Employee(name=John Doe, company=Company(name=Kategory, address=Address(city=Functional city, street=Street(number=23, name
    17

    View Slide

  18. Transforming immutable data
    while kotlin provides a synthetic copy dealing with nested data can be tedious
    employee.copy(
    company = employee.company.copy(
    address = employee.company.address.copy(
    street = employee.company.address.street.copy(
    name = employee.company.address.street.name.capitalize()
    )
    )
    )
    )
    //Employee(name=John Doe, company=Company(name=Kategory, address=Address(city=Functional city, street=Street(number=23, name
    18

    View Slide

  19. Optics without boilerplate
    You may define composable Lenses to work with immutable data transformations
    import kategory.*
    import kategory.optics.*
    val employeeCompany: Lens = Lens(
    get = { it.company },
    set = { company -> { employee -> employee.copy(company = company) } }
    )
    val companyAddress: Lens = Lens(
    get = { it.address },
    set = { address -> { company -> company.copy(address = address) } }
    )
    ...
    val employeeStreetName: Lens =
    employeeCompany compose companyAddress compose addressStrees compose streetName
    employeeStreetName.modify(employee, String::capitalize)
    19

    View Slide

  20. Optics without boilerplate
    Or just let KΛTEGORY @lenses do the dirty work
    + @lenses data class Employee(val name: String, val company: Company)
    - val employeeCompany: Lens = Lens(
    - get = { it.company },
    - set = { company -> { employee -> employee.copy(company = company) } }
    - )
    -
    - val companyAddress: Lens = Lens(
    - get = { it.address },
    - set = { address -> { company -> company.copy(address = address) } }
    - )
    - ...
    val employeeStreetName: Lens =
    employeeCompany compose companyAddress compose addressStrees compose streetName
    employeeStreetName.modify(employee, String::capitalize)
    20

    View Slide

  21. Optics without boilerplate
    You can also define custom Prism for your sum types
    sealed class NetworkResult {
    data class Success(val content: String): NetworkResult()
    object Failure: NetworkResult()
    }
    val networkSuccessPrism: Prism = Prism(
    getOrModify = { networkResult ->
    when(networkResult) {
    is NetworkResult.Success -> networkResult.right()
    else -> networkResult.left()
    }
    },
    reverseGet = { networkResult -> networkResult } //::identity
    )
    val networkResult = NetworkResult.Success("content")
    networkSuccessPrism.modify(networkResult) { success ->
    success.copy(content = "different content")
    }
    //Success(content=different content)
    21

    View Slide

  22. Optics without boilerplate
    Or let @prisms do that for you
    + @prisms sealed class NetworkResult {
    + data class Success(val content: String) : NetworkResult()
    + object Failure : NetworkResult()
    + }
    - sealed class NetworkResult {
    - data class Success(val content: String): NetworkResult()
    - object Failure: NetworkResult()
    - }
    - val networkSuccessPrism: Prism = Prism(
    - getOrModify = { networkResult ->
    - when(networkResult) {
    - is NetworkResult.Success -> networkResult.right()
    - else -> networkResult.left()
    - }
    - },
    - reverseGet = { networkResult -> networkResult } //::identity
    - )
    val networkResult = NetworkResult.Success("content")
    networkSuccessPrism.modify(networkResult) { success ->
    success.copy(content = "different content")
    }
    22

    View Slide

  23. @free & @tagless
    is being ported to Kotlin by the team .
    Freestyle @47deg
    + @free interface GesturesDSL : GesturesDSLKind {
    + fun click(view: UiObject): FreeS
    + fun pinchIn(view: UiObject, percent: Int, val steps: Int): FreeS
    + fun pinchOut(view: UiObject, percent: Int, val steps: Int): FreeS
    + }
    - typealias ActionDSL = Free
    - @higherkind sealed class GesturesDSL : GesturesDSLKind {
    - object PressHome : GesturesDSL()
    - data class Click(val view: UiObject) : GesturesDSL()
    - data class PinchIn(val view: UiObject, val percent: Int, val steps: Int) : GesturesDSL()
    - data class PinchOut(val view: UiObject, val percent: Int, val steps: Int) : GesturesDSL()
    - companion object : FreeMonadInstance
    - }
    - fun click(view: UiObject): ActionDSL =
    - Free.liftF(GesturesDSL.Click(ui))
    - fun pinchIn(view: UiObject, percent: Int, val steps: Int): ActionDSL =
    - Free.liftF(GesturesDSL.PinchIn(percent, steps))
    - fun pinchOut(view: UiObject, percent: Int, val steps: Int): ActionDSL =
    - Free.liftF(GesturesDSL.PinchOut(ui, percent, steps))
    23

    View Slide

  24. KΛTEGORY is becoming modular
    Pick and choose what you'd like to use.
    Module Contents
    typeclasses Semigroup , Monoid , Functor , Applicative , Monad ...
    data Option , Try , Either , Validated ...
    effects IO
    effects-rx2 ObservableKW , FlowableKW
    mtl MonadReader , MonadState , MonadFilter ,...
    free Free , FreeApplicative , Trampoline , ...
    freestyle @free , @tagless
    recursion-schemes
    optics Prism , Iso , Lens , ...
    collections
    meta @higherkind , @deriving , @implicit , @instance , @lenses , @prisms , @isos
    24

    View Slide

  25. Kotlin limitations for Typed FP
    25

    View Slide

  26. Kotlin limitations for Typed FP
    Emulated Higher Kinds through Lightweight higher-kinded Polymorphism
    interface HK
    class OptionHK private constructor()
    typealias OptionKind = kategory.HK
    inline fun OptionKind.ev(): Option = this as Option
    sealed class Option : OptionKind
    26

    View Slide

  27. Kotlin limitations for Typed FP
    Fear not, @higherkind 's got your back!
    + @higherkind sealed class Either : EitherKind
    -
    - class EitherHK private constructor()
    -
    - typealias EitherKindPartial = kindedj.Hk
    - typealias EitherKind = kindedj.Hk, B>
    -
    - inline fun EitherKind.ev(): Either = this as Either
    27

    View Slide

  28. Kotlin limitations for Typed FP
    No notion of implicits or Type class instance evidences verified at compile time
    fun A.some(
    AA: Applicative /*<-- User is forced to provide instances explicitly */): Option =
    AA.pure(this).ev()
    1.some(Option.applicative()) //Option(1)
    28

    View Slide

  29. Kotlin limitations for Typed FP
    (for now) an implicit lookup system based on a global registry is provided
    fun A.some(
    AA: Applicative = applicative() /*<-- Instances are discovered implicitly */): Option =
    AA.pure(this).ev()
    1.some() //Option(1)
    29

    View Slide

  30. KΛTEGORY ad-hoc polymorphism
    With emulated Higher Kinds and Type classes we can now write polymorphic code
    inline fun raiseError(e: E, ME: MonadError = monadError()): HK =
    ME.raiseError(e)
    raiseError, String, Int>("Not Found").ev() // <-- Not ideal
    //Left("Not Found")
    30

    View Slide

  31. Type Classes
    This is how you define Type Classes in KΛTEGORY (for now)
    interface Functor : Typeclass {
    fun map(fa: HK, f: (A) -> B): HK
    }
    31

    View Slide

  32. Implementing type class instances is easy...
    32

    View Slide

  33. @deriving
    KΛTEGORY can derive instances based on conventions in your data types
    @higherkind
    @deriving(
    Functor::class,
    Applicative::class,
    Monad::class,
    Foldable::class,
    Traverse::class,
    TraverseFilter::class,
    MonadFilter::class)
    sealed class Option : OptionKind {
    ...
    }
    33

    View Slide

  34. @instance
    KΛTEGORY can also derive implicit discovery for manual instances
    @instance(Either::class)
    interface EitherFunctorInstance : Functor> {
    override fun map(fa: EitherKind, f: (A) -> B): Either =
    fa.ev().map(f)
    }
    34

    View Slide

  35. KEEP-87
    But we are not stopping here, we want to get rid of some of these hacks!
    KEEP-87 is A KEEP to introduce Type Classes in Kotlin
    https:/
    /github.com/Kotlin/KEEP/pull/87
    35

    View Slide

  36. KEEP-87
    If KEEP-87 makes it to the lang
    extension interface Functor> { // <-- real Higher kinds positions
    fun map(fa: F, f: (A) -> B): F
    }
    extension object OptionFunctor: Functor { // <-- real Higher kinds positions
    fun map(fa: Option, f: (A) -> B): Option
    }
    fun , A, B> transform(
    fa: F, f: (A) -> B): F given Functor = map(fa, f) // <-- compile time verified
    transform(Option(1), { it + 1 })// <-- no need to cast from HK representation
    //Option(2)
    36

    View Slide

  37. What if KEEP-87 does not make it to Kotlin?
    @implicit as global implicits through a annotation processor / compiler plugin.
    Discuss if a fork to the compiler with compatibility for KEEP-87 under a compiler flag
    makes sense.
    37

    View Slide

  38. Credits
    KΛTEGORY is inspired in great libraries that have proven useful to the FP community:
    Cats
    Scalaz
    Freestyle
    Monocle
    Funktionale
    Paguro
    38

    View Slide

  39. Team
    anstaendig arturogutierrez ffgiraldez Guardiola31337 javipacheco JMPergar
    JorgeCastilloPrz jrgonzalezg nomisRev npatarino pablisco pakoito
    pedrovgs pt2121 raulraja wiyarmir andyscott Atternatt
    calvellido dominv GlenKPeterson israelperezglez sanogueralorenzo Takhion
    victorg1991 tonilopezmr NigelHeylen ersin-ertan
    39

    View Slide

  40. Join us!
    Github
    Slack
    Gitter
    We provide 1:1 mentoring for both users & new contributors!
    https:/
    /github.com/kategory
    https:/
    /kotlinlang.slack.com/messages/C5UPMM0A0
    https:/
    /gitter.im/kategory/Lobby
    40

    View Slide

  41. Thanks!
    Thanks to everyone that makes KΛTEGORY possible
    41

    View Slide