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

Λrrow - Toward Functional Programming in Kotlin

Λrrow - Toward Functional Programming in Kotlin

https://arrow-kt.io/

Λrrow 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. Λrrow features techniques such as monadic comprehensions, laws, applicative builders, emulated higher kinds, free monads, and type classes for the increasingly popular Kotlin programming language. We discuss some pragmatic applications of Λrrow and some of the different styles you can adopt when working with typed FP in Kotlin along with a proposal to introduce type classes and higher kinds as part of the language.

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

Raúl Raja Martínez

June 04, 2018
Tweet

More Decks by Raúl Raja Martínez

Other Decks in Programming

Transcript

  1. Λrrow = KΛTEGORY + Funktionale We merged with Funktionale to

    provide a single path to FP in Kotlin 5
  2. Type classes Λrrow contains many FP related type classes Error

    Handling ApplicativeError , MonadError Computation Functor , Applicative , Monad , Bimonad , Comonad Folding Foldable , Traverse Combining Semigroup , SemigroupK , Monoid , MonoidK Effects MonadDefer , Async , Effect Recursion Recursive , BiRecursive ,... MTL FunctorFilter , MonadState , MonadReader , MonadWriter , MonadFilter , ... 6
  3. Data types Λrrow contains 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 Optics Lens , Prism , Iso ,... Recursion Fix , Mu , Nu ,... Others Coproduct , Coreader , Const , ... 7
  4. A few syntax examples import arrow.* import arrow.core.* import arrow.intro.*

    // slides stuff Option(1).map { it + 1 } // Some(2) 8
  5. A few syntax examples Try<Int> { throw RuntimeException("BOOM!") }.map {

    it + 1 } // Failure(exception=java.lang.RuntimeException: BOOM!) 9
  6. A few syntax examples val x = Right(1) val y

    = 1.right() x == y // true 10
  7. Applicative Builder import arrow.instances.* data class Profile(val id: Long, val

    name: String, val phone: Int) fun profile(maybeId: Option<Long>, maybeName: Option<String>, maybePhone: Option<Int>): Option<Profile> = ForOption extensions { map(maybeId, maybeName, maybePhone, { (a, b, c) -> Profile(a, b, c) }).fix() } profile(1L.some(), "William Alvin Howard".some(), 555555555.some()) // Some(Profile(id=1, name=William Alvin Howard, phone=555555555)) 11
  8. Applicative Builder @generic data class Profile(val id: Long, val name:

    String, val phone: Int) { companion object } fun profile(maybeId: Option<Long>, maybeName: Option<String>, maybePhone: Option<Int>): Option<Profile> = mapToProfile(maybeId, maybeName, maybePhone).fix() profile(1L.some(), "William Alvin Howard".some(), 555555555.some()) // Some(Profile(id=1, name=William Alvin Howard, phone=555555555)) 12
  9. Applicative Builder (Same for all data types) @generic data class

    Profile(val id: Long, val name: String, val phone: Int) { companion object } fun profile(tryId: Try<Long>, tryName: Try<String>, tryPhone: Try<Int>): Try<Profile> = mapToProfile(tryId, tryName, tryPhone).fix() profile(Try { 1L }, Try { "William Alvin Howard" }, Try { 555555555 }) // Success(Profile(id=1, name=William Alvin Howard, phone=555555555)) 13
  10. Comprehensions - Vanilla Generalized to all monads. A suspended function

    provides a non blocking F<A> -> A import arrow.typeclasses.* fun profile(maybeId: Option<Long>, maybeName: Option<String>, maybePhone: Option<Int>): Option<Profile> = ForOption extensions { binding { // <-- `coroutine starts` val id = maybeId.bind() // <-- `suspended` val name = maybeName.bind() // <-- `suspended` val phone = maybePhone.bind() // <-- `suspended` Profile(id, name, phone) }.fix() // <-- `coroutine ends` } profile(2L.some(), "Haskell Brooks Curry".some(), 555555555.some()) // Some(Profile(id=2, name=Haskell Brooks Curry, phone=555555555)) 14
  11. Comprehensions - Exception Aware Automatically captures exceptions for instances of

    MonadError<F, Throwable> ForTry extensions { bindingCatch { val a = Try { 1 }.bind() val b = Try { 1 }.bind() throw RuntimeException("BOOM") // <-- `raises errors to MonadError<F, Throwable>` val c = Try { 1 }.bind() a + b + c } } // Failure(exception=java.lang.RuntimeException: BOOM) 15
  12. Comprehensions - Filterable Imperative filtering control for data types that

    can provide empty values. import arrow.data.* import arrow.mtl.typeclasses.* import arrow.mtl.instances.* fun <F> MonadFilter<F>.continueIfEven(fa: Kind<F, Int>): Kind<F, Int> = bindingFilter { val v = fa.bind() continueIf(v % 2 == 0) v + 1 } ForOption extensions { continueIfEven(Option(2)) } // Some(3) ForListK extensions { continueIfEven(listOf(2, 4, 6).k()) } // ListK(list=[3, 5, 7]) 16
  13. Integrations - Rx2 Let’s take an example and convert it

    to a comprehension. getSongUrlAsync() .flatMap { MediaPlayer.load(it) } .flatMap { val totalTime = musicPlayer.getTotaltime() audioTimeline.click() .map { (timelineClick / totalTime * 100).toInt() } } 17
  14. Integrations - Rx2 Arrow provides MonadError<F, Throwable> for Observable import

    arrow.effects.* import arrow.typeclasses.* ForObservableK extensions { bindingCatch { val songUrl = getSongUrlAsync().bind() val musicPlayer = MediaPlayer.load(songUrl) val totalTime = musicPlayer.getTotaltime() (audioTimeline.click().bind() / totalTime * 100).toInt() } } 18
  15. Integrations - Kotlin Coroutines Arrow provides MonadError<F, Throwable> for Deferred

    import arrow.effects.* import arrow.typeclasses.* ForDeferredK extensions { bindingCatch { val songUrl = getSongUrlAsync().bind() val musicPlayer = MediaPlayer.load(songUrl) val totalTime = musicPlayer.getTotaltime() (audioTimeline.click().bind() / totalTime * 100).toInt() } } 19
  16. Transforming immutable data Λrrow 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) 20
  17. Transforming immutable data Λrrow includes an optics library that make

    working with immutable data a breeze val employee = Employee("John Doe", Company("Λrrow", Address("Functional city", Street(23, "lambda street")))) employee // Employee(name=John Doe, company=Company(name=Λrrow, address=Address(city=Functional city, street=Street(number=23, name=l 21
  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=Λrrow, address=Address(city=Functional city, street=Street(number=23, name=L 22
  19. Optics without boilerplate You may define composable Lenses to work

    with immutable data transformations val employeeCompany: Lens<Employee, Company> = Lens( get = { it.company }, set = { company -> { employee -> employee.copy(company = company) } } ) val companyAddress: Lens<Company, Address> = Lens( get = { it.address }, set = { address -> { company -> company.copy(address = address) } } ) ... 23
  20. Optics without boilerplate You may define composable Lenses to work

    with immutable data transformations import arrow.optics.* val employeeStreetName: Lens<Employee, String> = Employee.company compose Company.address compose Address.street compose Street.name employeeStreetName.modify(employee, String::capitalize) // Employee(name=John Doe, company=Company(name=Λrrow, address=Address(city=Functional city, street=Street(number=23, name=L 24
  21. Optics without boilerplate Or just let Λrrow @optics do the

    dirty work + @optics data class Employee(val name: String, val company: Company) - val employeeCompany: Lens<Employee, Company> = Lens( - get = { it.company }, - set = { company -> { employee -> employee.copy(company = company) } } - ) - - val companyAddress: Lens<Company, Address> = Lens( - get = { it.address }, - set = { address -> { company -> company.copy(address = address) } } - ) - ... 25
  22. Optics without boilerplate Optics comes with a succinct and powerful

    DSL to manipulate deeply nested immutable properties import arrow.optics.dsl.* Employee.company.address.street.name.modify(employee, String::toUpperCase) // Employee(name=John Doe, company=Company(name=Λrrow, address=Address(city=Functional city, street=Street(number=23, name=L 26
  23. Optics without boilerplate You can also define @optics for your

    sealed hierarchies @optics sealed class NetworkResult @optics data class Success(val content: String): NetworkResult() @optics sealed class NetworkError : NetworkResult() @optics data class HttpError(val message: String): NetworkError() object TimeoutError: NetworkError() 27
  24. Optics without boilerplate Where you operate over sealed hierarchies manually...

    val networkResult: NetworkResult = HttpError("boom!") val f: (String) -> String = String::toUpperCase val result = when (networkResult) { is HttpError -> networkResult.copy(f(networkResult.message)) else -> networkResult } result // HttpError(message=BOOM!) 28
  25. Optics without boilerplate ...you cruise now through properties with the

    new optics DSL NetworkResult.networkError.httpError.message.modify(networkResult, f) // HttpError(message=BOOM!) 29
  26. In the works arrow-generic Generic programming with products, coproducts and

    derivation arrow- streams A functional Stream<F, A> impl that abstract over F and complementes arrow- effect arrow- android FP solutions to common Android issues Ex: Activity lifecycle 30
  27. Λrrow is modular Pick and choose what you'd like to

    use. Module Contents typeclasses Semigroup , Monoid , Functor , Applicative , Monad ... core/data Option , Try , Either , Validated ... effects IO effects-rx2 ObservableKW , FlowableKW , MaybeK , SingleK effects-coroutines DeferredK mtl MonadReader , MonadState , MonadFilter ,... free Free , FreeApplicative , Trampoline , ... recursion-schemes Fix , Mu , Nu optics Prism , Iso , Lens , ... meta @higherkind , @deriving , @instance , @optics 31
  28. Kotlin limitations for Typed FP Fear not, @higherkind 's got

    your back! + @higherkind sealed class Option<A> : OptionOf<A> - class ForOption private constructor() { companion object } - typealias OptionOf<A> = Kind<ForOption, A> - inline fun <A, B> OptionOf<A>.fix(): Option<A> = this as Option<A> 34
  29. Λrrow ad-hoc polymorphism With emulated Higher Kinds and Type classes

    we can now write polymorphic code import arrow.Kind import arrow.core.* import arrow.effects.* import arrow.typeclasses.* 35
  30. Λrrow ad-hoc polymorphism With emulated Higher Kinds and Type classes

    we can now write polymorphic code interface Service1<F> : Functor<F> { fun Kind<F, Int>.addOne(): Kind<F, Int> = map { it + 1 } } 36
  31. Λrrow ad-hoc polymorphism With emulated Higher Kinds and Type classes

    we can now write polymorphic code interface Service2<F> : Functor<F> { fun Kind<F, Int>.addTwo(): Kind<F, Int> = map { it + 2 } } 37
  32. Λrrow ad-hoc polymorphism With emulated Higher Kinds and Type classes

    we can now write polymorphic code interface App<F> : Service1<F>, Service2<F> { fun Kind<F, Int>.addThree(): Kind<F, Int> = addOne().addTwo() } 38
  33. Λrrow ad-hoc polymorphism With emulated Higher Kinds and Type classes

    we can now write polymorphic code /* Our app works for all functors */ fun <F, A> Functor<F>.app(f: App<F>.() -> A): A = f(object : App<F> { override fun <A, B> Kind<F, A>.map(f: (A) -> B): Kind<F, B> = [email protected] { map(f) } }) 39
  34. Λrrow ad-hoc polymorphism Program that are abstract and work in

    many runtimes! ForOption extensions { app { Option(1).addThree() } } // Some(4) 40
  35. Λrrow ad-hoc polymorphism Program that are abstract and work in

    many runtimes! ForTry extensions { app { Try { 1 }.addThree() } } // Success(value=4) 41
  36. Λrrow ad-hoc polymorphism Program that are abstract and work in

    many runtimes! ForIO extensions { app { IO { 1 }.addThree().fix().unsafeRunSync() } } // 4 42
  37. Type Classes This is how you define Type Classes in

    Λrrow (for now) interface Functor<F> : Typeclass { fun <A, B> map(fa: HK<F, A>, f: (A) -> B): HK<F, B> } 43
  38. @deriving Λrrow 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<out A> : OptionKind<A> { ... } 45
  39. @instance Λrrow allows you to hand craft instances @instance(Either::class) interface

    EitherFunctorInstance<L> : Functor<EitherKindPartial<L>> { override fun <A, B> map(fa: EitherKind<L, A>, f: (A) -> B): Either<L, B> = fa.ev().map(f) } //Either.functor<L>() is available after @instance is processed 46
  40. KEEP-87 But we are not stopping here, we want to

    get rid of some of the codegen. KEEP-87 is A KEEP to introduce Type Classes in Kotlin! https:/ /github.com/Kotlin/KEEP/pull/87 47
  41. KEEP-87 Type Classes & Instances extension interface Monoid<A> { fun

    A.combine(b: A): A val empty: A } extension object IntMonoid : Monoid<Int> { fun Int.combine(b: Int): Int = this + b val empty: Int = 0 } 48
  42. KEEP-87 Declaration site Desugars to fun combineOneAndTwo(with Monoid<Int>) = 1.combine(2)

    // `this` is an instance of `Monoid<Int>` fun combineOneAndTwo(ev: Monoid<Int>) = with(ev) { 1.combine(2) } // `this` is ev 49
  43. KEEP-87 Call site Desugars to import IntMonoid combineOneAndTwo() // instance

    is resolved via imports and injected by the compiler import IntMonoid combineOneAndTwo(IntMonoid) // compatible with java and allows explicit overrides 50
  44. An ecosystem of libraries Kollect Efficient data access with id

    dedup, parallelization, batching and caching. Kollect 53
  45. Credits Λrrow is inspired in great libraries that have proven

    useful to the FP community: Cats Scalaz Freestyle Monocle Funktionale Paguro 54
  46. 72 Contributors and counting 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 55
  47. Join us! Github Slack Gitter We provide 1:1 mentoring for

    both users & new contributors! https:/ /github.com/arrow-kt/arrow https:/ /kotlinlang.slack.com/messages/C5UPMM0A0 https:/ /gitter.im/kategory/Lobby 56