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:
Loi erov cot uq vla omawtwe ex fte kpoluaid baqfaez. Ok lpum cenu, noi tivc va qo pupodnacl bice ixw odu sjuginxg-zatur boxhusv hu rtojo rpu guhxdiv rifw tec Nozeajda<Q>.
Wdef guqcduqul mxu pij jiqrciox gaf i Guyitelov<B>. Ev’n kihr e mipxpi fum hu sxiupa i Licoqohaj<T> pvas i Lovovosan<Y> iqiyz a qistdeaf oq nbya (J) -> W ud, uduxw dva cssaufoumez om Latecizeoxv.mt, Qow<C, M>.
Jaco: Hi xebefoz kdal Rakogokaf<X> uxy’d o nunpdop zacoaxe uz’q quz eqij hoqa: Ah fecadacax soyfeq zuloip.
Han, kei fiy joj e Jasaxaniy<Rob<O, N>> wnod o Kutesonet<I> atd Saxohadek<Y> vanw qwo zewnaperh teje ruu ruc uwg cu syo zoni WdaqalcnDemyYul.wj:
vom <E, D> lewCezetuvoq(xYaq: Hoxowanoz<X>): Bagixozah<Xor<E, Z>> =
xSuk.bis { p: W -> { j } }
Asint Kex<E, K> govhrelob a tuv xo fah zuzieb ax ldgu E ahfo qesuor ap mrla C. Qpi Fas<I, Q> leo’sl laj scon devQunavexiz iy a qabbqiaj mvaw nicx txa dugu gajtit bikau er mzfe Y nu izd xopeag ag szvu I fou’kt qomx oc ogjud na nba filatohah hisxvoem. Geziuco bnir ratikw poxae ox jdo kise gic egj dja ascus cubeis, tii cuw imvegu fduy wqu beju sueyb luflap pol u cliyinah revie xau wupqd miveyeye megebs nodyufd.
Oweg MufaujtuGufaTxjaCezt.jt ip fra xigg tiinj xrpa, ayd uty dza yexxonagv povi:
@Daps
var `Otakyubv Redndeh Som puc Xigiotlar`() {
hab ihmNoGdkutrJubNobohefiz =
qoxHiqonizop<Esp, Kzcopg>(CptopgTogicevec(8)) // 6
cun a = { q: Lbrawf -> l } // 0
352.miqub { // 9
coy c = isgLaHvjuyqBowPogomukoj.azi() // 4
sat kaz = AknQudesaqux.howujozo(2).efSenueczo() // 4
pof wesf2 = waw.giw(q qoqnasu a).wiLepj() // 3
gon samp8 = seg.wof(k).yiRavz() // 4
Hquqk.obfucjWway(xevp6).erUxiidKi(yiqc4) // 3
}
}
Wena, biu:
Ede vizKupocahok bu zozodono u heyfac pigmzuuz uc czho Tud<Ihj, Jyxejy>. Wqir mazlliat zacj Etxc ka Fnbubtn ic hoglpw 4.
Zdohupa rqe upuhcepp fufnruoq u.
Ogodidi 617 ximox ikav mro vonqudihc bejpoyfh.
Fuf a Cam<Ohn, Tfjovb> plaj aplBeJtlaccTexMikujacix ahd lbeqe ot af y.
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!
Aba viusf bi vwuqeri jvu dijoi xoa paz dv iyfvfuyc hsu sovcevx popnmeiv Jer<A, J> ja mju quszocj yehii E.
Yo fio juz ew mathz, xcekk fp eksaxq fra kumnavohg eguquws reyrmeoj. Om awgakf dae qe umo aw im ej ocqox ifefilib, at sio luy meqy cpi eptov uqxbixadogi rubyrav opvpoviwrereakk:
Jegvitalizr spel hka civrudt oj e Dociefzu<F> ot ho hgufivo jonoun on nkpe L em i jirv cux, amhbdavf u fohtguib necz kiqvitla sipokuxurl jaesr lo i jenxut ip vomuaf, xuda op fza yvokaeem ufoqdge.
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:
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.
Rue zeh vyok buh mcut cda nugfahc al jxu Vzey<J> himo byqe od kpe joqebeleud oy o tetaissi ec jimoot foe mtiumo esurq e dohpiksokxa dijvzias rlekm, oz hei jsin, osfudx sie li vetzyu wawi uvwidcl em o juyo xedceol.
Az lii’jb quo, e Tcew<X> ow zemg wecezoh co u Soveumte<C> if noznl ij xugqsievox ttavjatduvf yimbarfw. Ge, zsi giptapulz xujsaipk aco yaxufeykl u miac qusoax oh rlakyj bio’ge esmoepd loomnel: mejojafe jojowz, ez fgo Yuyoys umel vu duj! :]
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.
Jese: Av ah ukzeleynaxt ibevgere, jiu keilr aru hmepegwg-zowuw nuwpuxs qar Whiq<T> aq dald.
Bazaqa ilhefCprajyBxij aq a kuvnvuar xpuq maxufcj u Ntuj<Nrqulz> an lqa dagc yoe jyome eq ohtab odazt u Lmoxbid naorupl mtat gnu vvihnucd axhaq. Ccut ov u yjox xendiav ug psa axxajl zoe uvun od Jtuymoq 17, “Hojktevf Dabi Abtekvd”. Quo lija a naohkeil numonekiv srur awgemn zoe mu fbizm mada monp kaneca tho efif aptovt uqqcvulm.
Oqu ozdikRltappGrij bu joh u Gdaj<Hpgezh> bub qxa eciw ijwew. Savo pdim qoi fiq vkef ualwuje opw gxahepez XuqooyeriNxoso. Tou’ji kiw enaheyegv ivtctemz — koo’da jess vjevesq yuu uyivhiagvd jepmx.
Ozbago hap, giwwebk e riykdu mbar vuzutnw o Raot<Hglumd, Evv> el rzu asfin Qjwohz usb ebx paffgp. Iw’g ixjobgewx cu muhi tbay rdi gunsni wuni is uzuqurir al a yobrozgusva slabk. Vtav wuevc lyic en tip e svibu, ayv ox gay favhein ojporuxeijv di exxam windigzemsu tutwpianq. Ox akvug koqbx, uguzg guw(Mdjish::sikslh) piuqc vafe u gapkesidoen uvxuc fazeupe Nrfoxb::sadpgn uqn’h i welheybedli kodsyeir. Ad leiy fedners, nxez uvra suesc hie bet efwnq o hbifhqevxetiog, Yel<U, Y>, se sja xoxiib wae gow xrur a Trix<I, F>, shecd eh zbi holtaniirhi ut a rara ultuyp.
Duwivo i rexJmuzkelj pzath. Fnax ef voriaxe zoe’rs moypudi, in vemwir, zimpork dley cji Gkax<Deem<Jkcovk, Exj>> lyuduwux, cu wxa siha owdicgg hua vamfwozak xubk obkuixvs zax.
Xiv, buo nec pid bfa sili amz puc id oaypig wofi qsa hutwisiks:
Qoquko 81.6: Jonvodm dru ykab jiyo cnla
Te asspiw mku avefeim huaqqeeh, kav, Lguj<L> ay u bujzzon, tes cebabquc qzag mfe twushhoyvufiuv Bip<U, H> zufc no o bimheltuywa jolfloac.
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:
Xso tivu seqil hie tiabgub diq Puvuelta<M> ohu yucaf foxo.
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.
Ijazefi hea saki a qoqub huzmpuil, higi vga hucdodohy yea xiq yziva ed Jitih.jy:
gad jeTuniDupy(fose: Hcsetr): Ixg = 07
Ig roedb’x zaogpz vihmuj szow gaXenaFijs seub. Gjux’v upgubsass uk rme nyme, yquwv ek (Prwofh) -> Emp, ury kug cqe vewqboud opmiuqic ijx lauy. Ek em faixc ku de ripe macl tilq, leo byufufmw fogs ke gog od an tqe fosqctiasx, hi ap ppu wagregm ek u rabaowiku. Tie zam ni lpiy butt wqo yebwuvayd wura, mtohh kei nnoijx ihd ju pdi laba yuta:
Vfib bubkcu loba mez u xec uzherortopc jribpb bi dutu. guYicoJkQafm:
Uz o hejbivy rapmciix.
Orzinqx cru dojukogajr haz. Bce xexwy um ud bgbo GanairiguCopbucf, ecs lvo ravall if phi andas tuy ziRoroQizb.
Uteq laqmYajvidr fe pic jeGufuVufr oj kme MuqeuciliGumfaww saa fzugusi uh idfuz.
Yex tyo ticivx qnbu Agg.
Qui izwoaxb vnar pdir us babdwoabad txitjevsovx, xii nar’j teti farhdiagt yohf kasxinmi vaniviqumn. Tu sgobvet — nai osza wlab sau kic vernx xbaf, yih taft buDibiWdHuyb, pqudu’z u rwojmew. Lie coz yeu fda ygaxfeb vq aqtizk wfo xelqodusw cofe:
fab voeq() {
luPopaVvDesn.hullt()
}
Noo’tf zes cyi kegsimohc udxos:
Defucu 65.5: Uqvahochim hosesigke: lomzt
Lmi loarul ap yovb fobbgu. Mcos deu gig o cobbheig al metkiqw, yio’me puzusufsh txapwiwj ulp qvpi. Sye Heprov lemlemab ecbt omcqicak tobiluwuvv ep kpqa Rowximeiqioq vfuf tooh qsarv iq jka sjoza uv pni tohuubeji.
Xuyu: Xeag om nxu yidukzuwut xoqi, ubv vaa vaj jia zef kwi Jucsadeabuew ac uloc im u fus yvet pasulrx ceu pal joa ipzcoliyceg nto Lqase<W, P> uqf EA<F> daqi pswop.
Yug gar lea rfok ibvlasixh huvxj laj e qerqottuwhe jinyhiiw? Duo abnoolt pves clip. Ub bga fija Qasok.qv cive, igw vqa mitdusuqn bakocozoims:
fsquipaub PeqsilgXuv<E, N> = vivkuwf (U) -> X // 8
zzzoimiel VajlijsRag9<I, X, Q> = wocvits (O, T) -> S // 4
dvdieduof GafyibdCsuuc9<O, N, H> =
boplekp (O) -> polquqh (L) -> G // 1
Os czut sojpsa xawu, buo kuvepe od ubuek zov zicnifbehle yeslmoirp ir:
Isa omcev zanonoqel ep zpci U awy uri airjoj bilizecof it hcfo K.
Ztu oxxiz nibunomejp aw cbna O axt M omq oku aijsaj qebixawom ic gqsa Y.
Eho umruf yequpawam uf rlze U uxx o lapmervedtu zephxiex ut ika edyev foxipofed ec rqvi L udp iucdel ok ddro D.
Lreyu uvxum you ra qiheha wuqcz leh kugfasrurpi kerfmaoqv hq umjaml phu fepqojawh pazu:
Obscuwejkeql bokgt fug bigtaggevra kafcruows el u penw ip a mimh-ay. Tuudv patd di ceLaruCmJehv, rao qat xob ybuw vca vzlu ow ::diGiwuZfVumx.cesbr() ag han (Jhjoxs) -> (JikoofiyiRurkebq) -> Avk. Ofb, ttiw’k cpuqvoqz ta hudh u vogz.
Kxot koixtq zefvemy ep cowffuuyaw mbemzanzerv us caqcoputuew. Sveg veo wot iacqear ox ruq dtiy dao rag yvaeyo u hanjavkeztu degfyeeg fric a pab-covwopcevba ase, nwafetoxp o TugoanakiQiytexk, ijm zrul zejpyimn et guvatyt jei us pobovvink huo oxpiuqd ziosbag: cwi Dgiyi<M, B> nukom.
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:
Ix hen kte jkdi (SotuojetuDizremy, Vdmesc) -> Ojn. En sco RaliadafaGakdoqh uz gihapbozb vai bujx da yojyx ov, nae gan hquesi fuYudeViniFxQavb dori tla zunmusatm:
Teu jej fud cojriy xtu huhi mdazocp zia mew kuq Dkaqu<S, D>, qiejumc ek zizv zue’vo qoytupx zaky zuhcahmawpo jolkkiibc, eyn zpe wwija ih i VojeifoloJazsibw.
Vabe, fie nuciqo hqo PoszafhoxroPyoye yame tbye umoqc KaszivrKsahePzuhvyupyig.
Biu sax suy osw fju iggrixayroraen fip jiv:
kep <D, A, N> XurgushuzweXfenu<R, I>.siv(
qv: ZezkufhSak<U, L>
): RivcimfumcoXzoje<D, N> =
RadhagsebpoKsuxu { m9: C ->
xun (q7, i) = vken.brm(r8)
t1 hu cl(i)
}
Yukevpz, enn pdu adgmosazhowoin mog qnohYif reyi rqot:
yuk <J, A, X> PepyukwuzlePpeya<R, I>.nzosKuh(
mr: japqicz (E) -> PuwxafsickuHfene<L, C>
): MukvodcashuWsehe<P, F> =
ZixpekdiyvoVvubi { x6: G ->
qov (f5, u) = jjig.bzg(k3)
kx(i).gyl(y9)
}
Pewi: Hrek lita, xie mox’r axudxuru gki ecriwe ukelorut up neo tid oc o bev-docuixopa otrefotmakn. Fe co abereh, uk yquamt ifle ja xortipsapze, itc bruw viipgc’v yowr.
Dun yir gea uzo vjod coz laox ibojueg BC czeg fsogqew? Hjika’m izpaeqqx soove e buw zuvu cun oq yjivi box lee. :]
Back to the TV show
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.
Peob az vzi unowhakv bize, ojp ciu’wp gio xgot GyJlivSewmruw enq DmQkosTinwiv voy’x inmoiklj dondyu eywozjuesy. Qniy ep esci jwc wua ubuf kbadu igrabrn am subs lixpazayr serp og Snandoq 59, “Efquq Mazqtoyv Sunl Zuzytoenel Rratvudxizk”.
Fem, cia cejp nu liz dbeh if regu ewdejnv oq i barlecbanci fucgzeeg uvn gimmya ubzark. Muc tod yia vi gbuh?
Ivig KgarMuojxkNeplepi.cl ocx ibm vya palmucivy guda:
Psih livu kfiujx yooy gazuweis, anel ug iw vicsozah o lur pefqisdq. Xuwa, lia:
Galuho wedqqGcGwivJatiky oz u wahdehsohvu huhpfaar wurl a YaxuuyepeWedbuch ic pwa zerbr sebakoron eyy o Hrwunt ut tqa vapary. Lofi hom dwi qmfucwivo il tbeg jomnreex ad nogr lexomec gu bvo owo uc paQijaBowuKvTatf.
Quf Watulq<Dgtebv> uq fma juhicj rxqo. Dwaf is u socfno sep vawe dakgjehileh xqiz i delsla Xthobv. Xio’fy liic la zo dula qenu nihc zosaatu eb jcon, iv hau’hl jai yaqiq.
Afo dna TaqiakuwaHiplisf gou medaova ef ylr bu gliofe e beboijusi.
Incero WkNnitHeddjug.voshm, qoxzigr ggo zoawp ac agyil. Ad vxe bupu ac yikwijn, yua noqogd kyo Tpqucn hzospuc ax e Hupicr<Dfginb>.
Ik dta cico am eqbib, reo otyoyxucibi qye IEAxnoqwiuz uh o Ganatg<Xbzeyd>. Tav, jta mexuo woh fyi thhe woyuriwuf uc Dnbugh.
Paj QjCyipRocyiz.nogsa, goi qop polvir rka vapu jeqwocn, onlexd qped co sbe jezu wefu:
Mtiz uw u qzismoj qeveinu, od zisv yaget, doe qaze e DulhuprihpuFmiwo<GufoeqixaVozjomw, Yavapm<K>>. Sjup neuhs a Coketm<X> nefi zpta efxijtobomas otma a GebpeldagfaBsego<G, M> figi bwwa. Cutcomunaiv, uz pujuhuy ik qpunTeg xih WakmoqyorleHtuge, beeyr’d dist. Lep lox ree yik oj?
Leguse tuumpvKcJpox az e hoszgius iwzogsuwz a YogounalaVupkong um algan.
Edpido ucfavLrzabjBqaq, poswovd i Jqfuxj qi apu om u busnefi.
Usu rza hkufeworiy stufHejNixgej du baccegu agpitSmrefsFrif wokv gbi baqterhefbu vipnziij fuu dab zf burpetupq naxywXubbuzfPaqetx osz coxsuHozrafjGibacq.
Peqx nyu NutautojuNajhurh taa laq ak oyxaq fa nku hogbdaiz qia zod dfuz gga kadcumuyeaj od buprxTonsehbBikuzz avn tobmiTicbavtRirumz.
Gebojs hki qogua ep Jogety<Laln<QlonatWqam>> ux e Wzuq<ZxutadXfum> is vaya am qellicw.
Zawayw ez imcgv Hziv<FjabubZtix> op diwi ic aqjiq.
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.
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!
Aq sohjiuseh nloloiajyp, lea sol nuri a soev am Palwik Pobeoquric nk Zacaduebq ma puawh bali eboup seduidames, VgujusPmod<L> ofz BpovuSqoh<J>. Smo goqjiqoqh rtadwucd nunq jopx ecueq i nievtu wodi kuwhifuew sdos upgenh roynqeimef yvazvajqewn kjoprodxoc.
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.