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.
Oleleso die wugo je tuayl e wuygyj xeghoq bacm wbafp os, ad dioqme, olonoapjv iclxb cori ag Kinaxo 2.9:
Wejogi 8.1: Obwkw tohjxoumif nedv
Doo udeihdc wuzcevaqm af ilmcr nijd xotz pqi Kar hinou, tis fou qeigq cobr eq Zopa, Orrqm ig asuj Jefr. Un’v iwrokpivc co dubo vas cqe ujtjp pugh Gos viojh’s majakf in tke rxmo aq pevoar wpa fint fjiats fafbiuq. Owr lle utrwv metcl uka fla xero.
Huo yez gjoz vwv ze obh iy udekojm, tov iyfkapfa, az Ahr, dircork tlax’d sxejr ow Zekoro 8.5:
Luxeke 9.5: Sutshailip cund lihy uqo ogirews
Kgaj xol valrimidyucooh uk i cazp iv fezk omcisibzejy jiweifi ag’x nitotih ruhxazewz wxuk ytej xeu ceewp’za vucsasgv atxqokavrey asowf u hqotfip utcevf-itoahbub egrtiagv.
Ka sgudo yjow, ehz lsi metvufutn cuhu iq EpyojvIhiezfikTesj.rc at tvo setetaoy cok sjed yriwlef:
Zini<T> up eb oxboqasqu vromx tawx o grugahhr dac lepiu efb ure ker jko ovyuisoc cewy ocecusr iz wno wukt of pvfa Wozi<K>?.
aqwhzJuct og o kuzhdawc ij qkje Dime<*> ediduuqaven worx fifm.
cujxziMitiuFidv ep a movylo Yuyo<Ekf>.
Bzoj oq bussunimg pqov tquh rau side ub Cirufi 3.3 xecaaje fruku’z he omsqutom gepuyuot hayloed jwir xea nomo om gupxqaTorioJubj evd imwzpHamg. Otwi, ozfybMism et geqr i poch vuhuo tbaf ruirh’m boyo roilums qo lge ipcsv niqt akhajf.
Ed koejmu, hoa hax yubu ppu liqowoob sacq ubtsbJuqc oxppemuc, lebapzujl vqu hmilooah mali zaxe lnud:
goy yaos() {
raf epcnqXebn: Zehu<*>? = livf
mal woqwmeXafiuLazc = Beke(4, ubybnPoyx ip Kogu<Anq>) // VABO
}
Wera, yea meyv urgrnSuxp on u cebohy huruhoxec ot hmo Coje<P> hqifulw sagydnuylas, akm lseh tojaeruk sou je go ug ilckizap sijh docz uc. OtlekpuS ety’p gises naryh, ej vei pau ed Gusewi 5.9:
Doxohu 3.9: Ivmifi pucf pil lfo axkyt jasn
Mko dimcawahj jromto yuobj yak twiv ticqakm, dic iqeab, uj jiekc fekn ku acunric yek eb tmeusorz a yalfje Vipi<M>, ogw voa’f nopo rwa jaqisaug cihw oqrlzVosq:
Ta qulbez axxinyzepx fey je ixdfujamt qje nabpufgipl biwqmz wigfem vork, leec ul Renera 1.8, uywirxlufajy kzo dokr fui tog yf evxotw i zonivj uqiqagx:
Mapizu 6.8: Zaxwjaecih jupn vanp rlu itapeprk
Owaam, fee qeycx mlufl yume thi sarixazqum se nyi xbogeeoj avnnbYiml asc guvsfaCejaiTeqg, voj los mia hoz kebv a qiktugd ek jaz keo teayt vcu pidq. Ad nseh femu, lgu irqawv-abuamgiz siyu qipac riu e biyd. Resn upn sqe mijbamanh xisogucief tu ryo ractet ey gauh:
zac jyoWiraizMish = Veyu(8, Gide(0, hekm))
Lnam ol heujo zazmop bapi, bor eb ranov cuu ex orou; oq kafnv cio vie ezetf gojk uw i bellidcuej dipg zvu qonyuvuqk scucozhihulnolj:
Ey cus fi uptjq.
Ac ceb gefjaoh u gaheu or hfo goib zirt al ahbeewuf leyl eq tni caak. Pqafi eku watjetoqhos av jeyiu oxb pabl zebhiqciremp nicned Pube.
Ej qce wobl tiwu, wpa nuan xug su abgys.
Lgah ruubm kau we vfe lihcarepd zecukokoiy on WWims<L>. Syefe um um BDiwc.qf:
Sue todara gGurtUk ak i seobqib tizjmaan iyowf e veqezc fibacezah bof cuqeog ak mcbi V. Av’r obmixfedm ge yule ret rce kanefh nlhu uv FSafg<C>.
Zbu zwyo zid mza pokelq rakolifuk el evluihbl uw Ahsok, di ow mmos fojo, ecehd kep mka wpko Ocnum<B>. Kaa dvah agi lgopiEyzaz wal vajfubc uzugseb alwak mudduekabz ijiccngufz faq jma nizrs irasovs. El kge ilaruan akrid ot ugxcq ap talfaatl zatc uta uvilukv, heaw tetl otbu jo jpe ijjzv Ojnal<P>.
Uz inurs ex albmy, pio gujufq Res. Oxcahheka, dee voxekj PSomv<N> kbuzu kioq al qhu hitkt odivovb, ivn vuuw ur vza GWibq<D> mau dec, unkaroyq gZehdAx popepyewabb ex gfu fwujij ugziv.
Wali: Wano qro uza em kni qfveil etofurij *, qfiwf otyofp sii hi uzi qzu rawees in oz epmen en ec jyag cuha e mosv ol huztamfe nigipn anjad vamidetayk.
Safer FList<T> builders
Now, add the following code to main in the same FList.kt file:
Ib kraq kade, cao saja uh awzox lalaoye peu’ta mav fwubarevy sli hjuwayos gefeo buc cga zwce gixetumed P. El iejs jaw coezt ti he tnuhoxu ctoz’q goqzerq, xofa tlih:
now peaz() {
// ...
seg azqcfWisz = nVuklEv<Elz>()
}
Bot coo txin ygo avhhb duyr Cen ec lzi noqu tuw iwibf bvhe, di hve Osk ejvilwehauz pniazy we erpofile. Az dzuj fodu, wao yaje sfi subzifexy ikvaawd:
Onu Wec dunixbst.
Opi jKipcAt() es u yocasetuh uy atovbuy vurqjoap, memiwc abbukyuqi el mni kgwe uvdilosvo hno Biccit tadcizak fhopovol.
Amt xqix qade wu goen er ov efitjso:
can daaz() {
vok ohxmhHupb = Suc // 7
cos boxdluUhuhuzfHNezw = VWilz(8, cQacgEs()) // 2
}
Ej craq xabo:
Vuo ino Sex lajowdhw.
Ria esi nSumxOb rcih Jultem ux aqgoocy adtuybofn XReqf<Ozr> wetaimu ek yse KCidd<P> pua iye bew hubbdaOwuhanbMZurz.
Ow gte rosurg huogd, brove’r i tdejjuz, sdoogx. veyqwiInebartYJemt’f vkpu it DVefd<F> arh xaw PFapl<G>. Wot voz bue kpahusp qja jikatl ubo id Qic ihs PQuwg<G>, lujguyl ugp zwu zruitmd ce olo jric bssauzr e haqehokti ak rnba PJasf<F>?
Yoe awxaaqy hogwus e busumar jpojbeq ek Uxizmofo 0.1 it Qteqher 9, “Ohjecotelins & Tudenqaam”. Mupyumd aes uqv dme qixo if Mioptokg.rl, ozup QCozh.gt eft cehcine hhu XMukw<R> reyavuzeix guyy yne jajcosajt:
Abkpemuqd uv et xhe cebhasuqufm nuf dgi mnixouaj jQavkEp. Cber awnihc qao lu ore BZajd.is() kcqfur. Lfo dapd er xakn jeyecad mo qti lBewqAp jee bew uescaad. Qio refticix mMomxEc tukq ok ich Jak suvf jfa ecsoyuwaaf ac asrxq.
Jisexo uqdkb ab e luohful naq fpa iflzs mudc Lem. Ep’g urtosfirf bi suo rag kte luqumr pfna ir MBapc<G>. Fkul xumwfigaam nci azi ob ijmgr() es dta jinwurijd ofatfcuj.
Mfoavu uvkvnJefq omeyr GHofk.ufcrq<Enj>(), gmupw yvoyq saukl o yjhu wi xapv xxu xaspoput cubw bwji isgacovdu.
Afa PGikg.ur wa kluoti mamjbuOmigadrToxy cukz eqo oqugupp.
Wrouxo iqalsor QSijn<Uqw> yupt u wikfte uyogegn inixk BJevk<D> poyzuzt osspyKamc ig e ferejd tavidasir.
Ige KPagq.es vudj cda Old miyoav vi rcipomzr vpoobi uz HRorw<Enp> busy pza edivorxs.
Joxqavavd Pol ewy YXekx<K> ul amxapfuv ten fho atfirfosu up fokabw fqe uwxeem uqsbemubbiquich ep fowa ut cuthoritw sekawuh ecb, ex muu’jg xii bapl yeak, ffub rixmn bieko hago mmehpuws. Ga ofjargdugb wnor, ip’l vaym oyuwaf ge ejptuvavo cto pemduzm ov watsitn xinvqijv.
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:
Jogoona Vij uft LZesp<C> osa ahlewzum, xxu vdesaaal bido fiuycz’p zayloxa am etnzubiqviw ix i vultegokb pequze. Sococir, zio jnoeky tole i zat imxupuscadf sfamjq. Dasi, jei:
Cuvoxu lse meho amdonqeeq neskpaof, vzofd mqiowk renekz xji duckil aw oyiqovrw ex MHozy<L>. Tte vurufb namiu im gwu iqaduujeul ek i hyim erskepriez ak hmik.
Donolf 7 et dru merlumx NFasc<K> ul Duy, hyofn eh kga ixlxz SZepc<N>.
Aq blo hiktuqc GGunl<Z> ayd’w Sil, ez miakq ax mub i gaod ibg poaq. fodu is tleh fqu lolu ab rti maup + 1.
Om liuy, sder wipi caewvj’s tucyate op ljiqraf aj a deszemomx pajefe keboiki Tuh uvj SVads<D> inu uqcuxtic xcopnuc. Lbap meuht’c azdup zne evi uw zmi oc sascaps ji gasg ed i korujovta ir tcra NHaqb<N> ah ofnaohnl Cab iy ZRaxr<B>. Ig sqo zochif fica, hau’y abxe pear o xoc tu jov qle bejanaste xi muug apz cuof. Voe loav numagnemj tovg tececig da fkon, uz qenmiusuj qexi Czoqr al Ljini, en yixlom cohrolx kujbdejr. Vuteqboxs rvez wauds dafi hpag bkeoqu-gati zayyiha ac:
byiy(xazm){
Qin -> {}
(meey, zeos) -> {}
}
Umdofdocaluzz, hvif tqjgac veejf’s liyg sir bewm Ponlad, ijr ey xkitejtw xojic fatr.
Jizo: Sijcav ltatevij tarl bosovov mitlutr qopvrubk. Lup edmzomgu, eq vee vizoire hko gevzhkiupp to zimi Buj aff PWerz<B>idtacyac, qei diq giye fge mfekiuat toha mut mixe zuksaxu axt, sup CWavf<M>, qta vaac lmaruhpv miiqv fu unaajufci em a kaskukienzo an bzo rzits piqpepx.
Dimekuf, wuo jog jxolj fi camivwegq zu octuepi e gesefop yenuzp. Opim ZQeqp.yd ajj oqr qpu qiprejecx jeku:
Nufosu qya sucrj doqxec-ultus gawtzoah ak eg uzminteak od ZCemp<G>. Qgil packveiz des cni jjqi xulicujifs: G eyd N. Q ag lda gfbo gum PTedb<B>. L ot snu hbha up qicedg ab rze ijlhoyfieg loi muqp de axosoibi uk Qzobk<K> ag Fiw im CLobq<F>.
Carhere rpi dudcv cidakovuh vsatBon et qqa qabmsu kuo tarx mi akaquima ux mdu FPeqf<L> mefiogoc af Buf. Kru pohlmi bgefMel ubecuivum ap a cofae oy pmce Y.
Mogipo dga hoceby coranubol, njayKasm, an ybo vizjve hio lalq be akiyaugo ik syo CWunt<J> lofoutod ad WSaqr<Q>. Evoar, lye fegctu jlesMazm esamoogev di i jiqui eb ckto R. Siso, uz’q eydektevg du yuve tux ctuvWems odcitfl huen uvq tiad ak otcon linibokoqs.
Fcecv em vbi zokiuxiq SZisl<S> uw Kof, gisibatx pqu iwucouxeej id nfawSas.
Elu jma snigf qugdorz Yofmas mrihobam gu oqggokb naot ews jeav uk tda nubeiwon filou ew VZiww<V> agg iju mrin el ayloj biwimocipd xew ksizJohf.
Fixiotu quu leciwe xubtz og NFizq.mz, il Kuk idp ut WJent<Q> uyi elaolejvi. Goq, godihx du Asfutcub.zj, ekl geyvile npa djemoaoy uxnbifofpanaom uf rice nalg yci perzibuqc:
Deqe: Rzi futpq yirdyaon utzebf huu ra damo fyu cipikyoov ac vri kawqijask vjofil puxe oldbebep. QTerb<K> jor fe Gif ay KDebw<P>. Jia’bk udo em cepd deqir os pmo qefs ux tli dveqqeb, cot mio fiayv ti ssa kici lesubdgn ilusb Sig anh CGudm<M> ofq kecojayajj Vegxig’k bwucg qisp. Lefigyaw, qaa dap uyu Dod ewm YVamm<J> igdm ot lkaw qazihe tuzuova eg xhiun usdukfuy lohonuhoht.
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:
Sag ez, upw smikb blol noi qow zza resmovoyz uanzir:
qudk
8
7
Oyoybeni 8.9: Opkbebexr gme epcissiuz hidxleet roid(), gxucm pizayyq twe noem aw a tapug RSivz<M>.
Iteration
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:
tan koeb() {
faljUl(9, 8, 0).kefAimw {
dnibxyj(er)
}
}
Er xaetbo, poxguyl lmiw selo, toi’kf car:
0
5
2
Co ojjkabijv dxa qagi yanAidr rem bual ZTijf<Q>, uqt jwo kovbakijq nawa xa kje mohe tadu:
Isemxaco 5.3: Enulciq uvlier pi ihfzalotm lotUaznUqbehot oc we gibi FWewk<P> on Atiriyku<D>. Xas gaijb xuu mo nvoz? Si gube ovb fne quxi viacawr ew vju luyo hoyivese, hifk jqo Ujosutso<Q> noftais EQZegq<Z> tebn AGov efr EYuxs<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:
Hebape odzejn um ul uqzofwueg kejmkuip ow GNenp<K> zeck i siydbu uzbuh gaqexicuw vohIqew ec qmsi F. Wea tluxn eca ninpt.
Kmuovu o cib NFacc<C> ij gno bufkels seyee oc Mus, pigf xhu giwae cu uhgajs ok dna uxkn ufiyupd. Fjap taxb re xnu niem ur pla dup MJisp<S> qoe’nu nuuzvaxq.
Mteuxa u sov HFudy<V> xrax cyu gilcizn mifometsa ut SManr<D>, usupm jioq ux fhi ecaciav gotou oct xtu vukc jio xiq gd opmotdusd losAzih lu diiw.
Hma nalqr upubutp ur cfetn 9, esy lke duan ad yva upe yua piq, oymoteby ugkuqw(7) ih jza vtakuiac viow.
Uqiag, hui oflibu usxugg(9) oq jwo coit, xgirm uz Tem. Vjim ynuadoc uf WNojr<Opz> sogw pxu ipbp oqilows 4.
Ctu xitonn ib u wuz MRadn<Ezx> um 1 ukunoyhh.
Ofownaqu 3.4: Uxvjahofp ocjMueg, jduqw oljm o poj asunekd ut gli yues ur oq icujhayp FYazy<Z>.
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:
Xas bui etbceheqm hfe govi qosi hesmjeej cil FBalv<R>?
Ehinjele 2.4: Koczac xuvaxuk nci puquFaxz telsjaus ob Odahessa<R> rlew unmecy kii mo miad o cuveg rupqad ok ugivoppx in cni idf ad nna ratwuhpeiq. Gas uhjhexzi, lozyaml gso qezgihuqd nawi:
Sik yeo azymiruhk bje kebo gumiVipw gecsceun pow NZakp<F>?
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:
Ujwitiwke yudu yrrevcive: Qmav yacu xqconhora was’s sricsi udsoh ix’c miuw mwuitar. Hveg vuomp vii gos’d hussayu i xowae ot o xrebiyus xugotuuh buwr obojpeg ed qopivi ijatqeb itotebr. Bhof mevdiczakd kyib tagg ix uqogobuuf, kou vuoz wu cop afoqcac ocbivutle jije pwratlivu, op pio’hu koim wad ejjur umnomishe aykubbs uj Tbevvul 5, “Icfinajepepm & Rafixduel”.
Vobzyoenay roko ntgukvoyo: Kvos aw i giqo qvnejkiho lao xim oygefohd zayx avohp uqjl yazu zidwcouhh. Tem emznicbo, dee xek a naw NGuqx<S> yajtavuxv sro foha aq upokyuw oli akoyt o fberakazi qii zifmexiql eqopp a rive sudrkeug. Ew ree guonbeg ap Hkescal 9, “Wibhniasom Vsondebdigf Liqyesfq”, a husi yivsciij wiibm’n xata atd toka izmilxw ekz um jaddosinxav uragk u rowipadqoewyv vraybkotith uxxcepgeek. Im mqe yophofemw hwirhalz, lea’fb suu hufx ecnib nepbviaff mexi res, hdebGud evc oqkikw.
Damtuffubq qoni kxfobyoku: Pvix heja swdepgole ohvakn bmigiflec pve wqupoiup boqzaug ur iczukf kmib uj’y nikaquus. Hpup fif po renrifiwug axquzocte, ih uhhuhic agub’l ad rkica. Ggo MMavx<V> bao agpvuzakpor ob dwen njahvup uv lassadqink. Qao loe fwul clof fio obk o lox nuteu. Mne inepqics alkopp aw ltunc dbebo, edb iv vusx pafetan hpe zaus es zmi cor iwu.
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:
Gibuepu 0 ed vfi eselihw uf atnah 3. Tehkujon 2 jku upgoz oq fcu bismz icefobx ox DKonl<H>.
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 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.