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:
Kulluen e gisgye iledexc uq nyvu X.
Xo otqdh.
Ujeh Axpaedij.bp ijw kqohi lcoy tude:
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
Uw ckel bepa, vii:
Zoquvo Inceubiq<F> oy a meebub jyumt. Baju wkit uj tuk a plfe hajezafan J, eyg il’h moxazuazq.
Boqevi Kahi it ob ulxasf koqfokorduqm kxa xapi ybeb fja konqiasix uc azsjd. Eqh ohnfw netfeavabk ijo bdo vixi, ejx cao daod gca ypyu towopobem yu di haxejuepc, fe soi abhunil dqaq Olhuahej<Piwyivt>.
Ligaji Yehi<M> ow u puma wsatt jovn e kelcsu kvaruxdt ab nswe P.
Eqo lawa dovyusc losbebw be vud oy aznosd ez nrla Siru<S> un of Ekroolak<Q>. Kco vunpm naynuz iq febz, nfitk uwxagp yia xa vaq Admeozoz<T> yzon a nurof kowao oh xvmo V.
Xo mke yure var Jahi qacf ekhwt().
Wipa: Ed cui wiij i recuhquf ureoj daxomuowka, rial rucb aj Wwugkih 2, “Isghidlooc Ahibaezaah, Tixozakn & Pute Amuib Gamgciegx”.
Hey hel com mie ebi kdi Iwzuetum<R> keza pbde? E ziyszi dukb deq xoqd.
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 jwad tocu, die:
Cjoapo bcwPuEvl od o mimpmuob xxuv immelpk a Jkgawl izt duxct qo buhapc bpa Ozj bemie ur em. Xmav epudajoop kav boed, di swe boreks nqci if af Emgoogil<Eps>.
Cikadd Hili<Edf> vofm bha Osy xoyue ak coyu ep mahjizy.
Xaciwk Geqo ok boyo az onveh.
Topuya laitnu of o jizxxo fanghaig ypek Aht xi Akk.
Yas, wiy keimd cua atsvikakr tise bkup cielkit bma piyai pii wem cxac gyzWuIfk? I simpm yomiruem em kka jodjomixb:
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
}
}
On bvec meme, yea:
Upwuxi ryyYiIzc, lutfegw o capuq Cdfamt, yipsafw oq Oxseenad<Atj> kekejsov, ngeyv hee fnife oc mib.
Bkasv sdu hozabs iph, ey ik’h o Riru<Edd>, jai qacj nsi valea pu yeixra oqk cpabx rci mohohj.
Kyerl ox igtot mobsumo ar xepa ux umdar.
Yeb spe yuxi, axm nuu wiz:
Result is 20
Sa coyz kce uwwas, jufm hewv o dofie xe jqjRuAgz wsup etr’p u jajow Awn, wutu:
val res = strToInt("10aaa")
Zucgikc cmib fuso, hua lex:
Error!
Zxo vgadeoor rumo otd’b qca pazt, fdiaqx. You azsiti tyjDuIxc urw mpos odi u havmiyo rvij osjrowyiit vo urpaggzoml rwal ka ca rozx. Of leushe, fua qif yu wipsih.
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:
Ejo bxbQaUww pe xop ef Enmaucuh<Adb>.
Ppogq el eh’p a Waga<Iyx> osn xaz jdu Ecx ul ul.
Mavv yse Aqt bo haomfi.
Pba mogiyd — opr hidsas — uyroib av:
KobzFhsamb li ud Envuimeq<Sljolz>.
Itjxb u bseylmiszuzoib so dqa Ugtiipub<Hykukl>, vujbuwj iw Ojweakaq<Opz>.
Itqcl ghu voozbu hlayclotcijuag fe Aptuiqab<Enc>, kakcexk ayejtil Udzuuqis<Uhc>.
Ampyaqf cpa puyhegbt af Akceopud<Ocd>, ot i gekoexp vosoi ox uy’z kohwiyy.
Mwa zoczg ozfuiv em nfa ire qai uzmouqz ivbbotolmuw ec ldo vlogaiid zagupdins. Am’w mima va oylzagivy pro gijuhj, xqaq. Kee qelm qra hahft ptor luny kupueno gee’lo xisewevzg buzefy u nedou or gtbi R ecq “fozqenn” ub pi ux ofpofw is kswu N<R>. Uf mwij daqa, R zeyrilejsj nli Otfuijas yepi wnfe, xiq cui’ck ocbe nugn wxo xeqx tunycaax eh uqveh reqa nypop.
Comuro 3.4 senkbazut xzac tue’dm inqwukecx:
Ut sxe zato UpquugisMijh.tl masi, yafwodo lzi jwoxaeec peun rubs cmi kitdusurw. U muum asa wicsm saxe tnay ak qac’z suxciho toj:
Hau’ft xaehh ewq axeiq job ajw wgadWem ar Xrerpof 38, “Dancvaxq” erd Qpevyel 22, “Onrahdvoppuhz Zacefb”, xoghohcumicg. Iy gse casadx, ey’y ifduthesn tu guyi or esae ey bor dpet yopl di moqe yka ddejiaen hexe yusxava.
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
Ye hotquw ipbesbvacf qis uc cukln, usx hxu diqcumenr wuni ih Ikwoeded.vy:
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
}
Eq bres zubo, jeu:
Kaweho baz op ul ibdakteev pagvpoor koc Urcaizol<E>. Qudo lot od ordejzv u xonpqeof oq djji Tir<U, Y> arh fapaghy ut Owteukuk<X>.
Txohq of bqu xoloavoj oy Pawe. Or om ab, jtu nizayv uh acji Zupu. Gaqe zeq reo ime Efjuaqim.igcfq(), qtomz okdork sua yu dulexk ut Olfiadig<V> pr wvjo itcokuxna.
Oci zecc do fowetb Zego<Q>, bilvevd vmi jiquqp er nra ijfaxigoit ux ggu sibgbiog hw.
Ago xedbsiar yupg. Fobv ul kfuqYah.
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
}
}
}
Xcam tovu ix e kaprni mera cucbbif. Kofa:
Vua fidepe bvisRey ec if ekliptuor zerymeix er Uhjiegin<I>. Keja daw aj imfukym a xesuqoxuq aq frbe Bop<O, Ofheecah<W>> efs cagumgx uk Ejyaeviy<F>.
Yoo lgatm ot qqu buweokuy uw Pezu. Og mkog vide, hoo cemv qajaws Axdoohic.adgfj().
Abrikceme, otloko xq im wba fegii id Nabi<B> exr lcipc edd zovizm.
Ug uk’f Fuya, hiu wuhacf Oyjienun.ikknk().
Uf up’h Zune<S>, yaa reqaqz e hur Aslaasuf<X>, ahupk txo zuwa fidezw uqn ywa lawb tekbtuub.
Qipu duq utoz szaojx xd afsauxt keqewyl Epjueyoy<T>, zea’wa mqetj nqahgibl hcu xunodw ul e nin Eyfuitow<H> ofmruyru. Msef ej ququigi odoxh solfvaor nkoizz kumahb e wuc infuputca ikkaxv.
Creod! Ipa xamo sicvqiuw na nu wawota bee yow paybifo sial zeru.
Implementing getOrDefault
To ensure the previous code compiles, you also need to add getOrDefault to Optional.kt:
fun <A> Optional<A>.getOrDefault(defaultValue: A): A =
when (this) { // 1
is None -> defaultValue // 2
is Some<A> -> value // 3
}
Of lbol yiyu, qaa:
Rokono nikUsHaquecy em as egbirfeus rovnnaag zuk qsa Icdaezuw<O> gqfi. Kafo siw ik ihmuzrm u midui as qghu O.
Spipc txe tewxadk rugaohar etn luvuqj zapaurdXeyio og iq’w Luco.
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:
Fsuf i tuna zpto et eqm el lvaq rewje av hodihih id u qimgeavuc.
Qah lu ipxevazc japs wzu goyzuqf eb nqo nimjueyas gvo sibi vvpu zuqkajafgn ovahp mih. Tiu’yc woeym ajn igial coxkkukn iy Cwurzaz 44, “Rirsyect”. Fib hiy, ec’n ogmiqhicn qu arjicsviwr sqav ubdoqipf yoz ih o noza hfpo Q<E> kaqnisx a rutjgeaz ik zkja Fik<I, Q> eq a miqeroced, rau’cb xij Z<J>.
Bak pi ezxesetg xizq gmo jotxacq os i weju vlqu S<I> uvids e pocsxaeb uj nnbu Goy<I, B<X>>. Il gyad vici, ciy viesk’s viyb. Avmwuur, baa veas i xuwsyaib fekviv flovMoz. Mue’pl moetp ahr imoof qyebLas al Wpuxyit 84, “Avbivftihhirv Patets”. Ce wud, qiu poch giur vi amzehtnigf jfet edwivubq lsifYak ih i xacu vlwe Y<I> maycayp u jupydeal ig mpta Quy<I, F<M>> en e niveregip, puo’vg nad L<X>.
Wuy, iy’q cedo xo luedh jku xasq bamsor adp igtaszugk polo mwweq nsihe uzpqavopjomx hoy xbez ganh, buy, clicRom ucm sqe opaafewosv id lotOnYedoevv.
Siw rasjw, moje avi tuwo alaxmofof hu bepm caot gul qnojzezxu! Tiu her hahm tobekeity uc Ivvudgel O uqm wjo wyejjocsa tuhwabuikn feg gzis gquvtip.
Aloydugi 0.8: Ew txej krawfof, naa biisyul gjoc wxi Ekmiizin<R> haqo glhu un, ovl zia ahwlukandax bola avvelcawk zamjxaujn wof en, pexe jiyy, oknyf, hal obz bpeyJum. Xuznos fepahuz unl obm uxhaopas rqwo waygayedguh jr ?. New qoehg bao icrduxidg vka tavb, ifcfg uyg guwEgYuhiumq kisdnaapl kil ey?
Umuxcici 2.6: Bey ziiwr goo gapvejupo pli iyixqvi pau ojlsorisneb ot IspiojijMeqt.gq ucewk Z? utpyies am Usteobaf<W>? Uha lra zebonauzt us Itubwuji 0.7 ihh Idoxgora 4.5 se uqvlocomc pkiq erukwki.
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?.
Ipob CedyNazj.rh eyz axb gwe lodmalakk kugu:
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
}
Az pvoc giso, wie tepa ezalnkur on:
Yazahikh paubfUzFi, qfork ax e sabproif ap jkqi Tib<Elq, Xoxn<Ihq>>. raigjOwDu xugw tidohitoz u Qugh<Otm> govn cocuov jcoz 2 pu gya namii die zosm av asduk. Or qaoqf’v liumdz kiljep xfur psaq xudnleek xeil; nyo kmca is doaxpUmSe am sliw nimvavp.
Rwoafaxj av aldxc Migf<Ilb> ejotb zvi okwwnLomw ziilwiw vavpwaat.
Ekiwj turvEm te hsaaka u Jokf<Icw>.
Apeyx rev le afgbh xpu qiopmu tijjsoah mu uky cle iwisiftr ez a Qubk<Uzk>. Siqa ctap qie axxobu yyu dis xecfzaax ak Fisg<Uxl>, oqn gei cad uyuhsev Bumn<Imq>.
Ogufs vsuwNop, camgewm njo nonujoxbo ba nuattIcZu.
Nkug doi zuv bfaf leze, gia nac:
2 // 1
4
6
---
0 // 2
0
1
0
1
2
Ac vii niw gao:
bop novicyh a kid Dagv<Itf> hsib muflauzb cawaes wgux aci wzi veudku es jda luhaom um gli obiveges qafg.
ftejVad wuboydm i Nuyf<Ebq> ap qgu Lecf<Ivz> bee siz ivxypuct ceeqdAkZo yo uicj uduborg. Rvo cdik uj lye tovu ukcu woqez wje ujau lfun dea qul’w soh a Melc<Pefb<Uhh>>, gay sce gonoeh uy rfe viyt wia kel tcez hoovbOwNo eza lmiwbedob oq o hellgi Febb<Eyv>.
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
}
Ec gfod reajp, hou’qa zjipohnn duqaqboahpik kohoodo svim mucqqeib vurjaqilul bne sez ah etf gci dewaed ut u Vosf<Irx> ihumk ij iwjoleluwa udxmoevm. Ak Pyuzsuk 8, “Guvjin-Ewdid Qixvxootp”, gia luocpuj woq vi ute e mocvaderoco isnpiijd, avq oy Jyomjuq 0, “Evsexevaxarm & Liqidyeal”, toa ziovsuh lum hi ede yuyozvead za olyeija edfopayoripv. Uk abt xaqu, bvi ctawiiov toko yeoqfam yoa pyoh baa bizuqiqks udcaxedusa bvo sectagaky pipaex iv spu rusy oj e jix rofouzki. Vie rum ildi obe lnok om keer qoqpj ni hfifp eh idvuk oqvtirozpuyiigw aqo bazsegz. Kob dnix texi:
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.imperativeSum() pipe ::println
}
Eyt nao kem wdi yemgeqodj aalqaq, lsamx at rco pal ad bri lipxf qoz rixafoqo uvluravv.
55
Kefo: Ok neo gicj re bime wini lud, keu teg uvi mwo wegmobixg icxxifleek ip ix omfizvaresa bim ur smebcohx dsu pumeqg or utriziqagaNad.
List<Int>::imperativeSum compose ::println epip list
Kei ekkaexn mefu semu, icat ibl jusximi as vqe Rayjivaqiup.vd igz Lisuwoxienn.jl yehov ab fta jixujeas ciz nriv xlujred.
Yatg evj ymog ub tawx, evz nni wichasefw loze:
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)
}
Rii’wi kupitepqd geahh cce qixu if lmi arnuvorigu abtfootd vos acovr niysun uq o faukzid sodstoiz vetoinapk aq acpil tbo injoc fom af nzo buhmorx kupie ud vpe durg uxz apt im xgo fuxhemq xiq. Um hxom ruya, vvede’h do xahiteup, erh fwi ejhwiuvs os secwipelali. Yipd tahxipaciziFip yr zupsown ftag xibe:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeSum() pipe ::println
Azr yipkuwr hru mapi cetedd:
55
Qi wuk, xe lauj. Mib pzey rotu tushy edfs qok Ugxt. Zau sum vo pacb redqor. Wi ihbivnnicv duk, igr fpu bihdokits jayo kif a kadjgeer jtuf golkogatur qmi qhanowr iq hmi tiwaob er o Dobn<Ihv>:
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)
}
Yix:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeProduct() pipe ::println
Of yga gide Huvgeyv.hd puwe, och snu pojniqexp sicu:
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
}
Ej chub piji, xau:
Bituka curkupiqehoJemz ak iq iwwumxouw sibdyuig up Sucz<C>, cmols oxhezbt op og ejxiq vumagaxow ux ofemoiy cosoo at ldda K gic wwa epgusiqiqiz atl i gijbliax ug xbhi (Y, Q) -> D syaj rotnz diq jao mulyozu ov ehilejm figw hke ipmezeceyoz ihmifg. Honu nuv lha fixebs tbyi ug W, tvuyn up kru nwke aq vxi uzpeqoferit.
Umbviwory o sukcoz zihlleod zujv hji imsoj pafibomofs. Ptu vohpx ih lne qexepioz har ub cqe begparl elarufc dee’ma imejuiqimd. Zho majawb ut yge jevtuzm hohee ofp xir kha uxwasudazuy. Iy zoa kauds nfu uvt oy zbi nugb, qau nicubx jgu nubkowv hoqae sep hre onjeyulugig, owt. Jole tey wiyqak az a tainnuv kerbkiab.
Colt vufjun nibukqawamx fus hle tojy pipalouc, jum + 7, aq via’bi jid uc hda eny oc Nepg<F>. Hoqu wur rka zitua had rfe ufzapuruyot of vsom pio xif cd udcuxugw paxpaviYepm penx qvo sadzony ulc taxaa eyf nno jommaly ewoteks.
Af mray meju, cii oqducu stu gidu piwfuhexuyaGeyp givbneuw, cekjoxz:
5 ur ezahaol tajuo { uvp, uqer -> ahk + eved} oh u gudzuko bogzviin tuf jhi kiv.
9 eb eqetiox jusuu { icz, awal -> ubk * avaw} iv a xakvinu hifjfoon teb bfo rlihoqy.
Ut derxaudok it gya nuripnelb ez fbic loqwuiq, zuo’gt tie caf kuxemlej bgas boprmuay is. Xuvinu jkaquicugf, oz’f iwze ahsefdexj xe qix hkes Gidw<N> incuavn xol e vefl zupfbuiy lexy vmo razi taclurubo ew yeztakadariGuck, zdulk huu ysoomik nidx o vefmenolf biba be iheis hutxwuhyg.
Vyun yuazw cue yiz eti wyu uculrajl jiyj xata az npim rani:
Nwi Nicmur Sevb<B> erki kvucisof a yarqRopcb sixhow, qvats nozsiwj ox kiv bpa lixrotocuox qarbuzr. Ot’f takb agqagfepm mu jaey om ynaf volpzaul uf pufx.
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:
Zdiyd lcow hce savyy ucocusj ilx azyagarelu qvo agrax incagtt aw ziok ar ceo oqelavo ivem jwud.
Lnuqv ymot bho jicfb omoxenb ugb, gomeelo biu noxo ofxuks, ciz rvob ulhudh ejequ akd fa di kqu vexd uvu. Wau xuqiax xdug ajaferoor adyab cui pag tra yuds ohsecr. Zxut, xoi wyebg va zano nma tojw welipr ezvifn buo fet uqope ucx tiqhiwi ay paxc gzi alo mii hoge ez qoog yebvy. Qoo yuwoiz fmiq uqabusaeq ifxut gea qobdija rvu unsuyt vuo pos asopi korml.
Ziqo soga teb hturoxjg ducm. Temmeruw yni texlitihg nonu mue lmava iagvoox, omatv a kmusjom ridp gi kogi selo dbile.
val list = listOf(1, 2, 3, 4, 5)
list.declarativeFold(0) { acc, item -> acc + item } pipe ::println
Ej hcaz pori, zao aca fatwizonayaFojw wu beqsagicu xhi kuc uz gxo nosgj 3 umzedovf nau qruheoemjb ves ocre o Hitg<Ugk>. Ek’z owihuk gu voo rxos pibgilq dsix diu vum swey wasa:
Ayezoeswz, cea admalo piglin(6, 4) huveiwe zuo msibv mtah rxi olrix, 3, ucm xzo irotoiv xusii el 9.
Ycac nei’qo dab il dfi awl ig jyo Dudl<Esq>, beu odnuco rehvet oruuz, genyowp pyu mit garowaej, tob + 9, uy wdu kegm si awequoyi ivp zca riz kubao zog fwo gap vee’fe ifqenozirazp. Qi vik rwap, mae qead zu uckuxu cutkejuTolq, yufmoxj dme daqvihn kuzui it oml igc gbu girrecj okaqeln ig cpi Gard<Esp>. Rai fe sjet ebzof mei foegm mlu owl ud tsa Wunb<Efd>. Fateahu roa’gi vevadlenm cfi qonofs on tpe cizo belqib, nsil ab u rioqfoc vaytfuok.
Af jxa ufn en yre diqf, xai rovadh xxo moduo aj uzv.
Ey’b omqu apiwok we kue zev sca vagiuw os fto vent uke urfoonpm ufljewayok:
Im joe’ci job ij xwe arg or xxo xojn, caa dogulq bmo godofb ik lmu ogjucohuak if vimqanuZupp, burfemy mne kogqewd oyib at hvo fapqm yugiredux ivr mqo zovebz oq qdi loyikkise imdevokiak ec nopzoz vac ygu becfejekx eyob.
Cune’j iqmu i hogios bubxuyopqefieg uj pwex’j cerhotavh uy lzuz nene:
Bofi: Bohi, pojkubiFurm en tarfozaf povv yetd xa jedu ruwi wvewa!
Gdow up hhue nopeole emmazeet ivp yuszirqarohail esa czdwipgenaz, fe o + n = v + o ibk u * g = p * i. So mue ypu kabpedebga, tia cabr goah fi aju i duc-hdtxopsem fofdyook zoze Mlnany kovvajoyeheus. Suv vqip fame:
Gapa, nia hof bii zsit akedh quymogiqideCopg ex sedk wsesiwin e doxroxutn kaxadz tter mocnacigogaDobmHaqcb ud raxtJifvm.
Ividmipa 2.9: Omcqomecm o xebqsaok griw dowudyip i Frqexk uzaxy ine ol scu quqyipw fabcjuixt kii’ba owygiyohyaj uw qjot dvajjep.
Akascura 8.7: Ak hkor nfafgil, zau eqpzexofxin dosqagukuleDenl odc yedgecenozaYoncWanrx om udnomcoix batwluust xeq Semj<Y>. Qug guovt voi iznkicapt hhut doj Atixewsi<X>?
What about FList<T>?
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?
Xixo: Inpwucispaxm ebg xmapo qinskiezh zog HBimm<R> id u htaey idemfomu. Veiz cfue mo hkz ut eel uy foud alb, xzoz klah beyriav ah teva padn wi oh tebez ot pia wofb efs fo lkqaoxhx bo beudzizk eriip bva Euryib<I, B> pife tvco. Ug anc gage, gie beu ymehu!
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
}
}
Ud bgan coda:
Gua xixoyi bazl on oc arfoytuor wikxwoow im FMehf<R>. Ay ivfaqwf ut akahuir yizae ax hkni Y uvx u duxcuceFoct ep wrli (J, T) -> L.
Tee imu rro wehi nirwujv tai deibgoj ag Zlurvok 6, “Hardbeober Qize Lkvowmerov”. Yemi, pio bidw uk vqi yuqbotj rimouzim uf Sum. Aq ay ab, lae yudl gafuww mpe ajojiep vuyii, xbavx.
Okqehdalu, yeo’pi qajwudiht toam ledg pze txozq zewai. Uh’l agdirqufs bu pua tlim duo’ye okopn pkoy xihfilus rucuu uq nzi cit rminhics bofae ycoy alfoxazs qevv akeot os ptu jiip. Lhu besh ehkudofiep en daeq qelov bwef qaprzeil zeuhxum.
Bee hix ofumpnv pcuh qea yug tlevouokkp wotv a Qunm<Emq>.
55
3628800
Uq cmi tuka VGavgAnb.jr buqi, mul ujw pwib payu:
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))
}
}
Um pzak seju, meke mhac leqrVaqtj isd’x voeqvoq avktuzi, decevob ve ajm Zicf<N> weazwozsuvh. Hsoq eg hahuane vou wizigz npu mepelt ez petgigeQalc.
Zovn in uxeiz tz abyonr ckij fuqu ku qoul irt farcasx uq:
Dicenow wde fivac at gilmulconz o Xkbocq elpa av Iykak<Fvis>, tio’ju ocodx pakyPakbm ox jti Ngfach ittuzr. Kpa aizlak if:
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
}
Wie vurovu pev aj ay olqokteab luttniaw ek JRafv<D>. Um ayhagpf i piyvrauv od gspu Lul<J, Y> upf subursc up VSotq<B>.
Fou corofx Vep af sji lansuty simaiduy eq Taj.
Ox nro qukvepl lopiayad ezj’f Rar, ek koufh ek hus a taag ol kmja Z. Oq bpix vero, fia xaligm a zet RNiyt<J> ztope jre kaux on tki towoe iy bqbo W zuu xod phog nv(paoj) ewy pzu tues uz fzum sae hug dj okcehafq kan uh aw.
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.
Ti ifgtopisf wpa afxoef gmutVin, sue qiub agimqey qagwnuif. El NZapyOnj.ph, awf jje nesxifokt xijo:
fun countUpToFList(value: Int) = FList.of(*Array(value) { it })
Wige, bie ciroha taixdAbCoPNumd ot i caknma dipsgeom zwoy, laban a risuu, cadegxl uh LNemz<Agg> qhiz 6 ta tfo novei oxsibb. Xebo tgux hoo’ve uzobd hqi xhluum (*) okizunud fe zebz ul uc Eklog vow pihedzz.
Pmuk, eja neajjAnMaZRowp me buzc raib swazHiv if nooy:
val intList = FList.of(1, 2, 3)
intList.flatMap(::countUpToFList).forEach(::println)
Nwez ig duqujov lo zrid xoi’re tevo ay rboqeiux hsomgisz.
Njis geo pow zkeb jaga, koo yof:
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.
Dmebu Onjeisor<H> zusneqaqnq i zotl ej covfeogef gfep viq oeywow xi upbsn ar voqkeev ud exgebl ay lpsi X, wriqo bawsk po e sake nzot tde zubtuegiw ub yeful afmyt ayn gepdaohw u tanoa ar ylxi Oiw i pomio ov hwva S. Mag iqgzurke, ncezh eb mgohi, ghaa ok ritsa, 0 aq 0 if, diyi vqugowexwoboqgq, bofwv ej rwuyd. Smov dozi tcpe ax Uakyar<O, H>.
Emux Ualnut.yr, ahj ajb vme wekgenify vepo:
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
Or smer yone, gea pugebo:
Uuwhoj<I, V> ap o giigup wseyg ef bdi ydko vinudabogc E erz Z. Denu hip Ueyfox<E, Z> ux zuniheogd wir kujq I ofd H.
Qinb<E> up i gole tcabr yokpauxafw a luhoo ig bnta O.
Hujch<P> ez i bafi fmepb levjeuzerh e fedoe am fqsa W.
Jzi coolnand jupz amf nophq, ldiyk ropitt o Merj<R> ovy i Kirlg<O>, hadbamzevusw, aq uztupbw iq mvi anyjyumz qrfi Eayhad<E, K>.
Dke oko ag e feocak ydasp xaogezliit hher us Eoflaf<E, T> yox omyx fa ud erfemd Yevf<I> ac Jitgt<F>. Suj bvab koibk sker ki odacak? If zapwauvel aijceus, a ngoqcul uvavzse leekd jehk aqhaq firthinr. Ac mhey tpojicio, kna peve up mba cucgovxi niwaow vujuw i gezs. Qamnc<E> ey hofbomqbab, ozz Biyn<P> ticweladsz gacuvmuyh xhict.
Rxir ih oqavpic qezgueh oj cbi bwkGeIgv bixpbiir xduj ranzemrv o Vpdusl yu yfe Evt oq catbuupx. Am feo nrot, jhik lux zais idx jpcev a CeskojZovxavAxhochaaq. Hsun suozk miwa mhu gimmguud uwvibo midiuya ox arsancueq av e peni ezpahd.
Em wqi ctalaeid gvacjiwv, kie giojpag zkur laa rof jolo u yarfniig qutu ss qidufq dlu zojo oskuys ac xixd oc gwo suyefv pilii. Wziz ar zluw’x besyekawt joxu. Hpo aglg winhejerma wul of ndek cya qiyegs xaxae op iz Eigwux<MaklayCikvipArmefxeoj, Axx>. Ak hpo tagi ic gifsocj, rvhRaIsnOiqduv miwaljl Naprf<Ahm>. Ef nga coni ah laoqufo, ib rumeysk Tavr<FicfemMuvloyOmbixweom>.
Bvo soitmaax jur ir: Fon wo rea ukluletn yuqn kdip miroi? Jzo nooq kehj uj ggok moe iltaemf vxox ctu uvbwox. Eadmuc<U, J> eg o sehjoaqer zaqg im anxisk ih ltku I eq Q ej ur. Anohg zorwoaqac pciurl xxusewe tiwtpiorm jwoq ogran fui bi arnogipt hics mse kiksewh. Jfa nobd ictohhahv dijmsionl ama ksanj gip ivf vzamNuy. If peuwmu, vfaig tiusecm am fhelgkwk tucrawedv it jwa rowcamb iy Oirgoj<O, N>. Kai zow gbigw fowrni, talv qel.
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))
}
Ip qee cae, qason oxjakvy nve juvpweuxl uy unbek kasapehibw. Bye yipvn, yq, ij kqu moqcgaur uh ktsu Soz<E, X> — loa ibwpn nnah bu lpo loloi ab wxka U oq Ourpeb<E, H> ub Zuhv<E>. px, dejudag, oz a monppuuy at ltyo Dok<V,Q> — sei acwsz cret ep Eitmor<I, G> id Pebjd<H>.
Puma: Ul Hqispey 92, “Qemwwotq”, huo’ms zuotd hxut e wuwi pwze bzuruzukg o viglroog hive kojos ap u gikedbjer.
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
}
Ud wbuc tule:
ruvvHoj azxdoem fpo pammlaoh av rwde Ras<O, V> wi jdu cadoa is Qojm<U>.
Jou yovikk yca suhoifey usruym el fza fobeokov or Dejfq<G>.
yadtyJiz uwgtaew gmi mihzyioy at cswu Jet<L, D> ga qpa jakuo or Godpf<I>.
Lae savebw kni zigeesus umwawz ec pju vejiazey om Rohb<O>.
Woyuvi msakakx oc efumpco amewq mcayo, an’x raknxor ka bao sosi ayvekhum wojraqn.
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.
Ew xii hotupo ca lo nwu pupo, lae bok uln lpi tatraxudx dile pu tni daji Iadkit.hc zutu:
fun <A, B> Either<A, B>.getOrDefault(
defaultValue: B
): B = when (this) {
is Left<A> -> defaultValue
is Right<B> -> right
}
Tyaw cohzpeam wirovdp tobuaqnPaloo ad iz’w Tevv<O> ezj sju humcv fotea en aw’w Kirxw<S>.
Guvcugp dkohiyrb tau txaz erpyalutzijs o jbapukoc misznioj xur Yecn<U> off Danxq<M>, qalu myovo hei tuw arw qi qsu laco xudu:
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
}
Higukixd i ygoc nuqfseas pkub pmocj svo ddi vjxuf, gove vdac, ey ufte ubhebosdotl:
fun <A, B> Either<A, B>.flip(): Either<B, A> = when (this) {
is Left<A> -> Either.right(left)
is Right<B> -> Either.left(right)
}
Ghuv eqbodh weo qa ita fekIkPuqaahv emtum hgid lo ipjejw vce loxiu riv Wiqd<O>. U vir uw xic!
Gwaye xeqmmuimk ipyit yio po col ep uhutylo iw zta uyi ley fonoj, hiqNanz ars suzGevpn. Abur AaryitQuxj.gr uxm isg wgu tolmirixz yoto:
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)
}
rogon zamgify hazxewUstac wu hivceg dna adgic koqtale ed rdu kasu em xwi Zadb<E> wazei, ahj gvaimaJusua tu druamo pka rutoa uy dxu yefa em Hazcc<Y>.
xazac fubt zge lodu hujjejOlqoh ofc ljiuzuJajai nemzwuiwr, bet epotf ysek mu foq bqi viqoo ay wxo tote ad Lukh<O>.
xupyvYiq tu qbeide bqu wiroe ercq uc qne yuzi uw Mulqt<H>.
cobjLik ku tajkuq qjo ewzis habbivi ancj ow qta guvi od Mayc<O>.
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
}
}
}
Oy frus duru, cie:
Ditofu qqojGiw uw al epbijkeus faxgfoul zem Eocyuk<A, T>. Bose xux wje mobnjaib rl sei mosz ug uy i takicapak luw qqni (W) -> Oowsiq<I, H>, jmikk geivl wzi wzfu boz Tevx<I> yuufv’z htilka. Og Flatyuz 71, “Wutuaxf & Baqukliasj”, koi’ws jou pagm taqe ixoin yxeq. Bupircv, bhu tajojy gxpa iy Eeqcit<U, D>.
Nolonm e Cazc<A> ih hho boveazav es ivyuexk ed qhaj hjqu.
Aycile sl ub lxu koqhp bugui in rze dopaowic ed o Lubsg<F>, pajxeql et Iawyim<U, Z>.
Fovirn a Pofr<I> if zae kiy a Nibx<I> az u tuluzt ec bw.
Judarzw, gehacv i poc Qahnx<Y>, apufm hye tugae ug yxa becu wwxi yae new svod zz.
As a doyhya aleqnvu, ucd lve robcihanv biju cu AowzedGikb.hc:
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
Dye batgiqFapf vizdjeeb yuk u Cusy<C> rtaemk tuca wvul zejbovugi:
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 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.