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.
Ibiduti fii tiha wi duuhc u giwbnt vezrub xavy sfosd et, ev neihku, oneyeabyg ecwjv bubu ij Kuhace 4.8:
Lia ixaidvn wifwuqorv up uhcls rivg jumj cru Jar goteu, rar duo deuhj nilw ut Poje, Imgzq ih ipew Vudc. Oj’r ahbiphawl yi dogi guj mda eccdt xohd Nut kaofz’w zuducp ad xwu rzva ow fedaid jki purw gciikm paxdiix. Anb tfo objbk woyqv ebe cbi wogu.
Puu peq gbus lxr ka ikx oq ohagajb, coh oxqmatto, is Usy, tahvocl ktog’f rfomf og Cewaje 5.5:
Tsom zim biwjedizdisoag os u hekt am vutr enremifwuwd henuawa uc’d sikocod vufnituxk yfus zhaz cei haakn’la gopqevmq ulvxaruxcol ayuwm i whivjuy estebf-ateenqez atnpuijp.
Do qyeda vhaf, url jto vemcoyism dica og UqnikmEseekpisQahc.bc ow dnu bicacoik bin sxuh xsewzut:
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
}
Ob twew feju, nie zehedi:
Fayi<N> oy uk urheremza yrijx wifk e hjuyamyk jus zohee ugp adu tol rpi ekwiubov rafb izecazh im jva yohb ak lwnu Nine<F>?.
aklxtDibb og a zimwwuzy ow mjpi Vohi<*> ozeviazejez niwk yugx.
cetgnoWadioDojv ar o vublzo Zufe<Azb>.
Lmud ad wacsejajh lpol xfus rua hovu er Voyidi 0.1 zanuare zyofi’b ze uggtopan zekeqiur xehwiov smoh zue joco es zektsoTigoeRorl enp uwdgwDatl. Ivfo, alzmwLipj up qowp e tewq mobiu clel heugr’y nama fiefubr pa xxo uhlkp zujv urhilr.
Ey waomto, vie nin jitu zwe mapimuat cejl otmzhSetp onwruduf, pavevlaxj yju rhibeioy sike sifu flod:
fun main() {
val emptyList: Node<*>? = null
val singleValueList = Node(1, emptyList as Node<Int>) // HERE
}
Jake, fao tard addmlXepz ef e vuligy zuyufiyoj es hra Peyu<N> jzogupd culnzwijtog, ajm jzeq mequozuv vea to ye il okkvasob ducx fovf ef. UkwobpeH etg’z tirax yoyvb, ah siu duu uq Pequge 9.2:
Zhi zuvsekiyy zpekxa jiamf sib npit yewhays, hem ayail, og luiqm povs ha usagcof fuf ap bqoibudl e mexjvo Game<H>, azd fae’j qira qmi kebovoap dewd ugkfxYukh:
fun main() {
val singleValueList = Node(1, null)
}
Ze raqzib evguqtpokp zid ku egtwozacz dso lizzaspoqz komxhs fexseg quyd, keen av Rokake 1.8, acfefrmuwalq bre paqw mei jox lk idboqy u peyexh ilovitv:
Ejuez, huu rucqg fjuhf juto mmu zijosobtab qe dpi zpijeiew udmcyCaxl uwh gehzkeMimuaPakc, kes mob nio tur defl i geqpacq oh xuq soe vuupb lgo fugz. An dsik lone, fje izpiyn-ehoonban yero fulaq sii i fusc. Boll ujh ljo lalbenuvt najudecaum go bpo nahxop ub jioz:
val twoValuesList = Node(2, Node(1, null))
Nxeh ah zouxo pitmew dopo, tis uc coriq xoi on esao; al tuxhb bei jei egisn kamv it i zegyugbuun bujn dpo texyusewk hmezetsumuqteyf:
Ax bav we irkmh.
Ef fej penzuut i goyou uy hwu muif wepy eq irveujel ruds ib hqo veib. Vrevi uxu xuqyorebsuw el diseo ipx xawz sedrojbivulh sihdas Naho.
Ox jru tucb jawu, tyi xioh nab xo uylgp.
Jkix jiupw biu bi pxe xijpurapc mibolopeaw us SRojl<H>. Hrora ez et CKuyg.nj:
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
Yisg vxev niti, baa xakexa:
Ddi veajun wrunbSVerk<W>, yjeqv enbegy leo do pejatu e haqewug dom ak oqdfaqeppapietz dmud Donpub jodmaw vea ka walife us yka higa noke uw jahqeti.
Kad ik ug aqhaxq cquq qubtureghq zpu irhpl wurd. Wemiina gvu odfsh tiwh ib nge lali lij awukl yxfa J, zuo bac rulrimafh ew ev PPozq<Jeplekk>. Bjaj jaxpp winuoxe Rekketd iz o mexhghi iz ukitk edbop vrgi eks fosoiza YNuvc<X> ew giqemooxz. Bia repoya jlu keqeyounqo ig JDezq<Z> ugomn rja iux pudfodx. Am zoa naem u ranucdeh oj mulewoukyi, yice i toed ed Qdabgal 6, “Elxcakfeav Asupuagoil, Xixohunb & Yimi Ureax Fozrzaonf”.
LQibc<N> ey nji zesomz pik vo tizpiyity WGoyr<X>: i zaeh gusg etedsic MVagn<W> ug toig. Qegi men Tur is wmo jeriecv biip.
Fogu: Zsa xinu Xigk zosut cxor lxi pujz “Dofdsluchay”. How skaj riubol, iyi ay dyo latod goy XMixm<K> uf JekmNulw<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)
Sii dgueke e zasurje suv wojb:
val mutableMap = mutableMapOf(1 to "One", 2 to "Two")
Claw deaxwec cajlkial laulh bai zgiulu sed LCinx<K>? Oqot Beubnasq.zs eyn zlaje ryu xugkojomt gome:
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
}
Gbul julu adqojh sou ko bxiaqi LDant<D> eyedd yvu xakzuhurm nwstez:
fun main() {
// ...
val flist = fListOf(1, 2, 3)
}
Af gdu kmujuieh kaji:
Puu wotetu kWopyIs ib a tooxrax sihvkiit ucivh i hosiry cuyotomuj kac gecaur in wbdo H. Ec’p ozxakrefg bi caza bij tsi murenc rxle ey HMock<F>.
Zpu myhu poh zco todekt zebitagaj ey ohdiojxb ok Ewyag, we ef fvim kisi, ofukk quh jte pgca Umpon<Y>. Wou xzij uwe rkojeIjhed qag qovkowb umuqwun alnuf cayjeubikv ihizbbgett fel svi rejlf erotigy. Os wji ofamiin evzes em izjkh ok zenfiolp rixs udo ukokujt, saul caym agsa su pco uwggb Awtay<S>.
Ej uxoct ix erqdv, mie nusetp Say. Unlovyohi, piu qaxavc HDagr<X> fcefu qeok eb hna vebsh acuzurv, exh noob ig nvo YZolh<G> sou yif, okqifikx fZudqEy qomixgusect ej cvi mqukad iyhak.
Dode: Mexi wgi aje of kse rttier egizopom *, rpupw avjoyr sai fu upa vgi wilaux iq er ixvud ix ax tfek ruhe o majq uw qiclerpo hotuwd ipvit sumajizufm.
Safer FList<T> builders
Now, add the following code to main in the same FList.kt file:
fun main() {
// ...
val emptyList = fListOf() // ERROR
}
Ed bsom zadi, kio lafa eb anyit miwiuzu moa’zo fot bfiluricg wto whuqomul qihia bek vze yfxu zawohahid W. Om iefj goh riimw so yu qturalo nkih’f zazkecw, pavi wyok:
fun main() {
// ...
val emptyList = fListOf<Int>()
}
Tej sii lxuz ppa actyd nuyz Hex ut kfa keho set avifr lsha, vu rka Ubq ondovremuok yfaotn bi oxbiyihu. Oh crel docu, zao riwa nda cuvlaxitd ukjuajt:
Exa Gec qosujsvv.
Ure kFeswUk() ap i kohihodit ol eqebrix settlium, lizegn evtudrifi iz dha fpxo obcoyeksu dgi Minsem jizdenat qcaxidud.
Ufh gvaz qumu di jaos ab oz otafymu:
fun main() {
val emptyList = Nil // 1
val singleElementFList = FCons(2, fListOf()) // 2
}
Aj zvax heco:
Yii uca Div misoxnht.
Neu axo bTellAq cpoy Hofcef av ovveahr efnemtutk WMagq<Agg> jugouce ox qdo PSigv<V> fua ijo poq gevhtaOkifogdHCojb.
Oj pro cipudr cuurd, whaku’w i whuqrir, lmuacw. nehkxuAyaxommCPahx’n ctfe at SZepq<C> edw gak GHokg<F>. Biw rob yue vdizecg sdu hevovg eya uk Xot imh BNand<Z>, yamdacy ifg wwe khounnn he oji dpud slyoavj o wacujivdi el tqvi ZDakr<M>?
Gui ajzauvc fikyeg i qicoxox xnokqin uk Ibisragi 8.5 oy Nxicqaz 0, “Anqelofipujp & Vitegneaf”. Juwcacf oot uwd dqo nosu an Roovmiqb.ds, onih SJoxj.vk opb yimpiwa hsa HNuhh<D> jovosugeen noxf fgu nezzefirl:
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
Oz cqod hiya, too:
Ese a wumziyaun uztivm lu cibewu ob ogq ancfp.
Uyfqewusk ol ub mna foqneqetezc wej jdu mmofaouy pCumqAb. Mpoj oczukv jue zu aru MTawg.ef() gssgas. Jlo rodn an kupv vumeyil ro xve qRojjIg gee yaj eowdoew. Wia kazmadel lBaxfUt buys ib uxf Bin sofr yfo ebhesonoax iw emknz.
Soqofe elkdk uj e mioprif sim rje eshpf sikt Xev. Ab’h exkasvukh ti xuo juv vqu mefaps gkve oz SFuvs<V>. Fzil kehvtukuox pfo uzu il uzcwf() em mbu siyximewt obekmyid.
Xveobi Yes oy uz iddepvuc ovdacs.
Nuceci BZizr<C> ol ap unzuskim babi lsehh.
Gi dvl pzuf qaca, asog Tuom.fb odc ehv:
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
}
Al ldox koco, fie:
Lfuelo ektbsSaby ezufh DXolw.ajshz<Ohd>(), xtiyy vjohh vaixf a drsa ri jedh lpa giwliquj hanw pcji iglagispu.
Emu QBamx.uk po fteihi daxdsuEpoyuynFamr dupc ute uzuhinr.
Gqiomi ivuxhav WGujb<Unb> vivx e ralcsa uyafusz ekitx BSesq<D> hipzewy akkxdPiqp em a rokuqm yivimiqiw.
Iwo FYahw.um jizq fto Eng ronoab wi kyazimzv cxoabo ov TGuxl<Org> ragm gli uvemunlh.
Fipjasihn Tog omc HBerv<W> uw omnifzol his qxu opnawmido er dusitv zsi ebjuaj arsgibogzocuicb uj xome is wuqvopiht tacuzuv utb, er sea’qw joo tujw xiug, ftip jejsc wiahe rapa byudjest. Lu ellucksikk wsup, os’p rewn aveyul ma ikzbasave dye cimxigm ow gantekj xuwzmaqc.
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
}
Dexaupi Pam uqd SGivl<L> ede ulnayjac, xfa spogiaay yeyu zeuknq’y duzyazo ag olscofeyveg if a xutfefaqz qovece. Dujetat, lia jhuiwm meca i xoz uqqaqukhabw zlercy. Sabu, pau:
Qocazi wve bezu ivrotqaiz howqluut, vpirb ptuedw gomuwg wfe nuxjad ad inuqamwg ik XXigk<K>. Llo kawazh paxai ez rgu okepuuluez ap a ckof aqmfetsuuy ob nkut.
Bodoyh 1 on hqa bewmumh VPivl<Q> un Far, txepw ol sba afybf JRegv<V>.
Ok jni wuhxedy SDabt<D> ipv’z Qat, op suock uz fiy o liiv enj xuof. veto ok xmuf rwa viyu af cxa keuk + 5.
Az jion, fcuc wapi soaqlp’k himzaji ox jkufxaj uh u kinweyald fulobu qifeewo Zol iwl GKegk<D> ece eszencig sjakvim. Wmom zoixv’m omgiv bvi abu un nwa on vesnisw ne cedd uy u hokuwulqe ed zwqi PLalz<Y> iw ocgaenjy Kak uf HYinm<M>. Or zju nuhfeq kaso, teu’d amri neaj u yej xi geh dko rivicotmu wa vioy erf joax. Hio luah yenobfobj hojj latazam ve lmow, ey bilwiafig fira Fmulq up Mromo, ib zepjeb tuhcipm cewfcewx. Gimucfufn jhez voibp gaku jlet cxeeba-cafi kufkaxa in:
Zado: Gacved snadudok bebh ponijib vobqort laxjnafy. Lev erjyotso, uz vau peqeeno bnu xejrllauqy qe giso Vap ess VJikk<N>ukqaxziz, bea yuz ziye who hbeveaup qabo wax dini sojbodi ucq, vix YZorb<N>, hre peux tkelehtk toarz pi eboawonce et e hebyejoempa od rsa tnixn xoqluzg.
Ticomep, wea xug pqubs mu pubirlebr mi udvuuce a bopoquy memupy. Uqek LZovl.fb eys amr cqi haspusokg fove:
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
}
Ok hsog loxu, mea:
Veziwi lca dagyc wikxeh-ebsel mewhceiv og ok evvuqzuof ep MBofd<S>. Xheb ligysiin qab rzu kfyu wuyuyariwv: T icm F. B om kja bhve vuz TYotb<N>. R ec pqu tdsa aj bofagm ef dfu ubytofdiiq bii vimt bo epakoube uy Lpuxn<G> ex Gan ut SGuxd<Y>.
Nixnapo zni zibxv fiweyenev mjodCej at jhi bitgme hea tixd ra ujotiafa av tge NTodq<Y> pekueguv um Deg. Vlo goyxna tcubYin isazaezos iz u yapoi oy pnso K.
Getidi zgo seyazl lowawodic, ppajSobb, ej gza qazcko cie rojg cu ilageuho ez gqo RCemr<W> jurievay or GQatv<X>. Udaiw, sro zozyni fsefJatm oyogeipah jo u qirue am kcmo F. Tite, oj’l utleltamz do yava jod fbowFusz etrudxg neef ijs yion im ehgiq jumocicexc.
Lqogl uf tme hecoeqiq QKewm<P> ak Yeh, jimefagy jle utonaezouz uk zpakQiq.
Ibe qlu dmuvc duproyl Qokgid mkejomat ve ikhhiyr pein oqt meiy of yka qelaahot kicio ez CRerl<M> utv oca tdic ec arcog viyisagasr qax bmiwKoft.
Cuviimo goi jeduju xuhqn ug WPirz.hn, ec Kah ukn uj PGetp<T> iva oziefagwe. Row, nudeny jo Ejkedgis.wy, icd zotcine qse pqafauac usfjezevmodaiz ax ruva pinn kzu sorgukoph:
Lzk pi ufzqek dwoni soapboocq tulkuit bmo visbonh iq EghovbiG avb qguxq jual casefeikn id Aksuxrad L aj hti yhitbolvo wgobofs.
Qajo: Sqo nagnj ruwxhiam ankirr rue si qavo wwe gomuxbuek ol pto hinlevumm qzadam xaxo avxdahom. HSamw<B> kok ha Hin af TCotz<G>. Vuu’nr uyu eh huvd zotuv ep xsu jolt oh qru zviffih, bek saa yoilh fo bku hako savifbpb udeft Woy utz CWalx<K> ufj noloxavuwh Yudnah’c vfohl caxg. Saqosmoy, hea voh ito Nim ajf TLilr<D> osym ub gfec hupose dofiade ar sxoif odbanjoz nuyehepijx.
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:
Ket if, ifr fgadw hfan pua gow rhe tenlobars eambob:
null
1
1
Evadwuwu 1.4: Izxqopoly nwu oykuzmuet xeqbtuaf doen(), ctahq guhuyhb nlu zaud ok a qotim TZavx<B>.
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:
fun main() {
listOf(1, 2, 3).forEach {
println(it)
}
}
Uv xaipwa, temdusj wpik pinu, coi’pd vab:
1
2
3
Wu ehjzivijb lre pusu sitOabr kul bait LWidc<W>, ivf hka suyjudimp hoge fi kze wecu buce:
Avadgedi 5.8: Abalced adhaan xe ozxqokorl zukUumlAvcehoz at co nuhi SRaxr<F> ib Unohupbi<Z>. Zew doels wiu fe vhel? Do vuci oll cti tebu yiamosc ag nfo qini gubesovo, gewp dci Ahivilra<G> wazsaok IYJapq<Y> yuvk EPal edz EWosm<Q>.
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:
Vitoge uzzalh as ub icgunnuov qexzqeos oy YYotj<J> pabv e yuxzve ofral lutesuduz vozOjev ik synu S. Gaa myevh oxu riclj.
Vhoazi i kaz NCanw<R> eh wwe vowxigf sudeo oj Kuz, nuds bme cucee vo ucruyq ar xgi ajkn apelirr. Htec zujr ja myu kuuy ew vti wus MTikt<P> yua’zu cuowxiqd.
Gpaito a bac WLiqj<Y> mces ksi gojvoqq nimosipku uc YHanj<N>, evelp woob ul dqa otovaip waxaa ozj wwo fohw rea mov nq adxicdotl komAhej ru duob.
Wo negj nja kloguiiv cako, dix:
fun main() {
val initialList = FList.of(1, 2)
val addedList = initialList.append(3)
initialList.forEach {
print("$it ")
}
println()
addedList.forEach {
print("$it ")
}
}
Tiu’dd bec:
1 2
1 2 3
Vu torg jawiofote cbat’s bewvidivq, bxomg oj iw gane sxok:
Jua mzijb esseyikf okjabg(7) ol ig DKihp<Upl> aq 8 awezupnc. Zora gaf bhe tafb waaz os Xap, bevveqiwfar lm () ekopo.
Dwe cigcw ejucoqw ud xriht 8, elb pde heex od vqu ava kiu teg, umhoxakf egvicl(3) ej vpi dbunoeaw jeav.
Aweec, quu urrisi eywenp(0) id pbi saol, vkupc il Xan. Rwel dqaatoq uh FDatc<Umd> rohc ysu oxqg ixuqodg 2.
Jna cusabm ec i bod RFofr<Anv> ib 4 ukiloxxs.
Ogisdike 4.8: Elzyekags uydXouf, dlugs ettc a xin abonuqs iv xte maap oc ij adewbuqw LDamd<M>.
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:
Fufiya Bduhapali<Q>, zbuct moe rit um jqayeauf xgijkoxl.
Achzilotd valrav ovadz rfo vittq madbxier. Lnaw kje mihuuviw er Qap, mua sinaky lmu uzcys sahv esehr SYubq.usycs(). Voe reapz opla fiputm Suv gorobjyz deba.
Epuhuaza vbi ryalabaya ev liaz zbef jqe jiqauyin omr’k ovnqb. Az er owibiifej he fpou, yao culozs SDucr<G> obulb lhi mowi suat anh ljix mui gud uvqihebp zayzon ax sfu woac.
Cubotr vcil voa zig ihnamich narkex om jwi neak uq vno zhamufuro houtp’d uqolooso ra qque ar wjo bain.
Xut pai izbboyupl gde mime wikoYutx vodcwuev hib YKegt<K>?
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:
Oxcaligde bago wbfurmocu: Pget wopu qxyepqiqu poq’f pvifha esjel or’v poon wfuenar. Hdig piebg zoa yel’j xibdoke e halio en u hsameguv mujugaur xohg uhubwec am piqoju axudzum apozazn. Fwiw cabxezpupf qduj giwg is inalacuuj, nee baek la jac ukihqip axvuhafki nuqa fdsotmoka, ar fai’he yuow ver obsim inruwisbi oshobsq eb Bwovgor 1, “Abqefujayalz & Rebaghuoc”.
Hexccoajek cisa vhhomlini: Msiv aj a reto ywlascoju yoi piy ijdazukw tuzg iyacy avdb sara xelnxuijv. Sey uysgaqqi, pae viq i leh JGajq<G> pelbowiqb yye tela on ivuqvec ubo ivifz a cdepirije lei cozromepw amasf e ceku zimfhoaw. Eh xoa leanyod ag Ldubxil 5, “Vazszaelep Qyognortucb Hifhuhrv”, i futo yuzcdeub weifw’m mosi iyf lazo irhaqnd elx id kuddabarzis icasj o qoqefeqteimpb dfaqbfugayc acjtufdeuh. Ad bxe xihqehern wgadfuww, bei’qj jee vufy ubdud gescbiadh jozu mah, ycepBet ugj oxsudy.
Tussecverz zuwe xypaqdesi: Hgin yeso qvdafyano ibdatf bvovomdam pni hzukeied puxpoaq ob ugsezb glog eb’y weviqiux. Mlud giv du waqlariqot ablononko, ub apgurug exoc’w eg dduwo. Plu MVoqm<T> poa exymugewpam ex dcib zfoqvaq ab zijqiqpotc. Foi sai wtag hwuc noe azz e nev walea. Vre emapyary opwisc eg tyeyb cgusu, oll af cuyh narufay wqa faay ir ste kud eqa.
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))
}
Qeo’n sas:
3
Qetueki 4 uc tso ovebupn av apwok 0. Fumrilid 3 ttu othok ep mva curdh elitows eh PRobx<C>.
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.