<<

1. Functional Programming

What is functional programming? What is it good for? Why to learn it? Functional Programming

Programs as a composition of pure functions. Pure ?

Deterministic. Same result for the same inputs.

Produces no observable side eects. fun mergeAccounts(acc1: Account, acc2: Account): Account = Account(acc1.balance + acc2.balance) val mergedAcc = mergeAccounts(Account(500), Account(1000))

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50

Function can be replaced with its corresponding value without changing the program's behavior.

Based on the substitution model. Referential Transparency

Better reasoning about program behavior. Pure functions  referentially transparent. Substitution model

Replace a named reference (function) by its value.

Based on equational reasoning. Unlocks local reasoning. fun add(a: Int, b: Int): Int = a + b val two = add(1, 1) // could be replaced by 2 everywhere in the program val equal = (two == 1 + 1)

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Substitution model

Same example, but with two Accounts. fun mergeAccounts(acc1: Account, acc2: Account): Account = Account(acc1.balance + acc2.balance) val mergedAcc = mergeAccounts(Account(100), Account(200)) // could be replaced by Account(300) everywhere // in the program val equal = (mergedAcc == Account(300))

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Side eect?

Function tries to access or modify the external world. val accountDB = AccountDB() fun mergeAccounts(acc1: Account, acc2: Account): Account { val merged = Account(acc1.balance + acc2.balance) accountDB.remove(listOf(acc1, acc2)) // side effect! accountDB.save(merged) // side effect! return merged } val merged = mergeAccounts(Account(100), Account(400))

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Side eect?

They are not referentially transparent so we can't apply equational reasoning by substitution. Higher order functions

Functions that have other functions as arguments or return other functions.

Unlocks storing and deferring computations. val acc = Account(1000) fun Account.updateBalance(op: (Long) -> Long): Account { return Account(op(this.balance)) } val millionaireAcc = acc.updateBalance { it + 4000000 }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Higher order functions val acc = Account(1000) fun Account.updateBalance(op: (Long) -> Long): () -> Account = Account(op(this.balance)) } val millionaireAcc = acc.updateBalance { it + 4000000 }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50

Reason about our functions as if they were data. Exercises 2. Domain modeling and Algebraic Data Types

What's an ADT?

Composite type (A type that combines others).

Product types Sum types Product types

Example: Kotlin data classes.

Composed by other types. All of them have to exit. A & B & C & D  A * B * C * D Structurally dened by its types.

Product types - Data classes

// product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long) val acc = Account("acc01928j", User("SomeId", "Jorge", "Castill val debitOp = Debit(acc, 500)

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Product types - Data classes equals and hashcode derived at compile time based on their structure.

// product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long)

