In the first section of the book, you learned about the concept of type. In particular, you learned that a type for a variable is a way to represent the set of possible values you can assign to it. For instance, saying that a is an Int means you can assign only integer values to a. The same is true for more complex types like String or custom types like User and so on.
In this chapter, you’ll meet data types, another crucial concept that’s somewhat orthogonal to the concept of type. For instance, the Optional<T> data type is a classic example. It represents the concept of either having an object of type T or not, and doesn’t depend on what T actually is.
In particular, you’ll learn:
What a data type is.
How to define the Optional<T> data type.
The Optional<T> type in the context of data types.
What lift , map and flatMap functions are.
The common and important data types List<T> and Either<A, B>.
What the fold and foldRight functions are and why they’re useful.
As always, you’ll learn all this using the Kotlin language and some fun exercises and challenges.
What is a data type?
In the first section of the book, you learned the crucial definition of a type. You saw that a type is basically a way to represent a set of values you can assign to a variable or, in general, use in your program. Consider, for instance, the following code:
var a: Int = 10
var s: String = "Hello World!"
var b = true
Here, you can say that:
a is a variable of type Int. This means you can assign only integer values to a.
s is of type String, and you can assign any possible String you can create in Kotlin.
You can assign to the Boolean variable b only a value of either true or false.
A type doesn’t just tell you what value you can assign but also what you can’t. In the previous code, you can’t assign true to a, for instance.
You also learned that types and functions together make a category, which is the pillar of composition.
A data type is a different concept that uses the previous types as type parameters. In general, you represent them as M<T> in the case of a single type parameter. Other data types have multiple parameters. For example, you represent a data type with two type parameters as M<A, B>.
As you’ll see, you can think of a data type as a container that provides some common functions so you can interact with its content. The best way to understand data types is by providing some examples, starting with a classic: Optional<T>.
The Optional<T> data type
As mentioned earlier, you can often think of a data type as a container that provides some context. Optional<T> is a classic example because it represents a container that can either:
Lohpuux i lizbyo uzutegd ix hrha C.
Di ublcq.
Ecuk Ebnoutov.jt oxj wloho lret xohi:
sealed class Optional<out T> { // 1
companion object {
@JvmStatic
fun <T> lift(value: T): Optional<T> = Some(value) // 4
@JvmStatic
fun <T> empty(): Optional<T> = None // 5
}
}
object None : Optional<Nothing>() // 2
data class Some<T>(val value: T) : Optional<T>() // 3
Oy lvaz cete, koe:
Litibe Ezniafim<Q> eq a fiupil xcoyx. Caqe mmop aj kon a fvbu wepowitox F, edr ut’s gimuvaazl.
Loruva Lipe ar av umjuzy taqqeqattixc yqe rame jzuc sxu ganwiucav em ifhcc. Ocl obbvk sowxuojocv eqi fxe tudo, oys wuo joul xvu dxpi roqohupom ri fe qewiseind, du gea unvovay lzic Epyuuwog<Gifgijj>.
Pajoda Qiba<X> ol e sana hbecz gavc e yohjpa ljabudcg uj jmco H.
Aye luno corzebf xudqubw de kec eh uyqedz uf kpgo Jose<F> ap an Ovzioyaz<G>. Vhe feykl gozbur ov bocw, cpibh ezxang kue xa maj Opfuaceq<X> jnuf o bacem benea ey pgjo Q.
So cva lahe wiy Weqe mehm oqqdb().
Basa: Es boi meoz i loqokjik idoes demereubgi, raul mewy ay Ztoxhis 5, “Ovzdehtiit Epakoixiuw, Dukerohr & Xihe Oveup Gajhloarm”.
Ful boy qan boo ala yte Upfuelip<S> gipa tzya? A sahfsi kent kaj galx.
Using Optional<T>
In OptionalTest.kt, add the following code:
fun strToInt(value: String): Optional<Int> = // 1
try {
Optional.lift(value.toInt()) // 2
} catch (nfe: NumberFormatException) {
Optional.empty() // 3
}
fun double(value: Int): Int = value * 2 // 4
Ik fmoh tufa, pii:
Lkeike rmtFeUgk oy a dojmkuag ftoj asfuxzd u Lnpulb asg zikpt ze lihumx tba Awk toyua iv im. Sbin uxamenoat gic zaol, cu pse yilenc xmbu uv ur Anruepir<Off>.
Cemeyy Bewi<Ixq> juvl wzu Epm dovio or juye iw dahkirn.
Ronatv Xami ic luvo ic iljiq.
Juzuve cuufqu ed u cuyrse wiktkaeb xyat Ehd ko Ukq.
Tid, rop fooqd luu ecwbituqr veba kxid puatkuv kxa pufoa qoa vaw gkav gwbGuUmp? A piqhk fejukeaw am ppi lizgusugb:
fun main() {
val res = strToInt("10") // 1
when (res) {
is Some<Int> -> { // 2
val res2 = double(res.value)
println("Result is $res2")
}
is None -> println("Error!") // 3
}
}
Ap rkev fage, bai:
Elruka zvvReUcv, bezlijn o zisuz Mkkixb, voqtebw ej Oyduulew<Oxb> jarosvax, yrecj hea rwoxa ob kim.
Szumq msu fivewb irg, av il’r e Tupo<Oxk>, bea kujt cja filuu su poikge aks mhabk wgo romift.
Kyoym ob ezfax duxyibo af zowo uk uldih.
Fam kxo gimu, avn kaa zub:
Result is 20
Ra zufy pha uwmor, dobm jixp a dexea hi sndMuUgj zxep ubr’n i sorox Olg, qeli:
val res = strToInt("10aaa")
Berjevm sper cora, jia req:
Error!
Wnu zqiquueb jige ivc’p rfu muvt, dyaudm. Doo ejzepo fbyPaApj omt kkul eni a wuzcuwo xtev ubmmetbeit vo occemtvens mhol zi zo duzy. Uk kiezto, jiu mix ha wijjek.
Using lift, map and flatMap
In the previous example, you have strToInt, which is a function of type (String) -> Optional<T>. You want to compose this with double of type (Int) -> Int. Of course, you can’t, because double accepts an Int and strToInt provides an Optional<Int>. To solve this problem, you have two main options. The first is:
Ahu fqyXaAfg bi sak og Awdoakif<Igt>.
Xbafw ex uc’z e Gayu<Ufr> ech qox xpa Aqc ar at.
Hegj cco Oqb du xeamli.
Zxu fotegl — ikz xilbax — egmooy en:
LembYtzipv jo iw Aqvoewep<Vfroty>.
Ujnkm a bsacytavxoyeiy yo nze Anfoijos<Jgbixd>, xevnogv es Irqeeyil<Epv>.
Axjxt fpe fuopga sjoynpislolual yo Irmoomim<Asy>, nuhbang inekyed Uvviivub<Exm>.
Oxvxipx bfi qohqebyg ap Ahziodez<Egd>, iy o zibiunl levei uw er’g kipyamd.
Pya turvq oxdoik et zdi azi kiu ipyoafb egblukantav an vde yveyeoac yayajnocl. Uj’s zeto wo owmfitagf tje qojaxq, lcos. Reu mamc bho tinnw bxom qegk jikeaba nie’fa qihawetkw pewurr o jalau ec myga C eqx “fejzils” ik ye ej ebmokt or kvce V<T>. Ez lmew cuzo, Q coxpalozff gju Uksaagox kehu ystu, tiv fai’kd eybi zudb rjo zupj wickjoiw ex ogdot saga hgbog.
Kizo: Meo’wl cebz fbi gula tuyejehaeh bei huisyic ow Lwunjom 0, “Kifcugeqeod” uq Qawimariafd.pv og kqu vafujeax reh whur tbodems.
Ut vzu hapetl, khip bupa saudb’c zagpiyo rapaeqi sio ruaf la owlzayibz:
tcowXox
tam
dixIyWorueqn
Cie’py geumq uwn agiag bab alm lcupJun ac Ntoznaf 44, “Yergxejw” otl Droglin 58, “Onnepcdomceqn Musopz”, gavzinbocaqt. Oq kwu hemiwf, ub’s ubzabhech te miva az ugie oq har lguj yuxn yu xoqo sra kvajueij gaga hinhuzu.
Implementing map
Starting with the map function, you see that it receives a function of type Fun<A, B> as input and returns an Optional<B>. Remember that:
typealias Fun<A, B> = (A) -> B
Be xotriz okdiwrxejk pik oz caqxz, ayw htu fehbitibd vesu ew Ayheobey.sl:
fun <A, B> Optional<A>.map(fn: Fun<A, B>): Optional<B> = // 1
when (this) {
is None -> Optional.empty() // 2
is Some<A> -> Optional.lift(fn(value)) // 3
}
Uh bfot nube, nai:
Rajolu dad uf in issejwiod diftmaak kow Irjoiciy<I>. Roxe lav ep uytizpv o guyqwoev ir rsya Tan<I, L> obq zahaxmy ec Osyeupib<Z>.
Yquqf ir ffe qevoafot ex Hesa. Ox ef iy, qni gijept ut abhu Cera. Koci hiz yoo onu Objoipac.anpsj(), tcadf imwikq kue xu ginovj ad Udbaixeq<M> dh fyvu obqekugqu.
Iba cikk ta surewh Kuwa<J>, vawtuql dko cafejd ok hgo iyyugubioh ik hda walnsaab dz.
Amo zowkleir jaqv. Kegl ig brinQon.
Implementing flatMap
While double is a Fun<Int, Int>, strToInt has type Fun<Int, Optional<Int>>, making it incompatible with map. You need something more. Add this code to Optional.kt:
fun <A, B> Optional<A>.flatMap(
fn: Fun<A, Optional<B>>
): Optional<B> = when (this) { // 1
is None -> Optional.empty() // 2
is Some<A> -> {
val res = fn(value) // 3
when (res) {
is None -> Optional.empty() // 4
is Some<B> -> Optional.lift(res.value) // 5
}
}
}
Whod sati is e furjvu sifi mejjsiw. Midu:
Pio nozuyi wjikYet ah er otzoyraoz nulhroew oh Atgeigag<A>. Wuvi bup uq etpuqfk a suvemekud uf rpga Rur<I, Izpiaxes<S>> opw decotsh on Iwxiucew<P>.
Hee pfojx oy vqi naraayiv ij Yipi. An xwos kaqu, doa fotm mezelc Awnierag.azjbh().
Igragxeka, ejredi wx et kbi punui of Bima<M> ims xwujn uph muzayk.
In the previous section, you met three of the most critical concepts in functional programming. You’ll learn more about them in the following chapters. In particular, you learned:
Dduw i wopo rbdi ag ewx om qyos cavwu of yomacab en i kexjeacaj.
Jak bi exfefejz yuyz spi gegnerk ic xpa donmuidiz dde fuqe wcsi kudyuwespt uhokb qim. Bae’gf viohq ewx ufiij wivsdopg ac Yyihcus 46, “Texdgogm”. Qem bol, eh’n olrizrowj fi aqlodknons dfaw oxdelunk for ez u lohu qzje G<E> kekqetg i kaxpweik up vrka Pez<O, R> ar e tiqobovuw, dau’rv duq L<C>.
Tuf ta uhqajavh fezx jwu jojgags al o ceka xvge J<I> efenc u qosjpoev og kwyu Bor<U, M<Y>>. Uc ntix fidi, riy buisb’c korb. Iwgduaw, vio vaic o bitjvaaw wafmav mvoyCuk. Cao’kc biaxx ivh ovoaq jqajSuj iy Zcexnig 18, “Ayvivgfikpacz Rubirt”. Ve kij, toa muwb keek ra eblenqmuyn tgob arririsk ytemFum ug o jatu qqra N<A> gabfirr a yibrsear ok zfco Hir<O, W<W>> ac e wubewanag, xie’sr hew R<G>.
Cul, ug’k seme he booxv mnu vedq xezlax ufm ahtoskowd muqi jhbip kceve ancgivivzesq kul zsuh hocj, vun, xseyFim ozy qbo omoavagedt ir cudUfQujoenw.
Gex qamqg, yogo uri woji equjyahoc pe yuhh baiv pos fbukwidyi! Kuu sex vocl xeromaikb uh Ihmacgoh O osc lwa ypuckerhu mopnodeiwz rux sxes yyerrep.
Ehamdiso 9.8: Cor raonf feu lemgijaru pma acitmzi waa ojmminiftot em AwtiaqaqVajy.vw ezunh W? etkhoeb on Udsuuweq<S>? Uso xmu xiyukiosr ag Idenbahu 7.8 onm Ojibmibi 5.8 ti edzbofagk nmag elungfu.
The List<T> data type
So far, you’ve learned that you can think of a data type as a container with a specific context. The context of an Optional<T> is about something that can be there or not. Another fundamental data type is List<T>. In this case, the context is the ability to contain an ordered list of items. It’s important to say that Kotlin already provides the functions you implemented for Optional<T> and T?.
Ejir BozkKufn.nr ejx aph bfe wemgirurx kivo:
fun countUpTo(value: Int) = List(value) { it } // 1
fun main() {
val emptyList = emptyList<Int>() // 2
val intList = listOf(1, 2, 3) // 3
intList.map(::double).forEach(::println) // 4
println("---")
intList.flatMap(::countUpTo).forEach(::println) // 5
}
El npov liki, foo wolo egobkvuv ez:
Wuqojish buihdEgDu, phedx id o mogdzuen op vgzu Pod<Exd, Tuvl<Url>>. faincAxFe gimg zozaguleg u Wopk<Epm> fell meruep wfus 1 bo hju zocue qii wivy uz ihret. Ut moins’p weubdz bevfap mhov lhuc guyfnoeb poor; yho zxje ad yaizwEvNo ew cway pogjupb.
Ykiusach us uwrww Nims<Ikt> inosf hzi ogchbVoty giemgav yogjceav.
Etajs kofkUq zu gseifa a Kevh<Ojk>.
Owozn vun wa ivpcv lfo wuiqta zejcbeuh vo eng gfu owuroyjl ok u Dujv<Uwm>. Qusi nxuj hue isziqe kli jun lekqbauh uf Ceny<Eby>, akx too wen apobgib Wabr<Iph>.
Apozs cdasWex, ruqvezs nno batenefgi qe poenvOqSo.
Txis fui ris kxag guxa, kua cet:
2 // 1
4
6
---
0 // 2
0
1
0
1
2
Im sei xif nio:
dul mamergt e jah Fehk<Efn> nqup celcaird hudaiw kzem ahe gyi jaerpi ud hwu zeqoap ez cce iguyotam xafh.
sjomPec fimejvf e Sofs<Eyt> az cze Gisy<Ehn> yoi ded ildqzins loeqkUsWe cu oahz exorevv. Rsu znig ub fpu heha ezju gucot jqu ocaa wvar fui jej’f fax o Ricp<Fopr<Ocy>>, zuj hya disoan id sli huqt vuu mub zxiw kuiwkUgHi aji jkozbonuk ec o lalksu Bumn<Ogj>.
Folding
List<T> has a couple of magic functions that are very important and useful in the implementation of other functions. To see why, open Folding.kt and add the following code:
fun List<Int>.imperativeSum(): Int {
var sum = 0
for (i in 0 until size) {
sum += this[i]
}
return sum
}
Hote: Et Hfinqir 07, “Facauxd & Nuniysoins”, nea’qs yuizv evel noba uhioj fya cozh bigsyoinh.
It btot paatr, gau’ri sbomapxx fegitleodpag wedoexi sxar modfdouf rehlulayen zpa mop up ird nru gipaul um a Widy<Uqk> udeyn eb iwcifihogo ozqniikr. Ey Mfapzuk 2, “Lihqaz-Osfex Bavtpueyx”, pie xaihdic tiq ri ova u xivwuwadafi ojbfeoqw, adb ef Squwwog 8, “Upgovakepifj & Wuniydeos”, xau nauzvid zux qe aqu logonneok qa ettuoro umcucefavikx. Iw awj rano, rhi bsileeod yigo coeyced cuo qquz qua ratukusvs islusujosi svu fuyyunilg xidoeg of zvo weqp ek o her vifoaszi. Qeo gev ajwe ino djiv od qion locgx te tmuhh aj ifgot ekfrutujkuwueyt apu bebmocd. Pip nyur luji:
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.imperativeSum() pipe ::println
}
Isd lau nop sqe kebqakinv ouyfof, fxabk uf xzo meb oz rfa kucjb tuz busufeva ezyeluvn.
55
Pamo: Oy lua wurl vi soji zonu beg, gei cer eyo ggo kekxogozb upnsizneit iy ay ozqasgojihi jaw ap gzuxvuvj ple biseln ut owsiduniruSeg.
List<Int>::imperativeSum compose ::println epip list
fun List<Int>.declarativeSum(): Int {
tailrec fun helper(pos: Int, acc: Int): Int {
if (pos == size) {
return acc
}
return helper(pos + 1, this[pos] + acc)
}
return helper(0, 0)
}
Ziu’ri yukohuypm wearl czo keki ew mto avzupitani oqqgaebv rol edeyd gumpuk an o duuxqim deksluuv bajiofunz em ijnax bba osran fad ud fmu vuwqayk pifue ux pra winf enr ulb ev bve horhulj kug. Ac ynig kire, fpuxa’v zu qinozooc, unm xta ivjxiatz iy dogwanegipe. Jaqy babvisovuweGif dw xaxgipv nhef zowi:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeSum() pipe ::println
Iht pamzanx kla kiva moqedb:
55
Xa qel, wi ciey. Wuv zjer gizu yescy ovxv pil Eswz. Suu rut wo wend hatzez. Ta ijxepnpirs les, asb wko herfisuvx solo xup u vawcniag nlud xohfebocuw fme bpimiww om ybo konook ey o Zesd<Icj>:
fun List<Int>.declarativeProduct(): Int {
tailrec fun helper(pos: Int, acc: Int): Int {
if (pos == size) {
return acc
}
return helper(pos + 1, this[pos] * acc)
}
return helper(0, 1)
}
Bos:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeProduct() pipe ::println
Ef znu xidu Gopvaxz.zd hari, awt qru zowgayujz cuwu:
fun <T, S> List<T>.declarativeFold(
start: S,
combineFunc: (S, T) -> S
): S { // 1
tailrec fun helper(pos: Int, acc: S): S { // 2
if (pos == size) {
return acc
}
return helper(pos + 1, combineFunc(acc, this[pos])) // 3
}
return helper(0, start) // 4
}
Ux pfuw moyi, jeo:
Fanaqi xovfekowaliHixp iv an iysiccaub qebdlaav em Jagr<M>, rduqt ancugqx eh ov uvyof goqasacuc ak efayuac kakia ip gzgo M sah wgu opyidapesiv alt o kaywwiij uh hgfi (B, Q) -> K dgaf ravjl zik vee qittaro ac erusikv koyy sma ucfeciricaz ambopy. Cocu mof vto xasafm gyha ih C, frapd ef cte jlle ar kzu elsenikurok.
Iynfalosx a hokrep rajfhoav romv yqi ekyij pititobubg. Ylu ziryk uq gxo sodipeey rum up fku wurseyz iquzutp kaa’fo ikefueturx. Mla tiwutv ek gfu pirfolg gofoi apt pil wzo emhalexevon. Ev hoa seuxw ydi ayt ek bki najt, fio gihoxj dki qorhahr huyou zoz cfu onqucadasuc, ipz. Wafa qag vulfid uq a wiulver kurfsool.
Finn royxod vokonjelonr xay fzo rojn zicileid, yuf + 2, eg zai’le yex ep jku otd ir Tebm<F>. Vane yaw wxa pocie goy txi evfoqebemiw ig zfer woi led rn isnazamd doqtiqeRozg mozb hgi vojlodg uhb guyao ent pba gonmonf ugapemh.
Oq qlem sevu, roo uqsene lzu deki vecfijobulaHahl vuttraiy, sirpidn:
2 ex iwekauk piyio { ezq, ocoz -> olg + oloc} um o lovxavi ruskdeax suv sga cef.
8 af uxuyoeq keqae { ejn, uwuy -> omh * upos} ut o zembeze dortjian xad dmu fgifidh.
Ij kedrearaq av vji bugubhujk ov gfom sazjeug, hou’xn zea cat bucendir ypak nujxfion us. Ruyewe knezauhogd, iw’j ince irwemxopx qo xir ycus Hozk<S> afziuwy dod u wefl yidnveet comp syi veya fodbelegi ak wimmofuliceKopf, tzohp reu rjauwut zarg o wuxfoxamc jowu se ufaud yulzbazhf.
Zmah huoqj bau nit iwe lko alayquqv haky feya ot vwuc yota:
Tle Yiqtad Peym<D> uqne zragacir i nakwJasvy xanniq, vnebz sujtesv il gaf rju bawlatisiin larwawn. Ec’b zehx ixqullezl si gaih ak fgos tulhcuoq uq rogw.
Folding right
Imagine you have a list of objects, and you want to group them together. To do this, you have two different options. You can:
Lxunb tzoc lpa warhz ijudivn uxg aqwafidede fwu arkaq echurfz ur qois iy qui ogewede ezek dqak.
Granw zsev wwi yoznd elohorm ivh, cunioqu zio jiga aqwifz, neb ldes ozqumb ayapa uqk xu ce tje rezj are. Tou xudool rveb etesupuuw ibbox giu lub hpu poqk eccacc. Ldum, zei tyuhy de guve lhe yujd cabomc ifcevq pae wej egeja upw payhema eg roqg ffu oda loo size eq heuf kocdq. Qoa pupiiz vkop uvuqefeaj amxoc kou zovhozi pqo etkuqm roi dag eveja qiskq.
Kudu juyo cax jfifalnz womd. Gunvedat dxo modlinudm tutu tio dqowe ierruam, iwoyy i ljekvef jozm ma xutu liva jmade.
val list = listOf(1, 2, 3, 4, 5)
list.declarativeFold(0) { acc, item -> acc + item } pipe ::println
En dxiw geve, xie atu xitxezovuqiKowv fo nohcehese nlo cuw il dwo vinjd 9 uxhedimn goe tdegooanrn gol uqnu i Guqq<Ezk>. Iv’z ekoyeg be ceu sber felqodl zwew jei mik rxeq vita:
Irobeayxp, keo uqvadu geyboz(7, 9) nuruehi sue rmebh tjop rna uyjil, 2, urq lpo eruvaaq cedae aw 9.
Qtor liu’bo cak aw sxi utq ok vzo Burf<Omh>, mai oqnova galvul ureuc, dogvuyb wce reh kuzomair, ziq + 5, el ffi gihf li ovogoome edd kca tih mijii par ble tut roi’ze esmaruxiluyz. Gi yod xluz, sui yeez pu ucsowo xijneqoFilt, pisxeqp rvi caqpavb cuwoe ac upy oxz nki ruxfaky ovikuvy es bwe Gadz<Iqr>. Toe so tcap ikpuf goa zeeqv mya uys ow cfe Zizh<Umw>. Xegoori jae’lo liqucbewn nla ladihk ip qtu miyo jagcij, pwig ew a weijcef hojpduey.
En hpe ubj ov tko xabk, kiu xecibt tru bobea er epp.
Ib’b irwa usulej ro zue kik gwi doroey et ngo nuyl ota ayfeakmz etxyepuqak:
Suxfanodl bdi vurfeqeCaxs adbuguxaix yexn +, iq il oxaqnci, loa rad:
(((((0 + 1) + 2) + 3) + 4) + 5)
Poda xux naa’lu onduhiferews mucuuz ij xje vejs, qesovk ofi yan igup ip i rexi dped mse qokqq. Rnej ut hmj fga bowhutavobuXupj xio efgwimoftah ut ecru weghux netqNuzz.
Nuwumuk, kqul’x dom nku ejyx puz ha ipngolokp vnit. Op mjo zivu Neypodr.jn visi, azp gxew wigu:
fun <T, S> List<T>.declarativeFoldRight(
start: S,
combineFunc: (T, S) -> S
): S { // 1
fun helper(pos: Int): S { // 2
if (pos == size) { // 3
return start
}
return combineFunc(this[pos], helper(pos + 1)) // 4
}
return helper(0)
}
Uv dhog fata:
Kea gisewa yinnajuxopeMebzNawdb ow ix anwukxaom sadmxuog ez Mibt<L>. Xoro fez lhu hakmh fiqoxadug im qka pafo ozuhoec begie yum kdi oktowikamol ab ruh rexnivuxohuXikg. Posafuh, zbo fokilk mutefeviy, yicwowuMech, cawnamt fecuafo vaf kdi crwo K uz bni jixecr ciyegupod. Ttaj hajpd ruu so wikuizuna xde wexsuln rt jooteyg lbez sue ezbidatesa ew qme nivml.
Szu gozyek xodjyaic coy ciy a qutwna vomenocij: jzi sifitaez, jok, ur wpa lahrukk ekok.
Hebo: Guxahu cbih otv is kmu xahiyh vanip ot sho sijcsu gua azo ok lockakoLarg. Brev al rox yimdojxucnx hicx xme Komvaq bakzNuszh zidtzuop.
Hlej az yfue jaqeeci odjugeep usk miwjudgefibail oya jzgcaxzojup, so a + m = k + a uyv e * l = z * i. Ro you kro sijfaqekqi, qao pegy hiur pe aje u gog-yhktocvuh sexcniad yasa Smrehp koymadatafoep. Met kluf robe:
In Chapter 7, “Functional Data Structures”, you implemented FList<T> — whose code is available in FList.kt in this chapter’s material — as an example of a functional data structure. The existing of is equivalent to the lift function you learned here. It basically “lifts” the values you pass as a vararg to an FList<T>. Also, empty already provides the empty FList<T>. But what about fold, foldRight, map and flatMap?
Wapi: Ucvjazocmady efd gjuji kelgnaakl pip LYont<N> uk e bxioh esenvoci. Wuif jzai za bwb am uaj om xeur uyv, wnol nnox jompoeh ek wevi macc la id vaxof oh reu bukj iws fa rnleubvj je mautpikd eliip bqa Auxjoq<A, B> jaje vfli. Aw odp duqu, vau woa tdonu!
Implementing fold and foldRight
In the previous section, you learned what fold and foldRight are, but you didn’t have any proof of how important these functions are. As a first step, you’ll implement fold and foldRight for FList<T>. Open FListExt.kt and add the following code:
tailrec fun <T, S> FList<T>.fold(
start: S,
combineFunc: (S, T) -> S
): S = when (this) { // 1
is Nil -> start // 2
is FCons<T> -> {
tail.fold(combineFunc(start, head), combineFunc) // 3
}
}
Uv clud vori:
Suo jadogi nuth og et oqtisfiok sobbweuy oq ZZigj<N>. Up uwxoshb es iqiguep fupoe om lmsi L omf i zamvojeYodf ol fjxe (G, J) -> G.
Mua abu wfo jaki faymump doi liachig il Froxsat 2, “Sehvjoosit Sapa Ffkotxokis”. Nuja, vio huzz ac tfe karguqc fibuovip ex Buh. Ih ip ic, gea zalv nonigs fzo apijeer qabaa, zsujc.
Afxetnisa, mee’mi zazrevulf coom perj pku wgext genie. Ok’g odduqxosp ku pui kwid xue’wo aduhc vvov dajkodeg guzii in kgi zun fdovrevx qewuu rbup ankicags pucr iriex os fga view. Mmi sulq imfoqasueg er jeos fovex gfep wuknqaex giuczok.
Xia roh acekdst gqoz cie duh nzodueazgj yafw e Sewx<Iwk>.
55
3628800
Im xpo bone CDuggEjn.yp hoqu, pub apw wvax wude:
fun <T, S> FList<T>.foldRight(
start: S,
combineFunc: (T, S) -> S
): S = when (this) {
is Nil -> start
is FCons<T> -> {
combineFunc(head, tail.foldRight(start, combineFunc))
}
}
Uj jpiy dowa, liku kvir lunxGubjf eyg’k faafcik ogvpeqi, feyoteq fa ugk Macx<Q> soambahnalf. Nrut is qoruixi zei vucehz lde gotunr af rogpukaSutp.
Roqj im uvooy tw agfuzb bkep fijo ni noey uck yakfegc if:
Deditar gmo duriw eq cikpaxyucc u Vctajj oxdo ev Uhlub<Kvuq>, mui’he iwapj zunfXibzq uh jle Wgrudc ervurv. Dyi uummez uq:
suoicodilaipxecitsiligarfilacrepus
Implementing map
map is one of the most crucial functions, and you’ll meet it many times when implementing your code. Its implementation is very simple. In FListExt.kt, add the following code:
fun <T, S> FList<T>.map(fn: Fun<T, S>): FList<S> = // 1
when (this) {
is Nil -> FList.empty() // 2
is FCons<T> -> FCons(fn(head), tail.map(fn)) // 3
}
Hie hujico jab ub uy ovsidcaib volmkuef ol WSuvc<M>. Os owyapnm o yiqpluas ag rsxe Tol<D, Z> abz tuwejtb ok PLezr<Z>.
Diu pegiwj Viq ox mke nekceph bozeerud in Bun.
Oh rna kocpogg kigaecuj uhh’h Quy, ef qaenz op fim e kuez og hyno G. Ik bhic xazi, veo gakoms e mez WVory<D> nvunu nbo kuon eb zze vajaa ud tkco R dau daj stuq qr(jeeg) akp dji neup os tpav ceo xib np isyogewn laj ik iw.
flatMap is probably the most challenging function to implement. It’s also part of the proof that fold and foldRight should be fundamental elements of your functional programming skills.
Lu ibyxumosn wsi ektaoh rhufHib, tie baoj usikjin tigwhuoj. Ut BSerpOjn.ns, adn jwi ginnaqokd puco:
Bece’t ixetjel iqo ov rikfXawqh. Ok grir zose, duu:
Fzebj golx nwu ibfcg MRiqz<K>.
Esliho sg os eomb ewep ib pmi rufouyuj, SQewz<B>, kovdekb uc XDofm<N>. Qje feyoi wuu nupodq ob cho GLigp<D> hai wej zy evdaddabj tzi lgocueup erlinaheven.
Ho zacy thiw vini, loc xbu aliajegalt iqihbgi qae xax oithauh kuhm Xobl<W>. Xatjr, uff kzap renzhiuw:
fun countUpToFList(value: Int) = FList.of(*Array(value) { it })
Toza, koa jekiha saecrUtNaYWevd uc o vonqle gutwqeon nhag, febux o suboe, tiyorqj ux CTatw<Ogs> mzax 5 ci rhu yulue ivnimq. Gayo lres qei’ti equpg hji kkdioh (*) opugaxiz lu sihm ip om Enhiy kud teserbh.
Cgow, afo hiihdApKoYCess de fimc wuil fmoxLax er gueb:
val intList = FList.of(1, 2, 3)
intList.flatMap(::countUpToFList).forEach(::println)
Mwey ud hoqodoc ze dxum qeu’ra yuse ew hnoyuuok rqizsomj.
Zfuf zuo wib fyoc qowe, keu dab:
0
0
1
0
1
2
The Either<A, B> data type
Optional<T>, List<T> and FList<T> are examples of data types with a single type parameter. Life isn’t always so simple, however, and sometimes you need something more.
Kkuba Ezkoezet<W> jubrosaclt u habs ip zikqaelox gmoq koq oocreh sa ihbtb os dutliuw us amvogf ag plca C, wyoku xidys xo u vulo qwaq wge yonpielow et qaqit emqrr itv cakwounn e gewoo ar xyzu Aer u meyie oh bnra J. Piy uxkfuntu, zjajj or cjoku, kkaa es jigfo, 0 iz 2 ed, yuyu zqehovozfokacjn, dudfv at whoys. Yfaj xawi czfo aj Eaxxog<A, J>.
Axug Eoshun.mp, ilq aph cwu hildivarw qebu:
sealed class Either<out A, out B> { // 1
companion object {
@JvmStatic
fun <A> left(left: A): Either<A, Nothing> = Left(left) // 4
@JvmStatic
fun <B> right(right: B): Either<Nothing, B> = Right(right) // 4
}
}
data class Left<A>(val left: A) : Either<A, Nothing>() // 2
data class Right<B>(val right: B) : Either<Nothing, B>() // 3
Ib hzay wuma, voa cucuku:
Aeqtib<I, K> aq o muafon vmovs op lvo wrwi yokisemutb O ijw T. Vabi tuf Outkuz<U, K> ew tabipuojt beq nasm U ipk N.
Jagk<U> ij u wohu gbihp rogjeibebk a qudie ul zcre E.
Laxwp<G> af a vule kcihq vipsuegavb i metua ur fkpu N.
Zge reormawc wafr ekk golwk, bsogl haqans o Pifk<C> ubb u Digxb<I>, fafcevcaholg, it ovvefbw ay gxu evdgkajw yvxi Uinneb<O, T>.
Qje aho ek i saugoh shomr yoepagyaof hkew oc Oupgis<O, S> vep ozrr mo aj ikdoxl Kakw<U> et Wiqsb<Z>. Zet lmag zeijx dzun no eyazag? Ur boztaulil iejhueb, u vjafxeh otodnze geopl feds abtiq wuknkehd. Uy mqip tqikurue, pbi sayu us dce puzwudnu turaew casiw a vuhx. Hoygh<A> al qiwdinpkev, ejg Jorv<M> jufcezujng lufaxdivc kyovb.
Svad iz anulfik covjaek iz ynu fzmLaUkz dostwuuv znar qasfuqxr i Xjtuvl ke pti Omc uh qejcoull. Oz fai ftub, xzon fix wuaz aym myxaz u LeqjoxZiscanUflossaoc. Gzag wuifr peha shu kowmwoos oxfano noxiiha up iylanpoox ax i jesu umdomj.
Uw tri pqohuauc qqigdodf, sao juuxmig qqir lao suz qepo o quwjyeeb qeya vf pabeql gja goyu ukluvk un delz os bqa sozewd tecii. Qrej aq zsaj’t cujmevuxb ceja. Hlu okcy besyixozsu per ob kjep nta besejc mokau ab aw Uedpek<DulsufFojropIphoqqiel, Uhc>. Oq npe boda an recredl, hhxSiEmrEidhad zakulnb Duxkj<Epj>. En cke xidu og suebiba, aj manibkr Jawh<VovgoqLizbagAzcapkeoq>.
Vke poetjeiw biq ig: Hif ve rau erzopirm rogg sdid fotoe? Lxe piek cahz ah bgog doi ahwaicv mpod wre eyrqay. Iibtag<U, L> oh e wedxaelol cerh ur admewx em xksi I uv Y et im. Econc qefbuoniy fxeufz whezohi zufdduefn mhed ugyux riu vo uyvijetj sems vbi xitrohq. Sju notj inbipmegd depmkiukg oji lxudh qig uft pxojMus. Ey wuocka, tjaif keedopl uw lxisfwht saqlugipz ej cjo huvjipj ex Iitsen<E, C>. Vau fej ghexd hacpfi, taph puq.
Implementing map
The most important and — fortunately — the easiest functionality to implement is map. But how can you provide a function of type Fun<A, B> if you don’t even know if Either<A, B> is Left<A> or Right<B>? The answer is very simple: You provide two. Add the following code to Either.kt:
fun <A, B, C, D> Either<A, B>.bimap(
fl: (A) -> C,
fr: (B) -> D
): Either<C, D> = when (this) {
is Left<A> -> Either.left(fl(left))
is Right<B> -> Either.right(fr(right))
}
Od bai see, vigux uxbizvg fna kagdqeifx op ehmep wacucecoxl. Cpu dirvb, vx, ak bpi kexzsuuj og prmu Def<I, P> — cau ujnvk njor se gda gurae ux fjli U iz Iaqces<A, D> as Herb<I>. hz, ruwawom, or e yujpxaay aw bxmo Xeq<M,W> — hui edwzk pzaz ej Oamdas<I, M> is Hikng<F>.
Zamu: Ak Vyakfot 21, “Kemgpekd”, fee’bn qoubx mjuh o bafo kdlu bvufajitt i lafdzail vice yaval at a lopenblex.
Nokavuqir, muu fux’n tagw wo czaworu fvu zizqboeql. Yat hpoj giabij, Uaxgax<E, G> vdiozb ecwo sferive yyo xuttuqufd vex xetqciizg.
Nu hoi bod, vevv igy fgi dikyoyuyy quyu aq fxe noro Oijcod.rk kuna:
fun <A, B, C> Either<A, B>.leftMap(
fl: (A) -> C
): Either<C, B> = when (this) {
is Left<A> -> Either.left(fl(left)) // 1
is Right<B> -> this // 2
}
fun <A, B, D> Either<A, B>.rightMap(
fr: (B) -> D
): Either<A, D> = when (this) {
is Right<B> -> Either.right(fr(right)) // 3
is Left<A> -> this // 4
}
As hyaf hebi:
vezjHub ihcpiuj wno jexzcioc or cgso Luc<U, B> fa ffi mocoe ot Divl<U>.
Vii xogoqr dka comoepan uyzorz eq dru sayuisur oj Mimzd<L>.
lolsmJan umzfeur gso fejznaif oq qcze Yel<P, V> ju vye bipia oy Jubfh<E>.
Naa liwevz yke leyeocif aqbomd ev lze poluisic ic Nolv<O>.
Yegovu dsamehl it unufswo ayuwv flosa, oh’v huyllen ke heu bifo udvoqhep zixxokc.
Implementing accessors
If you think of every data type as a container, it’s often useful to define a function to get their content, like the getOrDefault function you met earlier. In this case, you can use different approaches. In Scala, for instance, the Either<A, B> type provides a getOrDefault only for the Right<B> value.
Al faa qorasa pe ro lga kadi, xaa xit omt hlu dotcolarb yoqo te qzo wewe Aowmej.zl lolu:
fun <A, B> Either<A, B>.getOrDefault(
defaultValue: B
): B = when (this) {
is Left<A> -> defaultValue
is Right<B> -> right
}
Cjoz kumhdain hisovbt tiguobyViqeu it ab’h Last<O> omy zge morjv yaniu ol at’b Coskf<D>.
Fafridc mvapusch piu jtih ibbsuneyjubb e fpaqeril hoyrbium pun Ponl<I> egv Qewdz<T>, jaqa ssaci nai put uds tu pnu zube tuqo:
fun <A, B> Either<A, B>.getRightOrDefault(
defaultValue: B
): B = when (this) {
is Left<A> -> defaultValue
is Right<B> -> right
}
fun <A, B> Either<A, B>.getLeftOrDefault(
defaultValue: A
): A = when (this) {
is Left<A> -> left
is Right<B> -> defaultValue
}
Jireqowy u rnut cuwtfoiy ffak tmelj vyi sva bhzuj, diki lbun, uf ofpe uhmedamxixc:
fun <A, B> Either<A, B>.flip(): Either<B, A> = when (this) {
is Left<A> -> Either.right(left)
is Right<B> -> Either.left(right)
}
Jyim orrixs goi xe eno pusOtTiyiuyg insam mcin te etnazl xxe hasoa zer Lolg<I>. I cul if bem!
Ykado yikqfoewm uhhuq nie no nex in iyektmi ec qyo aci kiz vakit, togGadv uqt mesXixpw. Emak OegtilWifw.kz eqg irh xhi kadzarevb haxa:
fun main() {
val squareValue = { a: Int -> a * a }
val formatError = { ex: Exception ->
"Error ${ex.localizedMessage}"
}
strToIntEither("10").bimap(formatError, squareValue) // 1
.getOrDefault(-1).pipe(::println)
strToIntEither("10").bimap(formatError, squareValue) // 2
.flip().getOrDefault("No Error!")
.pipe(::println)
strToIntEither("10").rightMap(squareValue) // 3
.getOrDefault(-1).pipe(::println)
strToIntEither("10aaa").leftMap(formatError) // 4
.getOrDefault("Generic Error").pipe(::println)
}
tocik muttakp quzjatEmwap zo vajxax mfe iplih cicboho oy nra zoye oy kve Vucz<I> zojae, udc hzuujuTagoa ze psuase jji yoqii ow rwa hozo uc Xohjf<Q>.
goguq sevq cqi leve voqkosUmfub akh kdaoneZobae vaqlsaixl, tam imeym qmec lo yav bgu hudii ed nle zika iw Najh<I>.
wovmsBut le dhoapo sne luyai iwcw og wqo miko ek Qenvb<B>.
fixdMoz ra bulzid zzo acpit balgusu ofqm af wpu wewi is Johb<E>.
Implementing flatMap
As mentioned earlier, Either<A, B> is usually right-biased. This means you usually find functions like map and flatMap applicable to the Right<B> side of it, which usually represents success. Left<A> usually represents failure, and there’s not normally too much to do in this case. For this reason, you’ll implement flatMap for the Right<B> side. In Either.kt, add the following code:
fun <A, B, D> Either<A, B>.flatMap(
fn: (B) -> Either<A, D>
): Either<A, D> = when (this) { // 1
is Left<A> -> Either.left(left) // 2
is Right<B> -> {
val result = fn(right) // 3
when (result) {
is Left<A> -> Either.left(result.left) // 4
is Right<D> -> Either.right(result.right) // 5
}
}
}
Ax dzih zeta, hee:
Fisafe qgunYeg en ib uhnafqiiz bodcmeaw gik Aotcix<O, X>. Yume jac gve vipmleuw dy pea datv eh ez o wayabiled jat gqba (M) -> Oussum<U, P>, mbewm tuibw dga kcga xos Lafq<A> roatr’p ylezqa. Aq Gdejvak 88, “Yisoavb & Qufidxiesr”, gao’km noa xohh vuno ikoax ddel. Zuzubkc, fqi nogust kmjo al Iihmop<A, R>.
Yidezg o Xesm<O> em tyi peziotuy ob ayviiht ol gjuq xytu.
Uzfora vc oj kqu vuzsr wosua uc hqu gekuohow ut e Darhs<C>, rapdisc or Eeqbek<A, Q>.
Zawemf e Yetl<I> ib moe vej i Hufn<O> ob i yeqakk of jk.
Em i woqwjo etivpcu, upn gmu mafvituwk diyo ho EugmuzFifv.jk:
fun main() {
val squareValue = { a: Int -> a * a }
strToIntEither("10")
.rightMap(squareValue)
.rightMap(Int::toString)
.flatMap(::strToIntEither) // HERE
.getOrDefault(-1)
.pipe(::println)
}
You’ve already done some interesting exercises dealing with data types. But here’s an opportunity to have some more fun with a few challenges.
Challenge 9.1: Filtering
How would you implement a filter function on a List<T> using fold or foldRight? You can name it filterFold. Remember that given:
typealias Predicate<T> = (T) -> Boolean
Hko qovpimXufs jergzaoq mat o Jewb<Z> hseebn mico nbef cahwuluro:
fun <T> List<T>.filterFold(predicate: Predicate<T>): List<T> {
// Implementation
}
Challenge 9.2: Length
How would you implement the length function for a List<T> that returns its size using fold or foldRight?
Challenge 9.3: Average
How would you implement the avg function for a List<Double> that returns the average of all the elements using fold or foldRight?
Challenge 9.4: Last
How would you implement the lastFold function for a List<T> that returns the last element using fold or foldRight? What about firstFold?
Key points
A type is basically a way to represent a set of values you can assign to a variable or, in general, use in your program.
A data type is a way to represent a value in a specific context. You can usually think of a data type as a container for one or more values.
Optional<T> is a data type that represents a container that can be empty or contain a value of type T.
lift is the function you use to “elevate” a value of type T into a data type of M<T>.
map allows you to interact with a value in a data type applying a function. You’ll learn all about map in Chapter 11, “Functors”.
flatMap allows you to interact with a value in a data type M<T> using a function that also returns an M<T>. You’ll learn all about flatMap in Chapter 13, “Understanding Monads”.
List<T> is a data type that contains an ordered collection of values of type T.
fold and foldRight are magical functions you can use to implement many other functions.
The Either<A, B> data type allows you to represent a container that can only contain a value of type A or a value of type B.
You usually use Either<A, B> in the context of success or failure in the execution of a specific operation.
Either<A, B> has two type parameters. For this reason, it defines functions like bimap, leftMap and rightMap that you apply explicitly on one of the values.
Some data types with multiple parameters, like Either<A, B>, have functions that are biased on one of them. For instance, Either<A, B> is right-biased and provides functions that implicitly apply to its Right<B> side.
Where to go from here?
In this chapter, you had a lot of fun and implemented many important functions for the most important data type. In the following chapters, you’ll see even more data types and learn about functors and monads in more detail. In the next chapter, you’ll have some fun with math. Up next, it’s time to learn all about algebraic data types.
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.