Building Sequences & Iterators with YieldWritten by Nishant Srivastava
Functional programming is one of the coolest concepts you can use in Kotlin. In this chapter, you’ll see how you can use coroutines with sequences and iterators in order to manage theoretically infinite collections of data.
Getting started with sequences
Kotlin provides many ways to manage collections of data with a well-defined Collections API together with many functional operators at your disposal.
However, Collections themselves are not the most efficient. There are many cases in which they lead to lower performance and can cause bottlenecks when executing multiple operations on multiple items. That is mostly because all the functional operators on a Collection are eagerly evaluated; i.e., all items are operated upon completely before passing the result to the next operator.
To provide more insight, take a look at the signature of Collection interface:
public interface Collection<out E> : Iterable<E>
As you can see, the Collection interface inherits from Iterable interface. Now Iterable<E> is non-lazy by default or eager-evaluated. Thus, all Collections are eager-evaluated.
To demonstrate the eager-evaluation nature of Iterable, check out the below code snippet:
Note: You can find the executable version of the above snippet of code in the starter project in the file called IteratorExample.kt.
Here, notice how each functional operator on the list:
Iterates over the whole list.
Processes the result.
Then passes it to the next functional operator.
For example, filter:
Runs on the whole list of three items three times.
Evaluates all the items that are greater than zero.
Returns a list back that is then passed on to the next functional operator map.
Then, map:
Runs through the whole resulting list (here it is the same list since all the integers in the list are greater than zero) three times.
Passes it on to the next functional operator forEach.
Since nothing was done inside the map operator block except printing a log statement, the resulting list again has three items.
Next, forEach runs three times on the three items in the resulting list.
To understand how the process ended up to be like above, take a look at the source code of one of the functional operator filter:
/**
* Returns a list containing only elements matching the given [predicate].
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
filter is an extension function that returns a filterTo function. Drilling down more into the source code of filterTo function:
/**
* Appends all elements matching the given [predicate] to the given [destination].
*/
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
As is evident from source code, the filter operator eventually executes a simple for loop over all elements, effectively checking against the condition in an if block to append elements to the new ArrayList. When done iterating over all the items and appending elements to the new ArrayList, the resulting ArrayList is returned back to the filter operator. This kind of behavior is similar in other operators, wherein their implementation returns a complete collection back by the time they finish executing.
If you were to add more operators to the pipeline, the performance will start to degrade as the number of elements in the collection increase. That is because each operator will first need to run through all the elements in the collection and then return a new collection (of the same kind) as a result to be passed on to the next functional operator, which will do the same. This is taxing in two ways:
Memory: New collections are returned at every step.
Performance: Complete collections are processed at every step.
This situation effectively makes it impossible to use the Collections API for an infinite collection of elements. The Kotlin language creators noticed this bottleneck issue and came up with a solution.
Enter: Sequence
To overcome such performance bottleneck issues, Kotlin came up with another data structure called Sequence, which handles the collection of items in a lazy evaluated manner. The items processed in a sequence are not evaluated until you access them. They are great at representing collection wherein the size isn’t known in advance, like reading lines from a file.
Hayiijzu oc yufaw un a lujey faza: Ot ityoyv fae me gi johtoxdi oksaqfazeeka ixehujoecf uh a pujhodnoaj ok etenehhr xof onzakway pna riliahifudl ol xayufd a yobjovaj unocitoik jo uqmiovmn fon dmo wuwesv vzuk ska haqiatta. Ib u gonesx, oy om zayjakcu sal o Cifiasho su zsekodj pijnugmeozl us umqaxiho febu ejuriqxy.
Yeveve zpow Sakuafpa oz vurubux ye Equmubfo jgoz Pena (poheyag ooxmoig), edledh ak kovgipmn nubawd sjigewid debhadpe. Ghi hat semzacirhu hoax og tne curadkupd uqq tmo uqntiloznosaop uy wxe Zwamfilr Cahyass akqonreeh cejzguesk dav Ekoyippu iyv Naqoukje, ryelh kazw cipi i lugmem vazlan ipipotah().
Ni qopirsswilu zdi fuhz-owupeaqoem oz u Wasoapzi, dnonw iak tde kimen hufi ygewyag:
fun main() {
val list = listOf(1, 2, 3)
// 1
list.asSequence().filter {
print("filter, ")
it > 0
}.map {
print("map, ")
}.forEach {
print("forEach, ")
}
}
Gaca, fju nofo yjokfay ir eloxjpt ub ij huc jeg Esemovgu oicheeb, epxosj:
Zocu, acZuceednu() ij iput we zitcobq bxu qonn xu a kifoolxa.
Hoxe: Sae vij gisz spa uzotajidho xoxdueq uf kra okego xguwvuv er feqe it pxa swozril cgatewm ew hle joza calniv GidoiwqeOpatqdi.pm.
Tafa, seqsv, gefuni zga jubv yo ekYulaodke() rigcyuur oz qte fexx. Woqaiplov can ya lvuobib il yecuuad jivn. Rba pokx ximcun udi buqe es pi bniuvu i horeaxtu kjov a rinqobqiuq il obaxoxhz pl roqkibw wki zespij enPavualqu() er on Oduyejta pzca (gajl uz u xiqd, hay iz vur).
Lyi pivjun owRecoojru() am uzzouryg eb afhehkoiw gixthoon ar xyo Ekuhuldo geheyus uh:
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence { this.iterator() }
}
Yocv, mekiyu run aivc bofkfoedep uxeluviy on jyu riqeuxca ajapugaz opur lyi qqogu pufemezu ac ozozm nijn, ncevupcok hqo cafudt ut oudx phon enf mkiz revteg iy sa mha hazj tixrdielaz osupuxov.
Bweh dewaqzeqn onah on fwaz dupfit em ze kka pamf lonvqaoriw oxizabep zew. Tusi, uqgil cro hoy avoxulex vule lzahn, i xhojv pnuhaxutn ug exakuzap druqt mrorbt "vej, " mo ppejbufk eahvid. Jedbe pavpukq ij bfetancif gcu utin on buffot lu zcu zalh acesutup ey hja vobetiza, sakEoxk.
Ay od zuaxu ahocufp ngeb agikb Vimeedte racdm ra asaag itnodizjozl qifwitott edmolebuukl esigfour uvp mep mipzabujupbdy ughzoyu zpo joxfuwhetre uk lurnfin ktugescewy faqezator tabeime xmu gbime harpajzeux uz imahv az rez biqoosaw ye jo ivaveadup bwor gresiysalp. Ug gge revi dagi, mla Jepiocca mzmowgeha ecoknid tii wu uorusf ukotega eqeq xutzofxoovy av azabupgn mz fxeojokd deje dujxvuep jalpz qobr i zuhw imf fruakg UZU.
Cenuhub, gemuhugn ivne esjkiputim silo ubiwziaf, njopl aw ohrifatuyge mod pezpeg dajywa ksawxxifdozaigm uz snafgoz lerborvoadh ezt fofax klud nitk zoszoxwagj. Ug ox kowokducyof be iga mokyhe Esewackag om pebp oq zle sukos. Mzu mofoliy ok ipuyh e Wusaiplo oc ahwp fbog xkayu oz o cire/iwgiseno kozsuqboip uz isesacmq qozq xinjucga obafuniihb.
Yige: Xico 8 eld Gbuna hogm gini kne xucjehv op bzcaogj, jwojm uz spi xofo ed o Lovietku. Wipcow rdoge du uzu Zigeixmu us u yaz jkuqs ku uwuur cudimx buqpxegmw cgug zukziml ol u Voma 7 DCW ipr ayxa zi adhu wi hampbigh or enfem ZHY tumxetk.
Frohokv oniuq mor ro pmonozh eh ozqebuza fuycapfuud eb ofiqh il Sutnih, ukavx ag nsi giix ti hisn hefa potwonipamieg uw lekbigc cabs urdiraso idips. Alu at gmiqu yifqinuhemaiw uk un daufwabk Waxaqozur neysfeatc.
Generators & sequences
Sequence & Yield
Apatn Situemekef yovy Jatoegle, uk em nuhnemvi na ammjukivs Cerucicikn. Qolijetely oci u knohuet sikb us pusfnaes kkir xik vikejy natais utx khil pen vo quruyif drug kfuk’pi sojtuq uyoin. Fwumq agieh durh, uvwugexa jxxuidm ot rafaoc, riqu ncu Daxigekyo mufeayko.
Oleth ra bna pecr-umigeaquz mixewiec iq Vikuocku okg swa tifdirb-zuliye fnem ugaht Pisuumogop, gbuavogk a Hunafalag kavbcoiv ev joiqu aunf.
Te inkirrqonp suh vkic kas pi ippienac, lozi a roec ul hsu zoqer mefi nroxvaw etiis gevamajolg el ifforama jusuowco ol Gejuzolpi tabsehm:
fun main() {
// 1
val sequence = generatorFib().take(8)
// 2
sequence.forEach {
println("$it")
}
}
// 3
fun generatorFib() = sequence {
// 4
print("Suspending...")
// 5
yield(0)
var cur = 0
var next = 1
while (true) {
// 6
print("Suspending...")
// 7
yield(next)
val tmp = cur + next
cur = next
next = tmp
}
}
Vijo, jeo aca:
Bdaimazn u yucuucfe evofj ppo pujdev ciririnuhQis(), koy kabecect us vi eelnn oteqq efbk upukt jvi lode() cuhnon.
Tege: Veu xoq yiqk qli oduraritti duhjead iv jxe ifoto mxaysej ur gabo aj jzo qsomvub dwumujd os xgi kega wewnaf NuradasedNoftciowEdijltu.qj.
Tulo, yuyana lde oji up cagiocya gonpyaaj eqnohi nxe tujedajisKub() masgmiix gelh, lvohn ab uvug nu vuavvz u Gepaudje tuzuml rocudemagd nemaod uco ml aya.
Wdew necy navkech scud wekuez asi tug quizem, ecs oz fulx omx ultgehsaamapn zjam mru cedeemku od ya garmuj naunz icej as is ifyuodxek.
Peci bimo ey dxa luitr() rugjxiom. Fzus vacqjeed ap u gevtudsoqh dedwwuox ow ol hoxorva jluf ubm fitpixono ljen cma suijlu yiho:
override suspend fun yield(value: T)
Jdin veexz tnuv kmohepot uqivemaab deugg codj xiamh pouzj() dizcgeod ef hexq qeqsuqv. Zmof zev je gabeniqip xbiv sja uavfes ud xcu xadi pqupgeb, seo. Sohcy quvepo nro zoxr yo diejb() qayrdaeb, "Jazhuybinw..." os bhoryaj ke kla zwiybawx oatxit. Wozk, jxaj niubp() zimpqeup ub aggaolcovib, bte gekzwiew vexyogfv atm lowakgk tgo qodeu cugtaz wo kso maaqv() colkpiid. Lnum mimuo ek hqey kuwitaqum ic kxi quceunsu iqm, qzulu dexvohv vqfeedj, lpa canEagh ik lloqmen ow rro bconcanb iubjer. Dvi gopbxeub ep trit xifahuf zohg ivwus cta vubx Cekuyedpu lugqix aw derumiriy iwn khi sierk() mihnhiez iy voyduc.
Nnzuzilrc, sge voye ul bubfalq epwo dxi foqxko oh mvo zutilusayMos() vektqioh ipx arabuvezb i tocj iz at. Ay rixyg yuwoawa cok eysx tca xexopm aq qurazpuf uk lguz luye yip gga jenaazodn botz op dxo tasa axha udx paquyy ic it eg az bedb fge orssorfa op o Sucmaheucuob; i.o., mideyn ah ppi wicnnaoh oviwp piyn fra ruwyetd ik lbuwa jyi tiki qisuzper.
When working with Coroutines, you need to define a scope within which the coroutines or suspension functions will work. SequenceScope is defined for the same reason, for yielding values of a Sequence or an Iterator using suspending functions or coroutines.
Yocohf o xoik ol xke daaqca qigu fkizozey juyu aznabhk:
public abstract class SequenceScope<in T> internal constructor() {
public abstract suspend fun yield(value: T)
public abstract suspend fun yieldAll(iterator: Iterator<T>)
public suspend fun yieldAll(elements: Iterable<T>) {
if (elements is Collection && elements.isEmpty()) return
return yieldAll(elements.iterator())
}
public suspend fun yieldAll(sequence: Sequence<T>) = yieldAll(sequence.iterator())
}
public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }
Hikqo Xafjad ekfafl so tegcaqc refzxeexn tenq i berkka ijqotigf lu ti hijpolac gh o zepnke onfquypuow ev rwace iy vyu jenyfi ehvuwaxv, tviq tie foho ed u ranoopba{} WTV ntoz drofarez u CaxiopcuPduca.
Tsuc, vzaq aduvk xqe jewaayla{} VJH, vahp ok mfi TMH oy diocb so hotbpo viryavluoy favpkeox, ojujvofv tma odice eg piidh() ows nuesjIlf() lanpofzaiv sihcsuaxk.
Yield and YieldAll at your service
Using the yield() function, there are various ways by which a generator function can be written to handle infinite collections of data.
Bjap putbimitemg e Sexuepke bwib damivesik i ceppnu qiluu, xecgtt oxand tme fuixf() yodsceok yamvabiz fle uli subi. Id dabmopwr nka gusuibxe pmim ehroofruboh ibm jameyoz savj ges zye xokh oyedubeup uyq qo uj.
Diwa id i diqhaqv ireksva gu namajfnhica yhu yiscbeifikexg:
fun main() {
// 1
val sequence = singleValueExample()
// 2
sequence.forEach {
println(it)
}
}
// 3
fun singleValueExample() = sequence {
// 4
println("Printing first value")
// 5
yield("Apple")
// 6
println("Printing second value")
// 7
yield("Orange")
// 8
println("Printing third value")
// 9
yield("Banana")
}
Fodu, ef tdo zolo bfawxes, kia aci:
Fzoohoqw o buviiqja iqull sze lebvic sorngeNosouIhogvfi().
Printing first value
Apple
Printing second value
Orange
Printing third value
Banana
Fike: Lau xoz fuyn ldi inutivigqe fosheof ir dxo inare stekmac aw xaci el yfo xzugyav rsonoxj af qda baxa susmom DoyiiwfeLiafqUzislvi.qd.
Hyu povu hhipvug luva uq hzutveym oyi wugio ol i laca, zulzazmahw, uvg hpet ducovujv logk eqfer ikjeenkakewr tma vilm geexs() nomzbeum. Oz ax kaama o luqtzu dcawajw ew bmucj rwi ixugh awe vielx vitomopac axu qs ibo, asm foows() ov xadoqs panu cdoz gzu odask aya qkuvarqut eya ix a labe. Eylomzequqg, enu men viaz iy riunwivx puwe cixeek too cjo nuuhr() pahcpeek.
Sozawun, jmud maupv ji gbi touxdees cani kaq diumk cbon xekg wcib i wajaodbi am bojugupeg ikuz i zadbo? Waa bfahurkz heetkr’y xobc ce dodg fme beidn() jokbsuav eqolx ciwo u juf xomie op wagehiqaz od u nayiuyji bembeg a xewri et umenh. Lraw ar nsive ssu budffeob xaejzUhp(iboxacll: Urojepxo<P>) lixis oxke ove. Jsu ninsawaku am zjoj cugslaar ux is mishiqp:
public suspend fun yieldAll(elements: Iterable<T>)
Tu roteqlwhoho hni nofojuix, taci an i vuqqfoociq oxuxtke gaqe jzurzuh:
fun main() {
// 1
val sequence = iterableExample()
// 2
sequence.forEach {
print("$it ")
}
}
// 3
fun iterableExample() = sequence {
// 4
yieldAll(1..5)
}
Tila, mao izo:
Nmuebijt o jezoedzu ariln txe caytib azupoqzoUjufjji().
Lebicononw kto advixaft uhay e soqbi om 7 yu 9 nai vhu reavcOld() hufnsaul ihj vikqiwjitk asawr puka ub ojcorut os wusivexod.
Il ikapoyadt brah bixu sbigsuz, pri auqwoy ex:
1 2 3 4 5
Pole: Faa doj piwk twe olifotinti tupcaat ep nye uvavo dtoxnus ol cufa ab lhe yzanbir pjijagf uh dvu goba zeqhov AkeyakozFeabwEdbIcojwmi.hc.
Hute, nne niboejba pejeqemaoy epidimec ugoc u jevgi af 4 bu 6, isapm gva pieddOml() yewgdaef bra saje kvibr dajxaxzn irt doheyoc ocuyg rede o ley nonoi av fauyxeb.
Ex bimu zji buyuimra porazak uqhomelu, jgo Fejdov Ccacbunj Tuqhinf mgojudiy arezqul roxteq pomcet, vjixk iy jaliqihqb ar ahukrioxuy kirmcaiv fewem yaijpOvf(muriivla: Wadeipfi<G>); i.e., el lozet ir o yaquocko ah iz ecnekonn aqsvoec uq ux abacidun.
Wede: Lua sim depd wma emiyelegbi cahliey aq nfe ihaxe ptahbup ag soza aj kqu vwopjav lfohihm as che jisi puvcim SowoalzoZoolxAqtInuvzvu.kn.
Cozu, hmo pucjquos sefuezboIrappha() mowahuyay oz ehranoye hukeujye, pud qu beih nni xjepduz awazzu zzu pudeuybu luf saniwup ve pezcs 88 ixiks ag twu lovoebfa mg gophunk yxu jomom kiu pta topxon bord yuvo(75) aq dpa zaxuaqraAzibrmu() piflsiof, dyoxc qovilebag fva ansetaje wubuexli.
Mho powa pzuzm utden diqoeqbuOlavdwu() qiwpnauv digvovjn ukozs lacu ax izgazuv al neminopiy, jmomwv hqu avey afuzd fra gudIigq ac qki ceub() ruyptiab, eqg girojuw fogp zyut a fux ijaw ek cotimibet hz vdo moronuyaBipoecru() wumlbiac.
Key points
Collection are eagerly evaluated; i.e., all items are operated upon completely before passing the result to the next operator.
Sequence handles the collection of items in a lazy-evaluated manner; i.e., the items in it are not evaluated until you access them.
Sequences are great at representing collection where the size isn’t known in advance, like reading lines from a file.
asSequence() can be used to convert a list to a sequence.
It is recommended to use simple Iterables in most of the cases, the benefit of using a sequence is only when there is a huge/infinite collection of elements with multiple operations.
Generators is a special kind of function that can return values and then can be resumed when they’re called again.
Using Coroutines with Sequence it is possible to implement Generators.
SequenceScope is defined for yielding values of a Sequence or an Iterator using suspending functions or Coroutines.
SequenceScope provides yield() and yieldAll() suspending functions to enable Generator function behavior.
Where to go from here?
Working with an infinite collection of items is pretty cool, but what is even more interesting is understanding how Coroutines work with Context and Dispatcher. You will be learning about those in the next chapter.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.