val acc1 = Account("acc01928j", User("SomeId", "Jorge", "Castil val acc2 = Account("acc01928j", User("SomeId", "Jorge", "Castil val result = acc1 == acc2

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Product types - Data classes

Synthetic copy method also derived at compile time based on their structure.

// product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long) val account = Account("acc01928j", User("SomeId", "Jorge", "Cas val result = account.copy(balance = account.balance + 1000)

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Arrow Product Types

@product annotation over a data class. It must include a companion object. Derives additional syntax at compile time. import arrow.core.* import arrow.generic.*

@product data class Account(val balance: Int, val available: Int) { comp Arrow derives the following behaviors with @product: Monoid import arrow.core.* import arrow.generic.* val result = Account(1000, 900) + Account(1000, 900) // Account(2000, 1800) Arrow automatically derives with @product the following behaviors: Monoid import arrow.core.* import arrow.generic.* val result = emptyAccount() // Account(0, 0)  relies on Int.monoid() Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).toHList() // HCons(head=1000, tail=HCons(head=900, tail=HNil)) Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).toHListLabeled() // HCons(head=(balance, 1000), tail=HCons(head=(available, 900) Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).tupled() // (1000, 900) Sum types

Example: Sealed class, Enum class, Coproduct types, Union Types.

One of many. Exclusive OR. A or B or C or D  A + B + C + D

Sum types - Sealed classes

Algebra with multiple representations. enum class, sealed class, Boolean, Either...

// Sum type, 2 representations sealed class Maybe { data class Present(val a: A) : Maybe() object Absent : Maybe() } val result = Maybe.Present(Account(1000, 900))

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes

// Sum type, 2 representations sealed class Xor { data class Left(val a: A) : Xor() data class Right(val b: B) : Xor() } val result: Xor = Xor.Right(Account(1000, 9

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes sealed class RecList { data class Cons(val a: A, val tail: RecList) : Re object Nil: RecList() } val result: RecList = RecList.Cons( Account(1000, 900), RecList.Cons( Account(1000, 900), RecList.Nil ) ) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes

// An expression language sealed class Term { data class Lit(val n: Int) : Term() data class Succ(val i: Term) : Term() data class IsZero(val i: Term) : Term() data class Pair(val a: Term, val b: Term eval(term: Term): A = when (term) { is Term.Lit -> term.n is Term.Succ -> 1 + eval(term.i) is Term.IsZero -> eval(term.i) == 0 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Coproduct

Also a sealed hierarchy of types. Arbitrary amount of representations up to 22 args. import arrow.* import arrow.core.* sealed class Coproduct3 data class First(val a: A) : Coproduct3() data class Second(val b: B) : Coproduct3() data class Third(val c: C) : Coproduct3() Sum types - Coproduct val accountOp: Coproduct3

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Union types (Meta)

Arbitrary amount of representations. No hierarchy restrictions val accountOp: Union3 ScreenState.Content(it) is PermissionError -> ScreenState.Error(it.toReadableT is AccessError -> ScreenState.Error(it.toReadableText( } Sum types - Union types (Meta)

Powered by Arrow Meta. Arbitrary amount of representations. No hierarchy restrictions (can use system types ) val op: Union3 = 1 //no wrappers val case1: String? = op //null val case2: Int? = op //1 val case3: Double? = op //null Rened types

Removes tests for type invariants. Compile time validated ✅ Powered by Arrow-Meta.

@Refinement inline class TwitterHandle(val handle: String) { companion object : Refined { override val target = ::TwitterHandle override val validate: String.() -> Map 2" to (length > 2), "Should not contain the word 'twitter'" to !con "Should not contain the word 'admin'" to !conta ) } Exercises 3. Immutable Data and Optics Why immutability?

Leverages determinism.

Avoid state mutation from everywhere. Restrict it to a single place. Make it explicit. Pure functions assume to work over immutable data. Write precise domain models. Ability to reason over program state. Immutability in Kotlin

Data classes with val properties. Cloning using synthetic copy(). val simonOnDrums = BandMember( "id0293j", "Simon", Instrument.Drums() ) val simonOnGuitar = simonOnDrums.copy( instrument = Instrument.Guitar( "newModel", GuitarString.newStrings() ) )

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Problem with nesting

Doesn't scale well for nested structures. val band = Band("The Free Monads", "Rock", bandMembers) val updatedBand = band.copy( members = band.members.copy( drumsMember = band.members.drumsMember.copy( instrument = (band.members.drumsMember.instrument a model = "FancyNewTrendyModel" ) ) ) )

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Optics  Optics

Arrow provides a DSL for optics. Direct and reusable syntax to read / modify immutable data structures. Generated at compile time using the @optics annotation.

@optics data class Instrument(val model: String) @optics data class BandMember(val id: String, val name: String, @optics data class BandMembers(val guitarMember: BandMember, va @optics data class Band(val name: String, val style: String, va

Optics - Lens Used over the companion object. Can read or modify a deep property. val bandMembers = BandMembers( guitarMember = BandMember("id0293rr", "Raúl", instrument = micMember = BandMember("id0294bm", "Benjy", instrument = In drumsMember = BandMember("id0295sv", "Simon", instrument = ) val band = Band("The Free Monads", "Rock", bandMembers) val guitarStringsLens = Band.members.guitarMember.instrument.gu val result = guitarStringsLens.modify(band) { GuitarString.newS

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Explicit comparison

-val updated = simon.copy( - company = simon.company.copy( - address = simon.company.address.copy( - street = simon.company.address.street.copy( - name = - simon.company.address.street.name.toUpperCase() - ) - ) - ) -)

+val lens = Employee.company.address.street.name +val updated = lens.modify(simon, String::toUpperCase) We are stacking lenses to build a composed reusable one.  Lens - Lift a function Lift simple functions into the context of your domain val band = Band("The Free Monads", "Rock", bandMembers) val toUpperCaseDrumModel : (Band) -> Band = Band.members.drumsMember.instrument.model.lift( String::toUpperCase ) toUpperCaseDrumModel(band)

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Optics - Lens It's a pair of get and set for a specic property. val member = BandMember("id0293j", "Simon", Instrument.Guitar() val memberInstrumentLens: Lens = Lens( get = { member -> member.instrument }, set = { member, value -> member.copy(instrument = value) } ) val res = memberInstrumentLens.modify(member) { Instrument.Drums() }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Compile time generation

You don't write the boilerplate. Companion object required!

+@optics data class BandMember(...) { companion object { - val memberInstrumentLens: Lens member.instrument }, - set = { member, value -> member.copy(instrument = - ) } } Optics - Prism What about immutable ADTs? No copy available for sealed class. data class BandMember(val id: String, val name: String, val ins val member = BandMember("id0293j", "Simon", Instrument.Guitar(" val fixedStrings = when (member.instrument) { is Instrument.Microphone -> member is Instrument.Drums -> member is Instrument.Guitar -> member.copy( instrument = member.instrument.copy( strings = GuitarString.newStrings() ) ) } Optics - Prism Prism works over the focus when it matches the correct case. val member = BandMember( "id0293j", "Simon", Instrument.Guitar("Model 1", GuitarString.oldStrings()) ) val res = BandMember.instrument.guitar.strings.modify(member) { GuitarString.newStrings() }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Optics - Prism Composed of: getOrModify to match the focus. reverseGet to create the source type from the focus.

Prism( getOrModify = { instrument: Instrument -> when (instrument) { is Instrument.Guitar -> instrument.right() else -> instrument.left() } }, reverseGet = ::identity )

Prism#modify

Works over getOrModify. fun Prism.modify(s: S, f: (A) -> A): S = when(val result = getOrModify(s)) { is Either.Right -> reverseGet(f(result.b)) is Either.Left -> result.a }

What if?

We usually don't know the type we work with. You'd need to match over the type  val instrument: Instrument = Guitar(strings = oldStrings()) val updated = when(instrument) { is Guitar -> instrument.copy(strings = newStrings()) else -> instrument } What if?

We match the type, then use smart cast to be able to copy. That means we'd need both a Prism and a Lens together 樂 val instrument: Instrument = Guitar(strings = oldStrings()) val updated = when(instrument) { is Guitar -> instrument.copy(strings = newStrings()) else -> instrument } Optional = Lens + Prism

-val updated = when(instrument) { - is Guitar -> instrument.copy(strings = newStrings()) - else -> instrument -} +val optional: Optional> + Instrument.guitar compose Guitar.strings Optics - Traversal Let's say we had a more complex domain. sealed class BandItems { data class Instruments(val items: List) : BandIte object Lights : BandItems() object Wiring : BandItems() object SoundTable : BandItems() }

// A nested list of items with dynamic type. val items: BandItems = Instruments(listOf( Guitar(strings = oldStrings()), Microphone() )) Optics - Traversal We fall into nested when statements 

// A nested list of items with dynamic type. val items: BandItems = Instruments(listOf( Guitar(strings = oldStrings()), Microphone() )) when(items) { is Instruments -> items.instruments.copy(items.instruments.ma when(value) { is Guitar -> value.copy(strings = newStrings()) else -> value } }) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Optics - Traversal Compose optics instead.

-when(items) { - is Instruments -> items.instruments.copy(items.instru - when(value) { - is Guitar -> value.copy(strings = newStrings()) - else -> value - } - }) - else -> items -} +val path: Traversal> = + BandItems.instruments compose + Instruments.items compose + ListExtensions.traversal() compose + Instrument.guitar compose + Guitar.strings Optics - Traversal Leverage the DSL ✅

-val path: Traversal> = - BandItems.instruments compose - Instruments.items compose - ListExtensions.traversal() compose - Instrument.guitar compose - Guitar.strings +val path = + BandItems.instruments + .items + .every(ListExtensions.traversal()) + .guitar + .strings Optics - Traversal Improve it using Optional instead of Prism + Lens. val path = - BandItems.intruments - .items BandItems.items .every(ListExtensions.traversal()) - .guitar - .strings .strings Optics - Each Operates over every one of the focused values. val member = BandMember( "id0293j", "Simon", Instrument.Guitar("Model 1", GuitarString.oldStrings())) val res = BandMember.instrument.guitar.strings .every(ListK.each()).modify(member) { GuitarString() }

// You can avoid passing the instance: ListK.each().run { BandMember.instrument.guitar.strings.every.modify(member) { GuitarString() } Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Optics - Iso Isomorphism (same form) Conversion from A to B and reverse. val bandMemberTupleIso: Iso Tuple3(member.id, member.name, member reverseGet = { tuple -> BandMember(tuple.a, tuple.b, tu ) val member = BandMember("id0293j", "Simon", Instrument.Drums()) val tuple = bandMemberTupleIso.get(member)

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50

Optics - Composition

DSL designed to be highly composable. DSL designed to be highly reusable. Create complex optics as a composition of simpler ones. Example: Stacking lenses  

Exercises 4. Error Handling and Data validation Usual error handling scenarios

Absence of a value. Exceptions thrown by third parties. Modeling errors in your domain. Validating data. Fail fast vs error accumulation error strategies. Absence of a value

Option is getting deprecated.  Alternative 1: A?  Alternative 2: Either fun main(): Unit { Either.fromNullable(null) // Left(Unit)

Either.fromNullable(1) // Right(1) } Either All programs have a happy path vs an error path.

A path we want to follow, vs an "alternative" one

Railway oriented programming  Railway oriented programming

By Scott Wlaschin from 11 May 2013  Post  fsharpforfunandprot.com/posts/recipe- part2/  Talk video + slides  fsharpforfunandprot.com/rop/

Code representation

Either models this scenario. sealed class Either { data class Left(val a: A) : Either( data class Right(val b: B) : Either } Note how Nothing is used. Biased towards the Right case Convention: Errors on Left side. Our program - Success fun loadSpeaker(id: SpeakerId): Either

println(speaker) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Our program - Error fun loadSpeaker(id: SpeakerId): Either

println(speaker) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Our program - chain ops fun loadTalks(ids: List): Either

println(talks) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Chain ops - binding suspend fun main() { val talks = either> { val speaker = !loadSpeaker("SomeId") val talks = !loadTalks(speaker.talks) talks }

println(talks) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Fail fast

First operation fails  short circuit suspend fun main() { val events = either> { val speaker = !loadSpeaker("SomeId") val talks = !loadTalks(speaker.talkIds) val events = talks.map { !loadEvent(it.event) } events }

println(events) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Error accumulation?

Interested in all errors occurring, not a single one. Only in the context of independent computations.

Validated & ValidatedNel sealed class Validated { data class Valid(val a: A) : Validated(val e: E) : Validated = Validated Note how Nothing is used Biased towards the Valid case Validated & ValidatedNel fun validateTalk(talk: Talk): ValidatedNel = if (talk.script.length < 25) { Error.ScriptTooShort.invalidNel() } else if (talk.script.contains("ugly words")) { Error.ViolatesCodeOfConduct.invalidNel() } else if (talk.duration > 45) { Error.TalkTooLong.invalidNel() } else { talk.validNel() } val res1 = validateTalk(Talk(45, "Some too short script", "Even val res2 = validateTalk(Talk(45, "This is a long enough script Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Limitations

Often used in the context of a background computation. These cannot express that, so depends on the surrounding context.

Thread pools? Coroutines? More on lesson 5: Suspend Exercises 5. Diving into Kotlin Suspend What is suspend? Flags code that needs to run under a safe environment (Coroutine). It's a ag for asynchronous operations. generates syntax that allows the user to treat those computations as if they were synchronous. How does suspend work? Continuation Passing Style (CPS) object Tweet suspend fun requestUserToken(): String = TODO() suspend fun createPost(token: String, tweet: Tweet): String = T suspend fun sendPost(post: String): Unit = TODO() suspend fun sendTweet(tweet: Tweet): Unit { val token = requestUserToken() val post = createPost(token, tweet) sendPost(post) } Kotlin compiler

Replaces all suspend by an implicit Continuation parameter. suspend fun createPost(token: UserToken, tweet: Tweet): Post = 

- suspend fun createPost(token: UserToken, tweet: Tweet): Post + fun createPost(token: UserToken, tweet: Tweet, callback: Cont What is a continuation?

Continuation is a generic callback. Allows for direct syntax async jumps. interface Continuation { val context: CoroutineContext fun resumeWith(result: Result)

//fun resumeWithException(e: Throwable) //fun resumeWith(a: A) }

Flashback to lesson 4. Limitations

Either and Validated often used in the context of a background computation. These data type cannot express that, so depends on the surrounding context.

Thread pools? Coroutines? Either within executor thread val speakerService = SpeakerService() val executor = Executors.newFixedThreadPool(4) val callback: (Either) -> Unit = { either -> either.fold( ifLeft = { error -> println(error) }, ifRight = { speaker -> println(speaker) } ) } executor.execute { val speaker = speakerService.loadSpeaker("SomeId") callback(speaker) Either within executor thread

Far from ideal wiring results with a callback. Mixing sync and async programming. Either within stdlib coroutine suspend fun main() { // kotlin suspend main val speakerService = SpeakerService() val speaker = speakerService.loadSpeaker("SomeId") speaker.fold( ifLeft = { println(it) }, ifRight = { println(it) } ) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Better, but cancellation?  KotlinX Coroutines required, or an alternative runtime . Either within IO fun main() { val speakerService = SpeakerService() val speaker = IO { speakerService.loadSpeaker("SomeId") } val disposable = speaker.unsafeRunAsyncCancellable { it.fold( ifLeft = { error -> println(error) }, ifRight = { speaker -> println(speaker) } ) } }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 disposable() for cancellation. IO>  Defers computation  declarative stack you can compose on, pass around.  Supports cancellation. ✅ Safety environment (side eects).  Re-runnable (retry, repeat).  We keep the railways.  Nesting  Wrapped style  Getting deprecated! Why not IO? sealed class Error : Throwable() { object SpeakerNotFound : Error() object InvalidSpeakerId : Error() object InvalidEventId : Error() }

IO { speakerService.loadSpeaker("SomeId") } .handleError { when (it as Error) { //  unsafe casting SpeakerNotFound -> TODO() InvalidSpeakerId -> TODO() Error.InvalidEventId -> TODO() } Only supports Throwable errors. No strongly typed errors.

An equivalence relation

IO> ↕ suspend () -> Either

The latter for a more idiomatic style. The latter for eliminating wrappers. But cancellation support ⚠ Arrow Fx Coroutines 爐 Arrow Fx Coroutines

Fully-edged concurrency framework. suspend DSL - same API than IO. Functional impl of Kotlin suspend. Cancellation system ✅ Removes the need for noisy wrappers 拾 Simplies Arrow extremely ✨ IO suspend () -> A A program written for IO. suspend fun getThreadName(): String = Thread.currentThread().name suspend fun main(): Unit = IO.fx { val t1 = !effect { getThreadName() }.evalOn(Computation) val t2 = !effect { getThreadName() }.evalOn(BlockingIO) val t3 = !effect { getThreadName() }.evalOn(UI) !effect { println("$t1 ~> $t2 ~> $t3") } } IO suspend () -> A Using with suspend () -> A instead. suspend fun getThreadName(): String = Thread.currentThread().name suspend fun main(): Unit { val t1 = evalOn(Computation) { getThreadName() } val t2 = evalOn(BlockingIO) { getThreadName() } val t3 = evalOn(UI) { getThreadName() } println("$t1 ~> $t2 ~> $t3") } IO>  suspend () -> Either A program written for IO. suspend fun fetchUser(): Either = TODO( suspend fun User.process(): Either> = IO.fx { val user = !IO.effect { fetchUser() }

!user.fold({ error -> IO.just(Either.Left(error)) }, { user -> IO.effect { user.process() } }) } IO>  suspend () -> Either Using suspend instead. suspend fun fetchUser(): Either = TODO( suspend fun User.process(): Either either { val user = !fetchUser() val processed = !user.process() processed } Guarantees stay

Performance improves  no extra wrapper allocations / runtime loop. Side eects under control  suspend.

suspend is the Kotlin stdlib mechanism to ensure an eectful computation runs under a safe environment. Guarantees stay

Error handling and recovery by Either. Either#catch {}, Either#fold {}, Either#conditionally {}...

IO error handling is gone. suspend fun program() { val maybeSpeakers = Either.catch { service.loadSpeakers() } val maybeAge = Either.conditionally({speaker.age is PositiveI maybeAge.fold( ifLeft = { /*...*/ }, ifRight = { /*...*/ } ) } Guarantees stay

Use Either#catch {} to strongly type domain errors. suspend fun main() { val service = SpeakerService() val res = Either.catch { service.loadSpeaker("SomeId") } .mapLeft { it.toDomain() }

println(res) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Guarantees stay

IO#map and IO#flatMap not needed.

You are computing over the inner value already. Sequencing and mapping is flatMap and map over Either while you stay within the safe context. Guarantees stay

IO#map and IO#flatMap not needed. suspend fun program(): Either either { val user = !fetchUser() val processed = !user.process() processed }

// or suspend fun program(): Either fetchUser().flatMap { it.process() } Exercises 6. Concurrency with Arrow Fx Coroutines Eectful programs

Almost any eectful program can be written as suspend + Either. suspend  safety, side eect tracking, non blocking. Either  error control and modeling. Arrow Fx Coroutines

Advanced concurrency tools working over suspend. Functional implementation of suspend system. Provides collaborative cancellation  Environment

Safe context to run a suspend program. Entry point to perform unsafe eects. suspend fun greet(): Unit { sayHello() sayGoodbye() } fun main() { // Edge of our world val env = Environment() env.unsafeRunSync { greet() } } Environment

Runs sync and async and cancellable suspended programs. fun main() { // Edge of our world val env = Environment()

env.unsafeRunSync { greet() } // blocking env.unsafeRunAsync { greet() } // non-blocking, rethrows env.unsafeRunAsync( // same but explicit lambdas fa = { greet() }, e = { e -> println(e)}, a = { a -> println(a) } ) env.unsafeRunAsyncCancellable { greet() } // non-blocking

val disposable = env.unsafeRunAsyncCancellable( // same but e Environment

Use it only when no suspend entry point available in the environment.

suspend fun main() suspend controllers in web apps. evalOn(ctx)

Evaluate eect on an arbitrary CoroutineContext. Comes back to original thread after. suspend fun loadTalks(ids: List): Either

println(talks) } parMapN

Run N parallel ops. Cancelling parent  cancels all children. Child failure  cancels other children. suspend fun main() { val op1 = suspend { loadSpeakers() } val op2 = suspend { loadRooms() } val op3 = suspend { loadVenues() }

val program = parMapN(op1, op2, op3) { (speakers, ro Event(speakers, rooms, venues) }

program.cancel() // cancels all child ops. } parTupledN

Same without callback style. suspend computation  returns a tuple of all the results. suspend fun main() { val op1 = suspend { loadSpeakers() } val op2 = suspend { loadRooms() } val op3 = suspend { loadVenues() }

val res: Triple, List, List

Event(res.first, res.second, res.third) } parTraverse

Traverse a series of elements of the same type parallelly. Applies a mapping function for each. Cancelling parent  cancels all children. Child failure  cancels other children. suspend fun loadEvents() { val eventIds = listOf(1, 2, 3)

return eventIds.parTraverse(IOPool) { id -> eventService.loadEvent(id) } } parSequence

Same thing without the mapping function. Cancelling parent  cancels all children. Child failure  cancels other children. suspend fun main() { val ops = listOf( suspend { service.loadTalks(eventId1) }, suspend { service.loadTalks(eventId2) }, suspend { service.loadTalks(eventId3) })

ops.parSequence() } raceN

Racing computations parallelly. Returns the winner, cancels losers. Cancelling parent  cancels all children. Child failure  cancels other children. suspend fun main() { val op1 = suspend { loadSpeakers() } val op2 = suspend { loadRooms() } val op3 = suspend { loadVenues() }

val res = raceN(op1, op2, op3) res.fold( ifA = {}, ifB = {}, ifC = {} ) } Retrying / repeating fun complexPolicy() = Schedule.exponential(10.milliseconds) .whileOutput { it.seconds < 60.seconds } .andThen(spaced(60.seconds) and recurs(100)) suspend fun loadTalk(id: TalkId): List = retry(complexPolicy()) { fetchTalk(id) } Error handling? suspend is equivalent to IO. You stay safe until you unsafely run it. At that point, you can attach your error handling strategy. Same for all operators in Arrow Fx Coroutines. Error handling? suspend fun getTalks() { val ops = listOf( suspend { service.loadTalks(eventId1) }, suspend { service.loadTalks(eventId2) }, suspend { service.loadTalks(eventId3) })

ops.parSequence() } suspend fun main() { val res = Either.catch { getTalks() } res.fold( ifLeft = { /*...*/ }, ifRight = { /*...*/ } ) } Cancellation

All operators in Arrow Fx Coroutines automatically check for cancellation. Cancellation

À la carte cancellation by cancelBoundary() suspend fun loop(): Unit { while(true) { cancelBoundary() // cancellable computation loop println("I am getting dizzy...") } } Concurrent cancellation

ForkConnected  launch coroutine with cancellation linked to parent. Cancelling parent cancels child. suspend fun main() { val parent = ForkAndForget { ForkConnected { service.loadSpeaker(id) } }

parent.cancel() // cancels child  } Concurrent cancellation forkScoped  launch coroutine with arbitrary cancellation lambda. suspend fun main() { val interrupt = Promise()

val fiber = service.loadSpeaker(id).forkScoped { interrupt::get }

interrupt.complete(Unit) // will cancel fiber } Exercises 7. Safe Resource Handling Bracket

1. Acquire a resource. 2. Use a resource. 3. Always: Release a resource ✅ Work across async boundaries 

Work safely with resources even across asynchronous boundaries, and be agnostic of that. Is this resource safe? class File(url: String) { fun open(): File = this.also { println("File opened") } fun close(): Unit = println("File closed") override fun toString(): String = "This file contains some } either { val file = File("data.json").open() Either.catch { println(file.toString()) } .handleError { file.close() } }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 File leaks when cancelled  either { val file = File("data.json").open() val p = Promise() val fiber = ForkConnected { sleep(2.seconds) Either.catch { println(file.toString()) p.complete(Unit) }.handleError { file.close() } } p.get() Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Always release

Resources terminate for 3 reasons: Completion ✅ Failure  Cancellation 

We want to release the resource for all of them. bracket

Acquire, use and release. suspend fun bracket( acquire: suspend () -> A, use: suspend (A) -> B, release: suspend (A) -> Unit ): B bracketCase

Acquire, use and release by case. sealed ExitCase { object Completed: ExitCase() object Cancelled: ExitCase() data class Error(val error: Throwable): ExitCase() } suspend fun bracketCase( acquire: suspend () -> A, use: suspend (A) -> B, release: (a, ExitCase) -> B ): B bracketCase error val promise = Promise()

Either.catch { bracketCase( acquire = { File("data.json").open() }, use = { throw RuntimeException("Boom!") }, release = { file, ex -> promise.complete(ex) file.close() } ) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 bracketCase cancellation val start = Promise() val exit = Promise() val f = ForkAndForget { bracketCase( acquire = { File("data.json").open() }, use = { start.complete(Unit) // fiber is running! never() }, release = { file, ex -> exit.complete(ex) file.close() Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 guarantee

Run an eect and guarantee a lamda always occurs in the end. suspend fun guarantee( fa: suspend () -> A, release: suspend () -> Unit ): A guaranteeCase

Run an eect and guarantee a lamda by case always occurs in the end.

suspend fun guaranteeCase( fa: suspend () -> A, release: suspend (ExitCase) -> Unit ): A guaranteeCase error val p = Promise() val attempted = Either.catch { guaranteeCase( fa = { throw RuntimeException("Boom!") }, release = { ex -> p.complete(ex) } ) }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 guaranteeCase cancellation

val p = Promise() val start = Promise()

val fiber = ForkAndForget { guaranteeCase( fa = { start.complete(Unit); never() }, release = { ex -> p.complete(ex) } ) }

start.get() fiber.cancel()

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Fiber & bracket / guarantee

Can use both in combination. fun main(args: Array) { val env = Environment() env.unsafeRunSync { bracket( acquire = { ForkAndForget { infiniteLoop() } }, use = { println("Do something else") }, release = { fiber -> fiber.cancel() } ) } }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Bracket limitations bracket and bracketCase are limited. You are tied decide how to use it right when you acquire it. That also makes it hardly reusable  Resource

Resource is a datatype  reusable. Resource is a  map, atMap, traverse...  compose operations. Resource

It allows you to declare lambdas to acquire and release, then pass it around and reuse. listOf( Resource({ openFile("path1") }, File::close), Resource({ openFile("path2") }, File::close), Resource({ openFile("path3") }, File::close) ) val fileContents: List = resources.parTraverse(I files.use { file -> file.toString() } } val fileSizes: List = resources.parTraverse(IOPool files.use { file -> file.size() } } Resource error val p = Promise() val r = Resource( { throw RuntimeException("Boom!") }, { _, ex -> p.complete(ex) } ) val res = Either.catch { r.use { it + 1 } }

Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Resource cancellation val p = Promise() val start = Promise() val r = Resource({ Int }, { _, ex -> p.complete(ex) }) val f = ForkAndForget { r.use { start.complete(Unit) never() } } start.get() f.cancel() Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Exercises 8. Functional Streams Before streams

We needed multi shot emissions, we used: Event buses  Massive callbacks  After

Reactive Streams  (RxJava 1, 2 & 3, Reactor) Programs as a ow of events. Benets of RX

Declarative style. Cold streams (defer execution until subscribe). Compute over the happy path. Hook error handling strategies on a single point. Describe multi-event based programs naturally. What we need

Emit multiple times. Embed eects. (suspend). Compatible with advanced functional concurrency concepts. Cold streams  pure. Composition. Declarative. Arrow Fx Coroutines 爐 Functional Streams

Provides lightweight and highly ecient functional Streams. Evaluate embed eects (suspend). Seamless integration with Arrow Fx Coroutines eects. Pull based Stream vs push based alternatives (RxJava, Reactor) Receiver suspends until data can be pulled. Built in back-pressure. Pure Stream

No evaluation of eects to produce a result. val s = Stream(9, 10, 11) val result = s.take(2) .compile() // terminal op to consume the stream .toList()

// [9, 10] Stream with eects val s = Stream.effect { println("Run!") }

// drain to drain the stream output (for effects) s.compile().drain()

// Run! Cold. Describes what will happen when the stream is interpreted. Terminal operator to run it. If eect fails, error is raised into the Stream. parJoin

Run multiple streams in parallel, join results. val s1 = Stream.effect { 1 } val s2 = Stream.effect { 2 } val s3 = Stream.effect { 3 } val program = Stream(s1, s2, s3).parJoinUnbounded() // or parJoin(maxOpen = 3) val res = program.compile().toList() println(res)

// [2, 1, 3] Stream.eect + evalOn

Evaluate on arbitrary context. val s = Stream.effect { evalOn(IOPool) { println(Thread.currentThread().name) } } s.compile().drain()

// io-arrow-kt-worker-0 async wrapper

Wrap callback based apis. fun SwipeRefreshLayout.refreshes(): Stream = Stream.async { val listener = OnRefreshListener { emit(Unit) } [email protected](listener) } Other relevant operators

All the usual ones.

Stream.effect { loadSpeakers() } .handleErrorWith { Stream.empty() } .flatMap { Stream.effect { loadTalks(it.map { it.id // .effectMap { loadTalks(it.map { it.id }) } .map { talks -> talks.map { it.id } } .compile() .drain() interruptWhen

Arbitrary Stream interruption. program() .interruptWhen(otherStream()) // races both .compile() .drain() interruptWhen + lifecycle

Usual Android use case. Observe a lifecycle and emit a subset of the events. fun Fragment.lifecycleDestroy(): Stream = Stream.async { viewLifecycleOwner.lifecycle.addObserver( LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_DESTROY) { emit(true) } }) } interruptWhen

Arbitrary Stream interruption.

program() .interruptWhen(lifecycleDestroy()) // races both .compile() .drain() Will terminate your program as soon as a lifecycle ON_DESTROY event is emited. Entry point - Manual

Terminal ops are suspend  your Stream has to run on a coroutine. Use platform suspend support if available. Otherwise, manual entry point  suspend { program() // returns a Stream .compile() .drain() }.startCoroutine(Continuation(EmptyCoroutineContext) { } Entry point - Environment

If your platform does not support suspend entry points, use Environment. (Recommended). suspend { program() // returns a Stream .compile() .drain() }.startCoroutine(Continuation(EmptyCoroutineContext) { } Exercises OBS 07 Goodbye