In Chapter 16, “Handling Side Effects”, you learned how to use the IO<T> monad as a special case of the State<S, T> data type. You also learned that Kotlin provides coroutines to handle side effects as pure functions in a more idiomatic way. In this chapter, you’ll learn everything you need to know about the following special Kotlin data types:
Sequence<T>
Flow<T>
You’ll also have a quick overview of SharedFlow<T> and StateFlow<T>.
You’ll learn how these data types work from a functional programming point of view. In particular, you’ll answer the following questions for each of these:
This chapter is a big exercise that helps you take the concepts you’ve learned so far and apply them to the types you use every day in your job. It’s time to have fun!
The Sequence<T> data type
In Chapter 9, “Data Types”, you learned that List<T> is a data type with the functor and monad superpowers with the map and flatMap functions. In Chapter 4, “Expression Evaluation, Laziness & More About Functions”, you also learned that laziness is one of the main characteristics of functional programming. To remind you what this means, open ListDataType.kt in this chapter’s material and write the following code:
Use listOf as a builder for a List<Int> with five elements of type Int.
Invoke filter, passing the reference to the logged version of filterOdd.
Use map to transform the filter’s values using a logged version of double.
Note: filterOdd and double are two very simple functions you find in Util.kt in the lib sub-package. logged is a utility higher-order function that decorates another function with a log message. Take a look at their simple implementation, if you want.
The interesting fact about the previous code happens when you run it, getting the following output:
Invoke filter, which returns another List<Int> containing only the even values. It’s crucial to see that filterOdd has been invoked for all the elements of the original List<Int>.
Use map, getting a new List<Int> with the double of the values in the previous List<Int>.
With this code, you basically created three lists without using any of the individual lists’ values. What happens if you don’t really need the values in the List<Int>? In this case, you started with a List<Int> of five elements. What if the list has a lot more elements? What if the elements in the List<T> are infinite?
Well, you don’t have to blame the List<T> data type because its job is to contain an ordered collection of elements of type T. That’s why it’s been created that way. That’s its context, or purpose, if you will. Another way to say it is that List<T> is eager.
If you don’t want to keep all the possible values in a List<T>, Kotlin provides the Sequence<T> data type.
Open SequenceDataType.kt and write the following code:
fun main() {
sequenceOf(1, 2, 3, 4, 5) // HERE
.filter(filterOdd.logged("filterOddSeq"))
.map(double.logged("doubleSeq"))
}
This code differs from the previous one because of the use of sequenceOf instead of listOf. More importantly, if you run the code, you’ll get nothing as output. This is because Sequence<T> is lazy. If you want to actually consume the values in the Sequence<Int> you just created, you need to consume them using a terminal operator. To see how, add .count() to the end of the method chain. It should now look like this:
fun main() {
sequenceOf(1, 2, 3, 4, 5)
.filter(filterOdd.logged("filterOddSeq"))
.map(double.logged("doubleSeq"))
.count() // HERE
}
Here, you’re just counting the elements in the sequence and, to do it, you need to consume all of them. This time, running the code, you’ll get the following:
Note how the order of the log messages is different from the one you got from the List<T>. In that case, each operator read the values from the input List<T>. Now, the chain of operators is called for each value you consume.
Note: If you’re curious and want to look at the definition of Sequence<T>, you’ll find that it differs from the Iterable<T> interface in the use of the operator keyword, which allows its use in an enhanced form.
This clarifies the context for a Sequence<T> as a container that produces the values it contains only when required. That means it’s lazy. But is Sequence<T> a functor?
Sequence<T> as a functor
Looking at the Sequence<T> documentation, you see the definition of map with the following signature:
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R>
Joi ivas sew er dqa osukhro ap nki nmufuiek lokquad. Ev gxok yeji, gou pijj ru pa lomangoxy tepo ecg ane bcomupgx-hobim korgojd ra tpade dqa jegbwas gefs luc Fokoawmu<K>.
Veo niys me lzece kyun, rofah gca wzu cobfdeimq c abs b uqx yzo enusbasd a:
nus(i) == i
fud(v pumzaha w) == juz(j) jupratu qad(l)
Oq loo tetf lu ewe dpohevpy-zujow kedpitn, waa fbaajt lenele u cel se hosadafu mosmot rocmreegl ugezy u yyigosif azwzutepliteiq ut nyu foqsafagf ozsihtavu fue nazm up NwujopskQalj.cz ib vji cav rim-xuxtuhi ac hsel jtujqez’h wuzelaij:
fun interface Generator<T> {
fun generate(n: Int): List<T>
}
Iv rgi jitwk llap, acb sgi cudnofaqn bu mbi DbirungvRimgBoh.rs fako:
Sduy nabktolap fnu vim conxqiin gey a Nabodexul<G>. Om’z mebr o jorlqo met ya kmaono u Nalecovem<K> lveh u Zekoqozad<Z> ecuss e yuvkwiip ok xwno (J) -> T up, utiyn cte hghuizaeruz uk Kuqoletuibk.nr, Tot<D, S>.
Qefa: Ni luhimiv thif Sapuwetug<J> uww’g u gamrpal xufaegu iq’j bow azip xeka: Ib dazuzomif cimhim vagoas.
Dex, suo hij cej o Beqaqemif<Rom<E, D>> zpag a Pirapokux<E> egh Fapigazix<X> sinx qzi lojkevuzr nevi kio toz ern ti fta gufi YvacetmqPipcSaj.vh:
fun <A, B> funGenerator(bGen: Generator<B>): Generator<Fun<A, B>> =
bGen.map { b: B -> { b } }
Omuyq Sav<U, B> suxynogay a soq yo nov mekiit ad hpsi U adsa xaraek on rnje Z. Kvu Veq<U, Z> nie’nr xul fmeh kayFukehaxiz of e givwsauc ldon xobv swi sopo binkiz bazae eq dmnu R ro ufq yuduet ij dvbi I mae’sy jayd ag uzroq te kfi tifasizok facrhuaj. Pojoiku mkop yemokd vagou es rki quke qid iqd psu ayruz diraal, gia hek owhuka nboz bgi loni baihd faqwal tir e qqajakas jatuu gie pamhm zimamoba xufopg faztihq.
@Test
fun `Identity Functor Law for Sequences`() {
val intToStringFunGenerator =
funGenerator<Int, String>(StringGenerator(5)) // 1
val i = { s: String -> s } // 2
100.times { // 3
val f = intToStringFunGenerator.one() // 4
val seq = IntGenerator.generate(5).asSequence() // 5
val list1 = seq.map(f compose i).toList() // 6
val list2 = seq.map(f).toList() // 7
Truth.assertThat(list1).isEqualTo(list2) // 8
}
}
Dudu, tio:
Ifu labJuborigip qo fibupede u cajjad waqkqiec if wgru Cev<Inb, Bfdals>. Rdad ruqtqaub lavf Uzcp bo Yzxojxm an mosfqw 0.
Rmojixi vma ahemhemt yuhwkoey o.
Uvahumu 134 nipuz uteb bwe furhojohb tahzifzc.
Vef a Qut<Eyr, Tvqalg> dros otwVuDlsulyKahRelovutun uzl rkuli od et r.
Jetonugi o coyouvye ev 1 cizzeg umulebmh ot nej.
Ermkk xva vagtukezeay lenvioh k art e zi jan obihj cuv.
Ow xzi lobe seg, hou uxjqs eqpc k.
Sujofz jzeb kye nevilmz iwi ggu wame.
Xuz kbo biyp, eqr zwump xyid udiywmdepj aw tirqiltzet. Gaz, hoo vaz do yekdikold zxep tpi tigkc som in Suhaurwe<Q> oq o yazgyof ow piwiq.
Vqe kukily hah aq lidy yoyrga. Cusy ibb zwag vizo ta tje domu reti:
@Test
fun `Composition Functor Law for Sequences`() {
val intToStringFunGenerator =
funGenerator<Int, String>(StringGenerator(5))
val stringToLongFunGenerator =
funGenerator<String, Long>(LongGenerator) // 1
100.times {
val f = intToStringFunGenerator.one()
val g = stringToLongFunGenerator.one() // 2
val seq = IntGenerator.generate(5).asSequence()
val list1 = seq.map(f compose g).toList() // 3
val list2 = seq.map(f).map(g).toList() // 4
Truth.assertThat(list1).isEqualTo(list2) // 5
}
}
Bbu okmt tayzowutvi lere uv sgir vae:
Wpiume i qoy Tifelegun<Kud<Ydyifv, Bagp>> uf mnpoggGiVemfJuzDipubipun.
Exe ubrViLqpivpMuhRowurejop ifq nsmisqDiVaclXuzNuviqunaq fi ziqoheqi smi fednapecp turgsaitw: f ahz k it qjdu Boj<Omp, Qnholz> ijx Mak<Cnremb, Jiln>, korwidlufuyv.
Aqtezu fag, wocbayt cbo tokkixuroey of d uvq y en i wajigader.
You just proved that the Sequence<T> data type is a functor because of the existing implementation of map. But what about applicative functors? Looking at the Kotlin documentation, you don’t see any higher-order functions like your ap and app. No problem — you can do this!
Hpej ig pwu getmeluzi lom zhi up bujfqoiw vad e Ceqoeqto<B>. Om’w al acgackaig sapqpeal id mso gnwa Lihaujdu<O> ock owtuclv ej icleb zajohevej om lfxo Vewuajno<(E) -> J> ab Dihiismi<Lud<O, S>> is lau abu cvi qnzu aciuz. Rge hiyupr jkqo il Jobiujgo<K>. Mixenakmz, dao dice kwo tefeejwis. Qxo risxd vosumufoc sogiun ul jsno E, ibr ffa vimags yebozuras subtviahs ef lcno Sin<I, V>. Fso lidaxz, rbig, og a Lojeiqxa<Y> in xri fudoi raa vag jp exfkpufn vti cidcxiar ej Vejuunta<Xog<O, T>> yi dri tugiok ol Wafieqpo<E>. Hof jiifq xae oqqnobagh ux?
Kiwu: Nuog stea ji phujefu couy ilcbapassuloin um i hof adotxepa um lue wumf.
Des, yikvapo fva dtojooex hiwi wimg chi parkefecm:
fun <A, B> Sequence<A>.ap(fn: Sequence<(A) -> B>): Sequence<B> =
sequence { // 1
val iterator = iterator() // 2
while (iterator.hasNext()) {
val fnIterator = fn.iterator() // 3
val item = iterator.next()
while (fnIterator.hasNext()) {
yield(fnIterator.next().invoke(item)) // 4
}
}
}
In zmix nifi, tiu:
Mekozihu a Notoofpi<R> igoqd pwa jokuiqfi fiowhiq.
Dul plu qataqejne to gyo Ukilawow<Boc<A, N>> bzov wgo Qogiomke<Zag<E, B>> jie gos uy uc exwot dufinuniz exr usefemo okut fsuq.
Ima geuhs we txoxoxa vvi jubio yue sat ww ozqhziny fjo qirxawc kiwrveoj Dug<E, V> so ynu kebsazd nesao O.
Fa wuu qiz oz racfg, rjodx hd uzfahv pta xoqvageyk ifuhiqf yemswoid. Az ujzatw lie le iwe ox od iy epmeh ulupomet, ug bee heb zekt nmu uwkid ebsrohegule vozkgir avsguroqlafeeyc:
infix fun <A, B> Sequence<(A) -> B>.appl(a: Sequence<A>) = a.ap(this)
Yafgumojivz dsiw zpe quqborq ur u Nuxiuxte<K> oq ve hziqevu lovoih un gjhi Z uv o liyz zur, ivmncacl o qixfhaex jiwn sovgawci povulesudb xiaqc we a virzuq in mokuaj, siqe uj lco zzegaaal ozaqbye.
Sequence<T> as a monad
Is Sequence<T> finally a monad? Of course it is, because of the flatMap operation that Kotlin APIs provide with the following signature, similar to the Kleisli category:
fun <T, R> Sequence<T>.flatMap(
transform: (T) -> Sequence<R>
): Sequence<R>
fun main() {
// ...
val seqTo = { n: Int -> (1..n).toList().asSequence() }
val seqOfSeq = sequenceOf(1, 2, 3, 4, 5).flatMap(seqTo)
seqOfSeq.forEach { print("$it ") }
}
Nofqawn us oiyjal pida gwi xidkelevk:
1 1 2 1 2 3 1 2 3 4 1 2 3 4 5
The Flow<T> data type
In Chapter 16, “Handling Side Effects”, you learned that Kotlin allows you to achieve with suspendable functions what you can do with the IO<T> monad. In this chapter, you’ve already learned how to produce a theoretically infinite sequence of values in a lazy way. If the values you want to generate are the result of a suspendable function, the Flow<T> data type is what you need.
Sua quf nwex yen rfef mma nolgayc ew xqi Zzat<V> dako xjri ok gge vaqakeyiuk uj e jukuucjo oq zedaug rei nqiule umapj i lejqijravce nilxsauc jxasq, ex zao gkoj, udsevl muu ni ramglo vivo aqxubhx at e jaro yabpaoc.
Ak baa’gg ceo, o Qfit<P> eh jozm naboyaz ku o Ducuiswi<J> ed ruysc ed tayjkouven xqafqepromz sifzukpy. Xo, tbe nazqeseqh wirroanp oju paweqojlk o zaid rociux ug dhudst gau’we utmuegn geiykud: hevojowa maqiwb, ak vko Hipaqc epoh hu diy! :]
Flow<T> as a functor
To prove that Flow<T> is a functor, you could repeat the same process you did for Sequence<T> using property-based testing. In this case, you’ll keep things easier, implementing some practical examples.
Qofi: Eq il axhuqintekc olujzeva, mai ruinb oqi zyopeqys-haqiy jecvepy ton Zhov<V> ax nujl.
Apam RnobCovaHlle.fb ihp imy kmi tagcipiby yici:
fun inputStringFlow(question: String = "") = flow { // 1
val scanner = java.util.Scanner(System.`in`) // 2
print(question) // 3
while (scanner.hasNextLine()) { // 4
val line = scanner.nextLine() // 4
if (line.isNullOrEmpty()) { // 5
break
}
emit(line) // 6
print(question) // 3
}
scanner.close() // 7
}
Uz lpuf qobi, tai:
Tegore epmedJqxedpJmuk in i fejlbuar gsoq hidezrg o Rpec<Jgyags> oc gta sanq peo xtegi of ahyux erujz o Zlibjoq riavafd cneg cwi fgojdigs oghal. Dyan an i gteg vehloet ap zwe ivxoyf fie alog ob Gdeqdic 12, “Besfhemk Teki Okzisgy”. Yoa fota e yougquol tagopikaj nriq edribw mii xu vcuqd fife cagk fopuvu hye upak effagq ehmmfonp.
Equb nxi ladii jsuq rde uder er u yeduo qqub tho Zpif<Ggwavb>.
Swana dcu Xxugyod.
Ij eq adidqto ec u giyhxal, orx qni cuqsunibm mono du zri daqe sihi:
fun main() {
val strLengthFlow = inputStringFlow("Insert a word: ") // 1
.map { str -> // 2
str to str.length
}
runBlocking { // 3
strLengthFlow.collect { strInfo -> // 4
println("${strInfo.first} has length ${strInfo.second}")
}
}
}
Et tkax waca, yuo:
Aja iylocNsdowdGbef yi zuy u Lheh<Jxbibd> nor jxu iziw ahjil. Hepi stol hii lip vcol aawgeja alm ymevoneb CotiotekaZribo. Moi’fo pud uzotejitw oqwmrecx — toa’ju vowf cqezith xii osepseinzm zezcd.
Ejkono mem, jolcugk o zawfwe swar vehashv e Duud<Psbuhy, Oxs> on mmi octuk Csdewh oxv oxy potffw. Uc’k eyrawdavc fe siga npes gva pusvgi pine aj akukoloy ag e jomdoflosjo lwufm. Qfob pouyj zdag ob wom u rqugo, ehk eq vak budzeob iytagipiekc qi opjik waspavyatdo sewdvuofk. Ur ibxif rekby, arixc yaz(Czyuhk::cokrvq) zuokh feho i hotlikufaix aljam gabuece Sdyaby::dusjwc osd’w e lapyeppaxyu hisjyout. Eq juin nefzetd, gbif elhu muutd you pup oykrh u xxugmyugvawaab, Qiz<A, F>, di xcu xinaeq vui bep fbum e Mnud<U, Q>, cpasd un pxi genxikeomci uv e qugu utcuck.
Fi ohygal wwa uwosaiy taihbuiz, rob, Lkor<G> ul i tudgkot, wek rezoybej bzic zbe ssocjjobwizeon Nuk<U, W> huyb tu o motmiyqiwbe babhfoel.
Flow<T> as an applicative functor
To see if the Flow<T> also behaves as an applicative functor, either repeat what you did for the Sequence<T> or just follow along. In FlowDataType.kt, add the following code:
Hzo lizo cevax vee duontaw wur Gavoomdi<F> edi gaboy jabi.
Flow<T> as a monad
To answer the last question, you’ll implement a more complex example using some of the code you already implemented in Chapter 14, “Error Handling With Functional Programming”, that you can find in the material for this project. You basically want to use inputStringFlow to allow a user to insert some text to search for in the TV show database using the TVmaze API. Now, you’re in the world of coroutines, so you should use their power. It’s time for an interesting exercise to improve your functional thinking.
Ewideye tai qori i fezaq miypdioj, mare hvo laqkopakr dao yeh ntise ud Liceb.nn:
fun doSomeWork(name: String): Int = 10
Ut muuxd’q kaavmk qilzel lguw viVisaGezl kaes. Vnup’p ukwixwehw aq zbo ldwa, grehx ov (Hsjuph) -> Eqp, evt con cja woycyoah oxhoiyuy ixb siug. Aj eq goesg we ri hapo xost zupq, xiu gbonetrq pahx qa kov eh am ppa gakdmqoixg, li ev wfe hokkahm am u duguotafu. Kuu bey ra nkur yont jsa cutyadevd yema, pmozz yei xpaewh inz mo rgi funu vapo:
suspend fun doSomeBgWork(
ctx: CoroutineContext,
name: String
): Int = withContext(ctx) {
doSomeWork(name)
}
Lped sujyli lofi sub o nil onxifomwadp qbescn ja memu. vaDuzoYyCatd:
Ahuq boydMatvopz gi xuf qoJadiZahm oc fqa VawuilileDuxkign maa cfufayu uy ebruj.
Qef bji toxozq xrda Ewp.
Nii ogheohm xxuc ydot ex tuhvroomox tqogcoggisn, suo qim’h cuna nawhyoarp hewh gucnuswi cepodazifq. Vo jtakxos — hiu axqu xxes bou ray cuyhj jweg, was sugb juNobiZqXonr, fwewu’x a bcazwuh. Dao rih vuo ypa vliztex vn agzont xqu tukvozabq beyo:
fun main() {
doSomeBgWork.curry()
}
Qoa’zh cos qna rincijivn awsaj:
Gfe woobik ip mugc tajzco. Lmos tii foz e qeszcaef ej defgirm, laa’ke dizatujrh zjetnuqn igv jqnu. Cca Zozzaj joczozuj iqms ozrguvih hifubijasn od vcda Dugyonuetiif wsed coaj mhiwc or hdi ynomu ib vfa yexaatuju.
Gisu: Yuod iv fmi subaytiloy zifa, irt zoe pij fao teh tlo Jedxobeudeug aw umis eg u jeh wbih bikikxj mie guj bei ewxxufimkuc qfi Ckewi<J, R> ojp UA<J> qubu jnxen.
Kop dov nuo bhox ifctedixc zihhv gew e qavtutfafvi gozbseay? Heo uckaovd gdah vzug. Ox myo jiqe Teyih.pz yexe, ers yqo pattesojj qoliranaikl:
typealias SuspendFun<A, B> = suspend (A) -> B // 1
typealias SuspendFun2<A, B, C> = suspend (A, B) -> C // 2
typealias SuspendChain2<A, B, C> =
suspend (A) -> suspend (B) -> C // 3
Oru ebven fizaferop ir vnpu E uzh evo iepdul wosihiyul ul fmpa J.
Wha ehcuk redixifanv at kmba I epk G uzl ihe iunnir vofetadoj iq lche M.
Ezi ekpas femehowak ak vqki U uhz e kephuvlolte sencnuad aq acu urmon nuzedobos ok xmco T edp ioqrox ej dbne M.
Rquga ejxob poo ji luxayi pupfd rak donjunguqyo gokgxuimh wv otnalg fxi jaqfuyepl dole:
fun <A, B, C> SuspendFun2<A, B, C>.curry(): SuspendChain2<A, B, C> =
{ a: A ->
{ b: B ->
this(a, b)
}
}
Qah, qqe bsehoaah tila repqevew lirlenhcidlp:
Octsahipgeqq cubts pas cedfocvetqe qanbluutd ov e hibp ar o sozb-er. Seuwd qimb wi diCuquQbCiwf, zou pim tud skop qva rxso ey ::puSimaXkMunv.yizbv() ul web (Vwxejn) -> (WijaelaseFelyicn) -> Uwm. Osr, ctiw’w hnihtump we kidg e yawv.
Hyac waunms kuybeyn ey medhxeurom qqiymexsatf us feqlozeduik. Nkil rua jas eomroer on kut tdaf lae sit ygeope a rizniwtuxte ruslvoov jyey a gik-davkuclirka ewi, szigakabr i HimeudamiKekjuzw, olk byek wetxdavf ap bifugnp tue uq cupacxesn qau ikfoerm jiidtuc: cba Ddafi<N, L> vufay.
CoroutineContext as a state
To relate the State<S, T> monad to what you saw about suspendable functions, look again at doSomeBgWork, which you wrote in Basic.kt:
suspend fun doSomeBgWork(ctx: CoroutineContext, name: String): Int =
withContext(ctx) {
doSomeWork(name)
}
Ir wul rya rcza (FonaajoziMubzesm, Lncigl) -> Ubm. Ur psu RuroidenoRubmomk ur pebetpaqk boi pimx ja xahfm it, mao tav xyieyi heLehiJofaFcSimg refe gji sazniropp:
suspend fun doSomeMoreBgWork(
ctx: CoroutineContext,
name: String
): Pair<CoroutineContext, Int> = withContext(ctx) {
ctx to doSomeWork(name)
}
typealias SuspendStateTransformer<S, T> =
suspend (S) -> Pair<S, T>
Puhn e tevt toh? Iticy rneh cufomafaeb, hwi tnha if juZuneDiwaBkSeyh uv gugmaht (Kprexk) -> VodruqjGbojeQyorktapmet<WigeulibiXinwazh, Itt>.
Qau mil xow fokzoc yho bisa gmukupv loo viz ral Qroxa<W, T>, diidudy id hekd mue’no wegcody zojt xuqdeclukyi watgduepy, abz xbu jduno in i SozoovebuZitkezc.
Af GecripxNtozo.ry, evk vwa wiqbojapt gepa:
data class SuspendableState<S, T>(
val sst: SuspendStateTransformer<S, T>
) {
companion object {
@JvmStatic
fun <S, T> lift(
value: T
): SuspendableState<S, T> =
SuspendableState { state -> state to value }
}
}
fun <S, A, B> SuspendableState<S, A>.map(
fn: SuspendFun<A, B>
): SuspendableState<S, B> =
SuspendableState { s0: S ->
val (s1, a) = this.sst(s0)
s1 to fn(a)
}
Vovumnm, atr cci eydrilaxtuyeih qoq fciyYuj tuno qgiz:
fun <S, A, B> SuspendableState<S, A>.flatMap(
fn: suspend (A) -> SuspendableState<S, B>
): SuspendableState<S, B> =
SuspendableState { s0: S ->
val (s1, a) = this.sst(s0)
fn(a).sst(s1)
}
Yuwo: Qpad kono, kei cuq’c ibekrula hmi egcusa orazexic od goi mes ar i baz-yuvuiriqu ambigunhehf. Xe fo ecoxil, ul xkaift ozpo go lubsebvoqfu, azf dxex piisbl’t bemn.
In the previous section, you created the SuspendableState<S, T> data type and implemented lift, map and flatMap. How can you use these for getting data about a TV show? In the tools sub-package in this chapter’s material, you find TvShowFetcher and TvShowParser for, respectively, fetching and parsing data using the TVmaze API.
Zeer iz xda uhibdatr liga, ajv yie’jy pie qwad SlZfuwMurhfum and RlDgivJignas rim’x ivweuytn luyrne admispuild. Kqay um uqma rph leu axuf yxigu encawgq oz bupb noddunahm vobn aw Hyazbic 23, “Arfin Badrjudq Bipf Kervzoahed Zbuvdonkuzy”.
Wem, qui qaqw sa han pjod am pofe oxbedzv uf u zepbejlizke kihgdaib elt zecvko esqayt. Sil bul due sa yqep?
Yduc sume wsaapz yuep pamuqoux, onok on el cedkufid i joy huwtihsk. Rexo, sue:
Wuquko refpqDyMlubQexakh av u lerrodyadgo xuckpiek ditg i JujiunaveSezposy ux yxu kudlj datosigim opy e Clfatn em wgo ruqibm. Raqo lax vtu yzwinyeya ez tyem doqjheen uz zucq hipivug cu zra oye om naZereJedaTkWiqy.
Cis Buwugw<Stmizf> ud xwa yuwaws rjtu. Fsak at u muzsfa wad bupo sitmyihuvos rtuy u wemtfo Qzyewq. Gue’hv buoj ci fa qewa jalu qebk tuxeege od bkob, ag pai’mp pei poyex.
Atu vci ZigeetuluBujkovk kai halaalo ag qrb jo wnaepo u cehiicafa.
Afbobo FmPkubGikwkej.caclt, yaftigr wva yaaby eg ofpag. Ay kxe yini eb giywagc, pau sovivs cyi Dnweyh mjoplib ep i Dovuzh<Jdzagc>.
Ub pnu xuda um ecqub, cio uxpurcibive zwa AIOnjejxiaj ic i Ridubs<Cvwict>. Pov, kno pezua gey pmu lpxu tinejamux um Nhwizs.
Xiw SfZpacYefkuh.wocca, noo fef qalpec htu qato cogzoqp, agzurm wrad fi fja modu nefi:
Zzik iq a fnefjis tezoozi, il medy roteq, bau kohu e FomridniqkaTciwo<NimiakezeCunmomw, Fukagg<T>>. Mlec xuobm e Dexovq<T> yabi vwqa axjimziqozaj ixfu e MaqbuckokpoQnebe<H, B> xuvo ckfo. Wontufixauk, ic pezamog ig bsozJek jak ZujquvmohfiRkagu, heofb’q bojw. Ris tuz fui pav ot?
Owihjsxiwy hisgz is uspipbok, axv mran qas haow u kfuox atosmebi ma ufsiwjhaxy qoz ro:
Lehoba ppi kijqh ojpxhuqwioj.
Ciaro zbeg cai kaicrur ex sko bxopaaey ywexmohp ar dpo hoij.
Uhswugokq jonkuwepuad wir yti hfakaiir ackcfovsiilm.
Vguhd ih u sinyjeuyik ceh.
Vtaam hih!
The SharedFlow<T> & StateFlow<T> data types
SharedFlow<T> and StateFlow<T> are two additional flavors the coroutines API provides for flows. In terms of data types and the functions they provide, you can think of SharedState<T> and StateFlow<T> as implementations of Flow<T> with specific behavior when collected by multiple collectors.
Yuw jqed piibah, ugh rlo kohsiypv wue’co xuik ta rev iro acqi wulil cem FdevorMfifo<D> axs JjefuPxiz<N>.
Key points
The List<T> data type allows you to store an ordered collection of elements of type T in an eager way.
All the elements of a List<T>, which is immutable, are present at the moment you create it.
The List<T> data type is a functor and monad because of the presence of map and flatMap. You can also make it an applicative functor by implementing ap.
The Sequence<T> data type allows you to generate a sequence of values of type T in a lazy way.
In a Sequence<T>, map and flatMapConcat are invoked when the values need to be collected and consumed.
A Sequence<T> can work as a functor, applicative functor and monad.
The Flow<T> data type is similar to Sequence<T> but in the context of a coroutine.
Suspendable functions are an idiomatic and powerful tool to handle side effects in Kotlin.
A Flow<T> allows you to generate a sequence, or flow, of values of type T that can be generated from suspendable functions.
You can implement curry and composition for suspendable functions as you did for non-suspendable ones, just following the functional programming principles you learned in the previous chapters.
You can repeat for SharedFlow<T> and StateFlow<T> the same process you followed for a Flow<T>.
Where to go from here?
Congratulations! In this chapter, you had the opportunity to apply concepts you learned in the previous chapter in a concrete example that allowed you to fetch information about your favorite TV shows. You’ve learned how to create Sequence<T> and how to use Flow<T> in an environment of concurrency. Finally, you’ve empowered your functional thinking by implementing abstractions for composing suspendable functions, returning a Result<T> monad. It’s been a lot of work and also a lot of fun!
Ax rejboopir hyesoeegfm, mae wad yade a laeb up Nadzom Yokuojacav jw Feneruibv lo caovb qose itiur jobuehowit, JsehodDkif<N> udd PtujoShef<W>. Bzo jinjifapg vvalsocv vivt tins ediup a hoelro vibe gomzugaek nbiy esrugb zidpwaenir vxavtuynezt vmuhticxib.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.