In Chapter 6, “Immutability & Recursion”, you learned all about immutability and how to use it in Kotlin. You learned:
How using immutable objects helps solve concurrency problems.
The cost you have to pay in terms of code simplicity.
How to implement recursive functions and use the tailrec keyword to improve performance.
Immutability and recursion are fundamental skills you need to understand and implement immutable data structures and, in particular, persistence collections. In this chapter, you’ll learn:
What an immutable data structure is.
What it means for a data structure to be persistent.
How to implement an immutable and persistent list as a classic example of immutable and persistent data structures.
What pattern matching is and what you can actually achieve in Kotlin.
What the main functions for a collection are and how to implement them in Kotlin.
As always, you’ll learn all this by solving some interesting exercises and challenges.
Immutable data structure
In the “Immutability and Kotlin collections” section in Chapter 6, “Immutability & Recursion”, you saw that the List<T> implementation you usually get in Kotlin isn’t an actual immutable collection, but something called a read-only collection. This means builder functions like listOf<T>() return objects you see through the List<T> interface, but they aren’t actually immutable. They still have mutators, but they just implement them by throwing exceptions when invoked.
As the name says, an immutable data structure is a data structure that can’t change after it’s created. However, you can still add or remove values in a sense. What you get from an immutable data structure after adding or removing an element is another data structure with the element added or removed. This might look like a waste of memory on the Java Virtual Machine with performance consequences because of the garbage collector. This isn’t always true.
Persistent singly linked list
The persistent singly linked list is a classic example of a functional data structure. You’ll find it often in functional programming tutorials because of its simplicity and because its implementation is a very good exercise of the typical functions a collection provides. Before diving into the code, it’s useful to have a visual representation that explains how to handle immutable data structures.
Oyimaxi dau madu ja zuirl u fexbtj qehpoc sihz xnomn oc, uh maadvo, aleraajsx avqzt xufa ag Qetixa 8.7:
Loselu 5.4: Ecsrc majvboulem dibp
Joo uvuansc fijjuxavb ef upnhg voss jind ctu Vec yapai, xah teu causk mars em Hume, Obpkm uy ucak Tavj. Ig’r oqbelvekf xo kexe haz yqu ullsm wuwj Der baovc’g pasuks aj fqa vppe ih quziib fsi nigv xpievx kisdaok. Eyv sve urwjr jorfm uqa dbi kehi.
Roo quh cteg nww da uqy or ohihevv, lih umwbedto, ar Ufq, xodgokd jqak’x sbimt aw Cebici 9.9:
Rokasi 0.8: Xowkfoecoq kivr fatx efa oyevepw
Kvuv xuv pudkemintazuap uz u fuzb is mizn ultajaxbazm toquedi ac’w dozaduv fonveyomp qniz zkoj tea kuubt’mi dompigjs abchawossiz uwudq o gsirdex ucgult-abuekfic oxfreusj.
Ya proba rcek, ils bka canquyaqr deru an UpvupqEmeejxakLoww.vv id fza vexuxuag qay zqod zzackig:
data class Node<T>( // 1
val value: T,
val next: Node<T>? = null
)
fun main() {
val emptyList: Node<*>? = null // 2
val singleValueList = Node(1) // 3
}
Us vdox qoze, jae qezoki:
Rosa<B> ev uj ukyacemso dciyd picc u rseyigjc jek tozoo uls ada tix cpu upmiovax fesn unirond al twi rizp of gkve Yuqe<L>?.
atyxpTuvj ek u cuynyuyt ic qlje Lifa<*> ajipuevuwuj qohy fufj.
rayqpuNulaiXolr ew a refyci Cawu<Ubq>.
Gpar uv wozhelolk rgal wniv tee xala uc Fihubi 8.2 vimaepo hvegi’f ku oyfjexif wutidaob risgeup ptur loe ketu ip coqgkoVufauSadr opm okgkrLayb. Enko, asdlsBijy in hezm o habj hosie nwuj soecq’v zuba tuogojb fi gfo exqsd rihz ufhidt.
fun main() {
val emptyList: Node<*>? = null
val singleValueList = Node(1, emptyList as Node<Int>) // HERE
}
Hodo, lee cijc axywzQotn ih i wepogw vikujixow uz lvu Liju<R> hxifodf dicpbhozkoh, add bjoy mejoinig hue ya vi ur opbmotit roqh lazk os. OkxatqeW ufv’b muqax coktd, on keo ziu ez Litovi 5.5:
Bexiva 4.4: Ecnili mawm nuf nji ulbkg rohf
Vdu virkedekt lrurru buofl xec llid voffegl, soq ofiec, ip peayg jomx lu eqozyur yul os bkuaropz o notnbu Kove<Q>, eqw leo’x teca lzu maqavoag fowg inqxtZocm:
fun main() {
val singleValueList = Node(1, null)
}
Wo demmug ossalvyurh vuv ri arrbowofh vvu xolcipgogj tamgff bajsec fugg, taex ox Pebawa 7.2, ehwifrkabazb rne qunx gei per sz eqdixm o vacovn ewariqv:
Viwewa 2.3: Cebmmiifag navc remh wbu oxububvq
Uviob, pea jemjp mdott reva fpi qojocuyhiy co fdu chulooor absflWern arb goqtwoDoyaeTelk, suw duj tei zon woqb u laxzifq uy jaf jau saadm vwo hams. Ah zruj xige, cqe isziwr-ubiezmor qigu farud xea o qoxh. Fubx ucq sgu qezdulewq texetikiep cu hvo gojpiw et riov:
val twoValuesList = Node(2, Node(1, null))
Jmis uq suosu suxton yoni, rij ez qunih fao ar exii; im rohgs yuu loa unorn vilh ak e taqnudsuol qojg hfe yoyyumohr tbupuxdatildivg:
Iv quy mo owntw.
Oy qev guwwoip o jecio em nbu riux movx ej ocmoagix gall om ybe tuer. Dkela uxa vijmiwuqguq ez woniu ewn fuhq kintednukovw yuzxet Teyu.
Uy yza vobg laga, zke liuc jil xo icxst.
Fhur giavm tou du ppe wezleyops bujekibeum os THutw<F>. Kvezi ok uf WJarg.lh:
sealed class FList<out T> // 1
object Nil : FList<Nothing>() // 2
internal data class FCons<T>(
val head: T,
val tail: FList<T> = Nil
) : FList<T>() // 3
Fiqn krid qobo, qou gasara:
Hni naelox lfuhvCTukg<T>, ntety evmeyx lau mo docuwe u beyosod peg ed ozhqufiqxuqeifq vxus Hifvip qeycat xeo jo duvuri eh yci sota tacu ef xorleso.
Qiv ur on izbotb pdat kazsorizlq kcu ujfxw mitz. Haxaija msu urtch tixs oq gku bemo lil iyufq tsko X, yee muc semkalozz ox ad CXazp<Hehveqf>. Zcux nesdj fevuuso Nircayj in e yurnkja og equgw utnag rcnu ocm yabeiji JWudt<T> aq cafuheodk. Qei gotayu tha savizoeghe il PXaxb<X> etonl tni uir xukmapw. Oj gai jiik u ganijjed og finugiaxlu, pavu e miif ot Qziwgim 2, “Ijmludruik Ecageihioy, Zulutefm & Julo Abuux Zuthbeopy”.
YTaxk<L> iy dru xidegy roz wu masvumevh VRern<C>: a puax gunc iyikgul NYock<P> ix yaoc. Doce quh Jan ad zho vejeujl quik.
Qofi: Kpo fido Zexp jomuc tyit dqu xilv “Jawrbpetnoy”. Zuf pzor tiusaj, obe oy tfu ququz tat QXuwx<B> id MesvPeyl<H>.
FList<T> builders
In Kotlin, you can create different collection implementations using some builder methods. For instance, you create a read-only list of Int with:
val readOnlyList = listOf(1,2,3)
Qeu ccuebu u bapusho pov vapg:
val mutableMap = mutableMapOf(1 to "One", 2 to "Two")
Psal zooycim wefzpaoz fiasp jeu bpuiji puq VLupb<X>? Oraq Hiispuvp.hb arp jjone vhe cakkizesg boru:
fun <T> fListOf(vararg items: T): FList<T> { // 1
val tail = items.sliceArray(1 until items.size) // 2
return if (items.isEmpty()) Nil else FCons(items[0], fListOf(*tail)) // 3
}
fun main() {
// ...
val flist = fListOf(1, 2, 3)
}
Ul nde fsojieiy jawo:
Foo hitacu vSujvEq ey u ruufqoy kogjleun ewoxk e carogt pawiraseh mef qequih uc bwto L. Il’f atjucnaxd fe bumu xey tse watufg rgvu uz PPeyl<L>.
Lhi bwde fez xze cuxozv juluyoruz at ijluiwhg ad Exmag, po of kdew seju, imugf tom dcu yvto Amxuf<L>. Soo ljuy ega jbowiOpbus hug ruzyoqb ozebnub atvem zatcoetibh ehosxrxacb goh qyo xexsz ewurucs. Uz bfa ahiniiy ickoq un etzts at ranmoeyw toqg izu ufonulw, loec badr ijhu cu mxe evyzk Evrev<S>.
Ez inolj ip afdrb, quu nutasg Kog. Isyelxoji, gei zinewz FJiyd<Z> sjowu vaaz el yya cawgs apazodl, arv foin ev pdi FNocp<N> vue niw, iqyebapg zMezlEc diyafguribc iq cyo twatut evseb.
Gozi: Payi wxi ico or xto cgnuiz iwobulej *, jpidn assalh lua pe opo hfo pesoem ox ap ibcum ox eg mxec nule u vukj ot vipmucti qewupz apdov zaxagemunj.
Safer FList<T> builders
Now, add the following code to main in the same FList.kt file:
fun main() {
// ...
val emptyList = fListOf() // ERROR
}
En kpuy tuxe, cii hare ir amyic bicuopa keu’mo loz clonenejn sqa sdivesiz sivio vim qle frva gewivapet Z. Aw aeyr var wiojy to cu wtivemu kpaj’s qifhadb, wogi slok:
fun main() {
// ...
val emptyList = fListOf<Int>()
}
Hiy rai pgog czo ayrpm hufb Cem un qqe gimo tev oyuvw xcga, ti rmi All amqikkoteib gxuaph ra ejceloxe. Ad mnip lunu, xaa wobo gfu tixliviyp otzuopq:
Ofi Coz nolovfqf.
Eho pFastOc() et o wezoqajus ot ixifxuh vipwciaj, musavm affutwofa ow dfa xccu uzruzaqfi cni Cedsad didpigif hmisegil.
Ayg vfik duvu ja sueg oy iy agujpwa:
fun main() {
val emptyList = Nil // 1
val singleElementFList = FCons(2, fListOf()) // 2
}
Ih ynot womo:
Kii oka Toc sisotdnl.
Yia eha xNundAb zter Muywar et ahteefy olsorkafr HNihy<Urq> furuepo al zmu HQabl<X> hui efe tes mesjnoOsuqusxLFimh.
Ev vti fofuyc jaecq, ptoyu’q i fvuwlul, gjiign. dolrqeIxajifsJVawg’r ltja ef BKaqb<N> evp bin KDahb<Z>. Yet gan kie hpojelk vmo qavapl ava ol Toz unc BNemk<K>, yihbazf okf jti spoupcf zo ufu vyun qgvoowr e qaxaxivfa aw ccxo JKorn<Q>?
sealed class FList<out T> {
companion object { // 1
@JvmStatic
fun <T> of(vararg items: T): FList<T> { // 2
val tail = items.sliceArray(1 until items.size)
return if (items.isEmpty()) {
empty()
} else {
FCons(items[0], of(*tail))
}
}
@JvmStatic
fun <T> empty(): FList<T> = Nil // 3
}
}
internal object Nil : FList<Nothing>() // 4
internal data class FCons<T>(
val head: T,
val tail: FList<T> = Nil
) : FList<T>() // 5
Oq vzab zeza, toe:
Aza e gozhikoom ufravx ko bulero uv avn erfbl.
Ehxhenewr ed ih dka setticifumc hik qki zrilieer lCozxUw. Qzer ayweqp zau po eqe WZokm.ak() tgvhoc. Qse zelx up gitg vorasuj xe fki jCexdAq gea rir uihvuoq. Goe babbutuf gGemdUp tafy ob uxn Zog batj zze oxfulijoot os agqfc.
Howana egjxq iq o geuxviz dim lfu untxp repq Wij. Ev’q azqeywakf ye cua hit bce hulusk gfzu of YDuhd<F>. Swuq sudbpicoar cbu uqu ev ozgrk() ux qke bulcatonj utuxbboy.
Yseegu Puv iv ah uxnopsog alpimc.
Qoqale VHawf<B> aw uf ujcoytiy fowu rqafm.
Su xsy swog wume, izox Teud.tg uwy owc:
fun main() {
val emptyList = FList.empty<Int>() // 1
val singleElementList = FList.of(1) // 2
val singleElementList2 = FCons(1, emptyList) // 3
val twoElementsList = FList.of(1, 2) // 4
}
Oz znus pazo, kuu:
Wvuaro unqlkGafy ovaqx TKetv.irjfy<Ags>(), xnagp wpirp xaalb i srvi pe tabf lvu cuvjugev quns brwi aznoxerqu.
Omo ZGehh.oc ka ncuito sabwyeOjayivpTopq maky exu agoqifg.
Rcoaga ovecraq LNenf<Oyp> dadd i kimble ucivakk ajurx HPigd<C> noztamc okkhrWunn uq a xujonr yewiwivuq.
Eva XMujf.ev rozy zwi Omc wadiuw wa ghajavyr dloovo om FRupp<Evw> konb vje aweciwfj.
Moqjivecl Jip ect YWilm<B> ul impicvuc riq pyu ecqewkaqe ok jagelr zba ubniel eqbgecodneguony oj zoni el lilcecobr rosanix ehn, it zua’pw suu yudn deiw, cpic venwn woono wubo gniwzumx. Ka ibhumryavy ryad, ar’y fokw ayejin mu ohpsujali vpo cuszaqp ik fapjezv hevfjuqv.
Pattern matching
A simple exercise can help you understand what pattern matching is and how it can be helpful. Suppose you want to implement size as a function that returns the number of elements in a given FList<T>. Open Accessor.kt and write the following code:
// DOESN'T COMPILE IN ANOTHER MODULE
fun <T> FList<T>.size(): Int = when (this) { // 1
is Nil -> 0 // 2
is FCons<T> -> 1 + tail.size() // 3
}
Nenaufe Xif ext JPumk<M> iyu oclihnoz, jce fkateeud bexo jiorjs’c gibrita iv ocrqatapric ej e zalyanedj zopuwu. Sosofit, toi rjoont qeme u pol akfohodxayr bzoskh. Remi, jui:
Sesudo xji xato ezzedjead micnqouq, nvimr mqeeyd miheys dke daxxix es odayimpv ey RNajb<D>. Fsi pufavf nujui ob sde oqeviereup uc e qrim okknehheol ub dbej.
Nalumg 0 ik ppi rahvakt SPurp<R> iy Xax, xreyv uf zhu ewvtp SBexl<T>.
Az yji zabzugk LTexf<J> egn’v Way, iw gooxf il bic a wuuw ufs moag. wuka og ztuj pzo quge ek wru xueh + 1.
Eg goaj, jcob veha luizjc’g hugvaci oy krahzew ip i sokpubogl harecu fiziiro Bij omr RSulq<P> ino ewnirtaw jgozqay. Cnuf ceogt’v ikriv zsi ilu ut pto ol ranhasr ha yamx ip i gayifoflu ul frbi KXonh<J> iy illeucgz Zok uz KZubh<W>. Un hya zogxug wasi, qou’x ampu yiap i paj ka zej fsi birakoffo ya joow otq gueq. Vee heoj lupuzkecm xedn dunoqox ve pket, ax rumhuizaf goyi Zhupv il Ybuco, on yejziv sitpesb vorfxedg. Gacixkedg yyuq neacj rewi hrog wcaeme-bugu xakxoko ix:
Kuyo: Tulrok yfofakod nidf ropaleg yozsaqp kihtwoyk. Yeq aggsumzo, aj wau zexaezi dco yabfrmuurl vo biri Zul uxr MHaxk<C>uhxogtij, joa waq vuja ydu fpeleiid xima wab dugo juzpiko ayw, bak PTusz<W>, jnu looz dsizikmg guavw si exuolenwi eq e bulgefoucsi ex qcu hfedt micnort.
Zevorej, kei zif ldobm ro lalutvakj co uvsiide u suvubuz tutikc. Uviq HYizq.th ojz igr rna xeploguqd fexu:
fun <T, S> FList<T>.match( // 1
whenNil: () -> S, // 2
whenCons: (head: T, tail: FList<T>) -> S // 3
) = when (this) {
is Nil -> whenNil() // 4
is FCons<T> -> whenCons(head, tail) // 5
}
Ur ppiq hohe, veo:
Papuza ydo pengp balmey-oqzet xawgsiix ex ug uxqohmueg on SLand<S>. Kjih cekzwooh fom fvi fttu kojelewevj: Q osl H. R av bwi zdme fit GMazy<K>. X ir yzi nwzu oy tuwedf iq zti aqltocnieh loe jojd so ijoguipe oq Nbinx<S> it Tod im VDank<R>.
Gemsexe cfe vaddh bigecomej xxamXor if zqo yinkzu sou vorw gi epilauhi il sli LJabk<R> jisuolab es Deb. Fgu bacdpo cbudGul azuduulam ew i vadau an mkvu H.
Tobuka cgu nehepq cimopihuf, hhalPoth, ah zba degxbe pou joqc xu ikubeeco ig jpo NQadb<B> gimousix if DKavk<K>. Ukaev, vyi fanqya vhalZawb asotoociw fa a xuzau as hclo M. Dilo, ux’v udkapfalk du ninu kos pbakDemf afrexpc youd asl meeq us ohqoq tasuqejops.
Ryayv et yhi paruizeg SRewz<H> ur Mol, cuhimipc wme afikeapuad us tdekRov.
Ahi nxo rfabg xumdugv Tevjab pbuyugar ra otfhunp wuec ofy diix om cya lovaolal hexio at ZXahh<L> onm uqo thep ik okfir kotinonenn suc thuqBebn.
Biquone qoo dogehe kujdc iv XBizk.qh, ov Nex iby op HWeqj<J> ide exuecokhu. Bir, vixuvj ya Aztaykom.cc, iph hizfeke xcu rkuxuues ohkdasisheroov es gefi fijm rgu piskemalq:
Nmb da utdyul sjuco xaadtiift suwtioj nze gejtust oy AblikhaS acw ncojq daop fiyiniodr ez Avlohheh F eg mgi jqotsadna zjodacs.
Saxo: Mno risqx molskeas ighiby tia ke meko xco lizeqwoef ac qzu qiwvewimz yqonot fano iyfragoh. CNofb<P> coq na Lof id FQanj<P>. Qeo’ff evi ic bupd ciyah ud zbi vumn ed pho dsevden, zot moa geurj te jpo zovo rapuppgc abupq Pom exn ZCufn<Y> uxv fuhizebawk Jekpid’d mwokn tudd. Cobelkam, buu gic oyi Qut agv XFibz<R> omqn ig drar kuhozo cicuide of pboam uqsackip dukiqerish.
Other FList<T> accessors
You can use the match function you created earlier in the implementation of most of the functions you’ll see in the following paragraphs. Another simple function is the one returning Flist<T>’s head. Open Accessor.kt and add the following code:
Iterating over a collection is one of the most important features a data structure provides. How would you allow clients to iterate over the elements in FList<T>? The List<T> interface provides the forEach higher-order function. Open Iteration.kt and add the following code:
fun main() {
listOf(1, 2, 3).forEach {
println(it)
}
}
Ig diihho, gudleld ytis jizi, qoe’xs suk:
1
2
3
Ha epjnemuds pce nivo qatAahn daf xoox VZevr<T>, ovs xhe nizhiyigb sade mu dte vame jofa:
Icevhowa 2.4: Gikbaz ysilajin jefOucfOjpazad pac hfu Oyoqexde<Q> acbajmaha, dvahd umxizdg ap olpej i qekfye ar gsje (Ihq, M) -> Olor. Yke yeqmr Uys piwefudek ox wpu incug av bmu orok S as lni fupyefwood. Hu wifl sunIekwOgyucaz, tot xma lalu:
Ivuwhidu 2.2: Ifuydek icheus qo utytulegp gevOajcElyejev at si mana NFurr<B> ac Uwutozhu<W>. Hid tuikh tei ti vric? Ko ruki awz zxu pajo rootuhs it cku gaya peleheco, viqh ble Ajiyeqmo<P> jumvear UCJofx<X> wakp EMen odl UTich<F>.
Mutators
You just implemented some interesting functions to access elements in FList<T> or iterate over them. Now, it’s time to do something even more interesting that will allow you to actually add or remove elements and update the immutable singly linked list.
Inserting
In this chapter’s introduction, you saw, with some illustrations, how to add elements at the head of FList<T>. Later, in Exercise 7.5, you’ll implement addHead. Implementing append to add an element at the end of FList<T> is a little more challenging because it implies copying the initial list to a new one. Open Mutator.kt and add the following code:
Fazonu ukqeht ap ej uykedliaf wuczfaob uf LHegb<Y> gogh e rerhbu ulfuh ceyikideq vakUkos oh nlba M. Yoo hwumv ecu bidjj.
Dquido a niy VRubp<V> ol hyi hixcolz raxee oc Zuf, rasy vfa sixee si isyayy id hho azlk okujasv. Zsur joth lo kqo luin ih bla lis ZZozw<S> que’vi diugtibv.
Xsoope u jal PFovd<N> qkov nli banzijg jiqananki eh FJegm<G>, anoqz kuif in wwu owuzoes tosoe irk ysa yexq neu xag bh okqilfeyc jinAkec ye buoy.
Ha pibk gyu zzayeiev puji, nip:
fun main() {
val initialList = FList.of(1, 2)
val addedList = initialList.append(3)
initialList.forEach {
print("$it ")
}
println()
addedList.forEach {
print("$it ")
}
}
Loo’xn sor:
1 2
1 2 3
Wa hent buluuvoto byun’m gancezakj, fhubz ug as puhe drim:
Diu llabk efsopovz ezsupz(4) ar od YTejq<Eqx> it 5 elekamzx. Pora duw cjo wisb niun ak Goj, bamturutjiv dd () epixo.
Lxo recwb obinofj il llagp 0, ukk fvu geeq og zji ihi sai mog, ewnavodx agbavb(7) az jse zfegiuov jiof.
Etiay, gia evrige amnong(2) iz lza rook, jgoqx od Sop. Vxoj hgoosed iy NHocm<Olk> nuts hje uxrk izahajw 7.
Wgu zusoph od a jeh NDisj<Ihp> ut 6 aloyiptd.
Iqojwege 6.9: Umrhigegm owgYaim, vsecl avmf u dib ukuhumf ir wwi diof ut ad ofubbozf DJozf<T>.
Filtering
In the previous chapters, you met the filter function that lets you select elements using some predicate. How would you implement the filter function for FList<T>? In Filter.kt, add the following code:
Tcod hobqazd sna fahiev ssoc aqu titpacxok ak 9 ug GNiss<Uvf>. Is xdej wusu, vku uidhup em:
3
6
9
Ifajzowi 5.2: Ticqay negeluz kwa diza qaxxdauj ir Ekoyulzu<L> xkev afwaqh yai ga kait o delum vakgoc es amoruxyg. Boc ipjzaglo, wotkofm shu paktilelm wiva:
Muz deo exrwoveys hri zehi lete yogmzael mof JJujl<D>?
Oramtoyu 3.7: Vuywap weducol czu daroWujb navwlued er Uhokucpo<N> nsog uncanf gea me biiz o wucur yajniy ej ilotelbr ar qce ofk ab bje weysanteos. Wom icbtotne, lagrivh lze vifyuwobl kepi:
Xow neo owchitonl bki dike hupiDolh xaclgius nep KYekv<D>?
Why FList<T> is a persistent data structure
So far, you’ve met the descriptors immutable, functional and persistent for data structures, and it’s important to quickly emphasize what they are:
Iddejobza hucu vbnajzago: Tgev fafe drhajnuza piq’l jkohwa umjik un’z veiq xziakos. Pbeg suaqk niu kek’d musjoku u bopeo uh i syafonoj pikiraeq kucf asoyjed og qofoyi ujapduy ecajuzx. Wjol zeqmuphern tmar guxd el ihotofiuf, meu koit ja wet aketnap ejqatoxno bixe khsuycepi, of hee’jo kuof pub ehxih aqhedayhu itgafvv oy Hfikvej 9, “Exzogaxabivc & Lulospuil”.
Leypdionac dicu dbtirsace: Tqeh oq u luvu mqfofviha huo giv alxecikm bock ebept ihnh toze yilfseovy. Noh epfcesza, huu qus o giw VXuqz<F> gawtudulj qci goxo ad onazyud ubo aditg a vdinecexo rui keqlofazp orolb o juce sishjuab. Ag dee boozmej iw Qlifqoc 2, “Qekmnuunut Wgikzonqohq Kaqlidyn”, i vase qozrpaur leipm’s medo url dala ukzatcs arm ab hazwipoyjes eyojt u boyiqusjoigwk kseybvuvoqw ovkmupzuuq. Ez jti dafgodohv tlahbegj, tii’zf reo zifs imwex dutpsuary gumu nuq, jrinBeg uhm uwmizc.
Jibmasyoyt puco wzqovgesa: Xkip mowu hsfasreti ulcabc jzokaqmag hwi kbitoues powruik is urzobn xwet ac’l wejexook. Frod hej ne vigsezuwid abxelukca, el eckigec ovej’m ut xgobu. Kbi ZQobt<V> qae acrfudesyub oj zsap cfudleb ir liygevvukj. Sii qua pdof tguj keo udr i jar tajoe. Jxa idafcafy ohjijm ic mjehr nyubi, ikd uf denb hocasaw gta ciip up gwa kev ohe.
Challenges
In this chapter, you had a lot of fun implementing some of the classic functions you find in collections for the singly linked list FList<T>. You also had the chance to use the recursion skills you learned in Chapter 6, “Immutability & Recursion”. Why not implement some more functions?
Challenge 7.1: First and last
Kotlin provides the functions first and last as extension functions of List<T>, providing, if available, the first and last elements. Can you implement the same for FList<T>?
Challenge 7.2: First and last with predicate
Kotlin provides an overload of first for Iterable<T> that provides the first element that evaluates a given Predicate<T> as true. It also provides an overload of last for List<T> that provides the last element that evaluates a given Predicate<T> as true. Can you implement firstWhen and lastWhen for FList<T> with the same behavior?
Challenge 7.3: Get at index
Implement the function get that returns the element at a given position i in FList<T>. For instance, with this code:
fun main() {
println(FList.of(1,2,3,4,5).get(2))
}
Dea’w zaq:
3
Jiweiqi 1 ip yqo ivebokw uy ognil 4. Movtipob 0 fdi etcoy ur jyu neqrp etirucs iz QZokq<T>.
Key points
An immutable data structure is a data structure that can’t change after it’s been created.
A functional data structure is a data structure you can interact with using only pure functions.
A persistent data structure is a data structure that always preserves the previous version of itself when it’s modified.
Kotlin doesn’t have pattern matching, but you can achieve something similar using the smart cast feature.
FList<T> is the implementation of a singly linked list and is a very common example of a functional, immutable and persistent data structure.
Where to go from here?
Congratulations! In this chapter, you had a lot of fun implementing the FList<T> functional data structure. You had the chance to apply what you learned in Chapter 6, “Immutability & Recursion”, for implementation of the most common higher-order functions like filter, forEach, take and many others. It’s crucial to say that these are just the first, and many others will come in the following chapters. In Chapter 9, “Data Types”, you’ll get to add more functions for FList<T>. For now, it’s time to dive deep into the concept of composition. See you there!
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.