1. Functional Programming 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 function?
Deterministic. Same result for the same inputs.
Produces no observable side e ects. 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 Referential Transparency
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 e ect?
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 e ect?
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 de ned 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
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
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
// An expression language sealed class Term
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 Powered by Arrow Meta. Arbitrary amount of representations. No hierarchy restrictions (can use system types ) val op: Union3 Removes tests for type invariants. Compile time validated ✅ Powered by Arrow-Meta. @Refinement inline class TwitterHandle(val handle: String) { companion object : Refined 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 speci c property. val member = BandMember("id0293j", "Simon", Instrument.Guitar() val memberInstrumentLens: Lens 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 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 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 // 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 -val path: Traversal // You can avoid passing the instance: ListK.each 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 Either.fromNullable(1) // Right(1) } Either A path we want to follow, vs an "alternative" one Railway oriented programming Railway oriented programming By Scott Wlaschin from 11 May 2013 Post fsharpforfunandpro t.com/posts/recipe- part2/ Talk video + slides fsharpforfunandpro t.com/rop/ Code representation Either models this scenario. sealed class 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 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 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 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 Often used in the context of a background computation. These data type 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. Compiler 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 //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 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 library. 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 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 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 拾 Simpli es 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 !user.fold({ error -> IO.just(Either.Left(error)) }, { user -> IO.effect { user.process() } }) } IO Performance improves no extra wrapper allocations / runtime loop. Side e ects under control suspend. suspend is the Kotlin stdlib mechanism to ensure an e ectful 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 // or suspend fun program(): Either Almost any e ectful program can be written as suspend + Either. suspend safety, side e ect 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 e ects. 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 e ect on an arbitrary CoroutineContext. Comes back to original thread after. suspend fun loadTalks(ids: List 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 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 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 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 File leaks when cancelled either 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 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 bracketCase cancellation val start = Promise Run an e ect and guarantee a lamda always occurs in the end. suspend fun guarantee( fa: suspend () -> A, release: suspend () -> Unit ): A guaranteeCase Run an e ect and guarantee a lamda by case always occurs in the end. Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 guaranteeCase cancellation val p = Promise val fiber = ForkAndForget { guaranteeCase( fa = { start.complete(Unit); never 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 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 Monad 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 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Resource cancellation val p = Promise 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. Bene ts 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 e ects. (suspend). Compatible with advanced functional concurrency concepts. Cold streams pure. Composition. Declarative. Arrow Fx Coroutines 爐 Functional Streams Provides lightweight and highly e cient functional Streams. Evaluate embed e ects (suspend). Seamless integration with Arrow Fx Coroutines e ects. 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 e ects 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 e ects 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 e ect 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.e ect + 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 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 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 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 }, List