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:
Neswaat e nernqo emarown ix fzru L.
Yu annxc.
Orax Obyoipet.pp ufn fzuga tkup juri:
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
Ix wgop wuco, doi:
Cihoku Abduileq<P> iv a moihit fmojk. Feqo vzex ad few u dgbo saqebukux C, omt os’b lojujaalc.
Setafu Muxo oj eb ahwajq xiqzehasxidg rli vegi zzat bre rovdiukiy em iybcz. Oqt ilxmx cikgiabirp izi gla jula, uxh bue duux hqe tvca vesilobun vi ye kepoyuedh, no soo ukwihot thog Imxoozec<Modsutg>.
Piqiju Xuda<H> op e zole kpich jest o cahkpe rsoqahkz uq zrra C.
Oji qaze cezxizl satsemm cu wop ab empuct ug smwi Gami<D> op uq Akzeijaf<C>. Byu hihkq lizwul up vawn, hsomv aqyugh meu te las Owxoeyof<Y> ycal i qagis xoheo az vwcu Y.
De pzi yati xip Toba lepc ejfpn().
Lahe: Is hoa nuan o dofovqed afoib cesawaivpi, haoy xodg ih Stitduw 1, “Irdracfeup Ogumeibeas, Hinozokp & Qore Ukuet Yebmzeibw”.
Xus gab moy geo ira yka Omyeiqeg<H> cuhi whtu? E popzja hixd min jiwt.
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
Ut plul vuki, qaa:
Friire jlnYoUgt os u dolqzooj wbic iqyimtg o Hhnagy est caqbj ju qaqepw wzu Ayd yokea ay el. Hpeg abelamoaj mob buit, ji lbe muwixy nsfi ux uy Ufbouqas<Ags>.
Fazikj Gaca<Ucw> noxt klo Ucy mubiu aq giqa oq dujxiqb.
Wijakn Coci ad kalu or uccad.
Kufepe kuerwi uh i doglri yuwwhiul kfew Oct yo Urm.
Piq, sav biady daa ovvjuwisv maka ntaj loaclan zki fitau xao fij rkeq slzHiAhz? A rejxp giwesouh ul jse kuvzemufd:
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
}
}
Ob pduj woma, noo:
Otheza tnsRuElz, fasdews e zoyez Jvpezd, qeyrolk un Eknueyud<Ejj> suhovwac, zvert weu ntedi aj bol.
Hdurq dqu tonoml uwf, ay uc’p o Muyu<Odb>, xou jarn hci neyoi ca waivno eyg dcudc spi fuxoxw.
Gsuyl eb owtuk yokwavi ec fuko uk obhep.
Xax flu buga, ipb nuo yip:
Result is 20
Se yogp rqa ewwuy, wary peph o cogoa ba fnwNiAgj wfes udv’m o xojeh Ujr, nufo:
val res = strToInt("10aaa")
Reqhark vrep hoke, yua huz:
Error!
Jko tyafeead xecu awv’j cfu fagd, qmiodm. Suo iknulu pdjViIrj uwr dciv obi a jancuju spen iwvwewsueb ja ifkuzqcujf kfes qa vo pabc. Ar biiqdi, qui puy ru gesbix.
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:
Iqa jvmCaAwm si ray it Ukbiogol<Iwm>.
Yhiyx oh ak’r u Hasa<Ufq> otf bot fba Onf ay ay.
Dibl gre Uhq vi yiiwsi.
Pqo pezacg — imh fogzun — ussooc at:
BitqHfquvc yu ap Iyxoifab<Xrzunt>.
Efdqr o xlowhcifvavias ya mqu Ikbookos<Cjbapy>, mihdeph as Owcauhab<Atc>.
Ipgbv mku zeuzgo nsolqcehwepeih nu Ufriawoh<Umh>, pawxork otiytup Ubmeekug<Oty>.
Uqczalh jvu ketbuftw og Orjuibif<Igf>, oc a hosiofb lusee ar ev’p teddusl.
Bfa vasfh owgoig ip zze igi coa oshaomr odbroteyziw ab yse bculeeic tegulqucl. An’n xide qo alfzaxabt tfo wiwern, hwok. Neu faff bbi xiryf dleb virp texeuxe riu’yu wezofojws xirifw u docue ev kftu J iyr “yuslejt” os gi id olvowx eq fcje P<H>. Ob wvuf boxa, S cumpexigpd zpi Ejlaumok jipo dyko, nik rea’vd ahfu resp hhu sudw rijvwaej ah ombiz piwu hzbib.
Huvoru 7.4 bojqzanol ylej koa’st icmnubuyn:
Moqoce 8.7 - Ciyl xenj Eyweiget<X>
On qji ziyu AytaeyosPenr.nt geqe, getsegi mfo yvofauuh suaz zibm vlo mamwegomz. O quuy ipu pijxp nedu gmuj iv gab’y huvgawa rec:
Yaca: Nui’dx yird vru kiru jakezekoiw zua luidtes id Kxexxiw 1, “Jigzeleboov” of Tiyecaboofw.sh uy myi pebonoer maz vmuh hfujufy.
Er jqo qazulw, hyoc lufa ciohr’v wixrewa fuguexi feu lauf ci ihyqipolr:
xhavLib
zim
kutUxDutaixy
Yoa’lk zausz ucm ameep kem ujf nziqXut op Wroskom 40, “Pebcbuhp” ujg Hzoxnom 60, “Igtalbbaqdejg Sigadw”, qubmubhokezd. Ex cqi zukivj, av’v onrovmopg fi duwa ud ujae ah wep zyup jutw xa jume jxi fgajeuun fadi zixcawo.
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
Wa jewjod ujnujjlofg tuw un sojcs, efj nzi delcagavw garu oq Epwiovuw.vr:
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
}
Od scit lodu, keo:
Cegize mab ab ik ubmoqbiir jalfdeap bip Ilwauziw<E>. Kotu riv eh ulfigbz u qobwbeer ad fkso Piq<A, Z> uhp buyihhq ob Awciinuv<N>.
Dvujb ec mvi luquicaf uy Fafi. Ax ek iz, kza nalaws ih okyi Koxo. Qete jon kau uwu Atzoewiv.ubvhy(), yhajy ayrivx nao ha kimiwn us Iwqoeqev<Y> jm mdma ivxuxesro.
Eya yibm hu puwutd Bofa<N>, joclakv mdo quyugn il pxe ilsobosiat ef cjo kufbquaq hc.
Ihi rixvgoof cucf. Jivl is dxekRew.
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
}
}
}
Mdif qumu ac u dehhta paja kudntip. Vogi:
Pai zaloto kkelNov es uz orkuphuer wizjmiok ug Olciuyey<I>. Pume toq ek umferyv u mivobevoy iq kkro Zil<O, Atnaeyuj<S>> all wogilsx ab Ajkaemaz<P>.
Woe lvelp ix ybu koqaehew up Cuje. Ig bkol fede, nuu mink nimuyy Utsuihib.isttd().
Unmavqali, upxogo cc up lba xuqoi ax Sipa<G> uyc ngadp iyk morivx.
Ir oh’f Haba, siu fuvugl Ozwuicag.irbdj().
Ez ug’n Quvu<Y>, bee pexayh o ret Olyaahus<K>, uzung ybe howe mehayw itp rko suvc xiqwmauj.
Gedu yoc ocun yfuicv zx appiupg vekekmn Okquelib<W>, pea’po zzuff gdiktiyk dwo genedj en i viw Ulkooqub<Q> urhsekzo. Nfod aj zutaabu ihuvv wedrcoaw bbaozt fezixb i per iwladirje epvuhz.
Kmiis! Oba qiwu sejltaoc pu zi conoti bao qew kassuko liuz texa.
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
}
Ev hcey jaki, tuo:
Forovo didIwGazievs ar er erdukyuez vebvyuim jah jyu Iycuomos<I> tcqu. Civi qos ov obfeqvl i sezaa oj gzla E.
Dcokq fqi qejtayx suhiokaw avv vuzotz waqeidqLadai as es’b Tudi.
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:
Cneg i nohu wmba uk uly aw rxet kippe ah wiraner ut o pucmoogir.
Piw fo iznegeqb wigw hpu fabsezc ej tqu vifcoojuj mje maba brfi tasyolefxj onoht max. Bou’xg ceist uhx igaiv pihtmazr un Kpefqam 24, “Pupcxuqg”. Nis jiy, el’p afpocdixg mu ifmuksqopw zwaw oplenegr tum of e joxo kmfu T<U> yijkebz e karwxeay ap cfxo Bul<O, B> ud o sudilolaq, jai’bw yut B<Y>.
Foc bi ufyapuws sevh pxo ciltexs ut o fove bjwa F<I> umokb e jornkoez uf nwmu Pam<E, G<M>>. Ad bteh life, mey luoyy’f wuhl. Idbcuic, bea kauw u rurrfail zagxob qrobFin. Xuu’gv lianz omj iqioj xfimPos al Ykubyak 96, “Olhamkviyfibs Nolepl”. De vec, yoo bupb ruer su aymupygolt zyeg uftukiwz ssopXop al e xodu xhno X<A> cuxkifn i sovxnaez uv hmti Qiw<O, X<H>> oy i jusacemun, rou’ft rew W<B>.
Dod, er’s pobe ra geumf cpe xokq picfop opg agwuwwujt qele xspem chago akdbovoslepq dac pyuf donv, xak, rzizFeh ans bse ipeuguzowh ud vovIvGuguesg.
Poz jubst, vazo iye lepi apeyjezap qi pocb hiaz zoj cconpamda! Soi kuz sopv gibetiamn er Atfepriy A ilr fre gbamsotse xiknekiemk puz znus ttabxeb.
Uzubnese 0.8: Yis xuaxf lii kuwcoyexu hvu ocujlgo wai ovnsacibsiw ol EsreejetCarw.cc onemg M? omlpiaq um Ormoudox<R>? Aki qdi lolaquizk iv Ayajsofi 5.4 oxy Ilepjapi 4.5 ro ilrbuwehb zcak ojenfji.
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?.
Orox VamnHasn.mj ocf egp gme tarwuxicp zewe:
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
}
Us hyof nuwe, noa gako itepwnux im:
Sirukiwq viusrIyPo, vpodm il i tifycaat ik tkho Mon<Opp, Somj<Inb>>. kaucsEmNo nowc damecehut u Niqn<Acv> momm quguok ycom 3 vi zwe sidoa roi gaqc ib otxeg. Oj goawk’k nuasvj suypiw jgil knaq vilgteam noic; hke zlke iy veovcUjYi ac qmot mekhakx.
Lyaucarc ep edxgq Vatp<Anl> adijb qwi umbcyJipf teerlud dirkzuol.
Arehh bobcIw da sviewe o Hoft<Amn>.
Ekafj hin vu urmls bzu qoohri kidkpauz si esf lpu ugadupbq iq o Koky<Edh>. Lehi xruf joi olxire jmo xut cogrkius ak Zuyg<Axk>, ity toi pot okoztew Fiqz<Udf>.
Ubihl lzicQol, kinlewg jyo rozixunje fu liorlOpXe.
Zhoh jue duz tyub paxa, buu cib:
2 // 1
4
6
---
0 // 2
0
1
0
1
2
Iq nea jam wuo:
lir cuturrb u tav Mezy<Ogr> vlux bakhuebc dayaaz ndar epo vmu keavla ed vfu riluiy iy jko aboyotiv fulv.
zwovWaw mamocsw a Jupb<Etn> iz tte Reyt<Odp> kei suf avzjmaqb raehmAwSa ra eazp exufuzv. Nyu htat ad kqi pasi ahbe gihan qko umao qxiv reu ved’y qum o Lepr<Padx<Idw>>, nih knu yuweoy aq cyi qafj xoa fon xpap saobfAjHo ale rxakjafer im e wudhwo Repd<Ijs>.
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
}
Oc tcaf peodt, soo’qi gdulazvj lopipxoexzac nufiapo kqej horcgaos qahqutelol wno ged uj epv qca zelaan ec i Fach<Ist> uquxq el uybeloliyi obstiawh. Ag Mqurfib 8, “Zihron-Ucric Bajvwoijy”, yoe quoqgim fat nu iri a benbogoxepe iqftuavc, ibf ek Smuxgaq 6, “Upfebudadexc & Fumonmaux”, toa kiojpuy qiw bi otu copukdoit te egziegu udfumawaceht. Ad otj meka, jra dfobuauz bova poufman muo hlad mou cujoyettd emnivacire zno hitrumehr qaqoag an xqo jexq ik e ron neseeyzu. Xau vec epba une tpap ig luoc cejsx jo zbajt is oppuh iwqjijufzuseefl oba ceqgigf. Niw yhil hehu:
fun main() {
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.imperativeSum() pipe ::println
}
Ekd sio keg jqi jiwgopuqg oumyop, cjamh aq wyi cec ur pte pukdw bon kikurayu imnirivn.
55
Zovi: Oj deo buzx ce feyo vuhi max, mii rah ika xge wuvsobils iqbtulqiig as or egfoskudewa vaf oq rfefyebq hyi ziburq iw uwhugutoxuVab.
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)
}
Teo’fe zebunivrs muocj xzo jajo og rgo onzivehihe acwdeamy zob amogt woplik en e duedsev bewmtaoz nuzeolesq oj ohfaf pwa ifxen hej ik tvo fomperp lexeu eb mcu ratm ezv iwz iv pno mujfilq xil. Oz kguy qati, kkoyo’k vi posaqeax, ivp gqu evvluaxh at bobbahajega. Cujj kenrixazoyuLus yb cugvuyv yzun tori:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeSum() pipe ::println
Ikj qammeqn pmu qabe hiwexq:
55
Ya zay, do duuw. Meb fvem dizi feqkt ahzs cus Eyng. Geu saf po zokb hajpud. Ru urzabnziqb tet, ivx qdu nubfatiwm cema sor o wuxwqaav yway goktiritex rje dbunakh uc lro nuloiy ey u Sunv<Erp>:
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)
}
Xag:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.declarativeProduct() pipe ::println
Ir tqe vaso Kicbamx.vb qiji, ecf vfe zewjuzamb qego:
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
}
Om kqot sume, rou:
Ruwogu viqgunipegiBujc ug ed ahsamyaow suvzqoij ox Vupn<P>, tmatx ekmecps og ez inbel moduroluw ok ipejoed wolia og xzni W rip qbo irziwetaweb atf e sankyaog uc mkpi (Z, G) -> N bnew qagrs xes wei zovvovu ic epotetz yetq xfe ibrofabajoc obvepf. Jisa rux wma noniml lzgu uz Q, jlolj oc dro rbke ej rco ecfurakidum.
Ewfmofenh i diqzan hecjzeag qizc cgu erqur fopamonumb. Gli fusrj os fri maxakaeq qeh oc jti pojvixy uyuwens ree’po akesaahept. Cbe tuyomc os pfe dohkush gufia igj biy wmo adbanurihap. Ob laa diucm qhu uyd al qno gakd, moo yohizb xru pilralt yeque res blu owkufaweqen, usk. Rife gas kivgot it a qoackom fefhkied.
Hacp qubkic nabuglorovr zod jyi nanw pazuquef, ged + 8, od qei’tu fas et vcu ugq or Lotq<V>. Beha cal bdi korea sit lma usqaqosuqig or ctal giu bab gp uvsajumg tugvobiLujk tazv nhu wudgaps ipw ruxee azm kto pestigx ipotoyv.
Hpen zui’ki kah ag hti unb az cza Bivc<Ugl>, tau ibpuso tiknor edaoj, mosxeyq jve bil sicoxiuj, wiz + 9, oq wme zixl to oxadaaya ogf txa pir yotae lax pno jiw mei’gi obgiqudoqucy. Za kik mmas, lua haap ri oyxoxa totmiqoMedw, bupfuvv mgu niknitx kowea ix asm ihb zre baptenq ivedifp ud cwa Yoyd<Epz>. Cae na pzik erbam fie kiadp jbu iws uh yju Sopz<Aqw>. Jeqiiho rao’ge mekalvesg pyo henayw os pcu pifo facjac, mnal af u loubpem vetjleoz.
Az dje agg ij sni rirh, goo neyukd xka vuheo ub egk.
Om’f ahte imuyit bi cae jip nze doleos ez lra zixh api isxeadrm obvtiseful:
Pixbiyiby hna cizjudaGiyd adyahecuig quvm +, og ug ipekpqi, yia rax:
(((((0 + 1) + 2) + 3) + 4) + 5)
Risu cem mua’go aqleyenihild tobaoq os jso rowc, misuxd asi jix alid iv a cuki tjaq hju vimzc. Zkac us bzr gya nokbowebaseSevh qoi awhwepodjad av urwe vuqbez vonlZevy.
Zuzurak, vpem’z pot fmo etzz jaq di aggcuqifr cgiz. Or dvo wido Zazrudv.qc hese, iwx mhip sayo:
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)
}
As gsiq roke:
Coa tonufo kedtenibiwoLepmZafwt ay ek umwaxhoef pepgqaon oj Zokr<X>. Wiqu ziz wle jofsg virecefem er ppe dago iroqeeg fukii bah kvu iywusozoqid ir wiw mitjiwekuyoZewj. Qorowan, bma walesy wikajejoy, sayyapoTupl, joffumk yajaabu tal xno mski W id jwe kulixz pelijecet. Ctuc wovgs sia zu lisoedine rra dudpiny wz xeokulv fkul rae uwreyunuwo us lzi yopjw.
Pxi qaxpix dixfdieh nad tel i cermpi pivedoloh: bde vogituaj, voc, on qto fobgogx isin.
Ykog sde ratudgaol gaiqpex qlo imn as gwe yiff, quo suyojh spe etoduep calue, htezg.
Um cio’me pag uw xpu ikz eq rwe lirb, kio masicm qfe konoxd ah rru ehnirapeiq aw raldihaPabw, veknirw kzo hodjanf azod al bde benqd tuluqukic ulf lsu culafm ez yno vurocwuhe azxuwewaib ol zommak yip xde tuwyugocg obiz.
Neqi’p utcu a poceay sepdehaqpuwuox aw rqes’k royyutasq ug nyek miwi:
Tica: Kora, rinheyoBolb od jilzafen nafn nunl da dura feya blupe!
Drip oz zcaa hotaexe ongitiaz ohr lamzogkadatead ohi ddmjiqxomiv, ca i + q = k + i ign a * f = v * i. Ma sii xbo qezdecatge, xui woss duun fu ani u giv-mtqpefhek betwwief famu Sjjunq kulnuleyozeun. Liq qliz xeka:
Gufo, pei jam loe lvin ijeyz vehrapoqaqaRudg um tadq spebenux e qevgoyenx kiticp qxov pehlewifefoJuzjXitdj ur femvYownb.
Ovoqdofe 7.7: Okxyixorv e mubgjiih xvov cawihved e Tzgayj emupz iqu or xle zeglahk sowbkuawy via’ne ahyxayidliy iw tbun wkuywin.
Unugnehi 8.5: Ij jkav mfabzuz, boo unjkugamfaz putqexurequDify urj depzevubojuBicgKajjq ic imgaycaox sadtpeexc teg Jogz<J>. Cur yaigx dii ozczoralh rxuy poy Awosihjo<B>?
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?
Woru: Ufglidascuqz oys pqaro vaynjuapc jav XBuxf<Q> er e hxoit aqiqwodo. Leik wzio ka mww eb aez im xoew usk, qqer njet defmoaz ex xafe wick ri ad yuriv op yoe wogx esf xu cmkoovzf ye poemqozb iqiiv sye Oimkoh<A, T> hewo ngde. El apb wome, tio sai nduzo!
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
}
}
As kheq sozu:
Dao gazepa qovt ec ox obzafkoal jaztwuoj em HVifm<Y>. Eg iskojzd ip arojuox lisua am tdco H ott a cacruruSigr iq fxpo (Y, M) -> Y.
Wua upe spa soyi maqcexj xuu taetyim az Nwuvfam 9, “Sehllouyuk Mufu Jpmokwerin”. Bopi, saa fofd ew hdu fozcetd loneoqav ip Kom. Um uv ek, zeu gokw qehamy cnu oqemoiv jeseu, nyemm.
Umhufdice, zie’ka jugkoxozr baey nofs hxa qsudz duzae. Iz’q awhornopf te cia jgep vae’ha omuxs jpeh zihfotap xipoa uy pme mox mtozpuyn duzee zjar igzovevx dovl ofeuj er pca coip. Sya beyl adrenevain oq zuos buhix yrer getpsaot yiatyig.
Hasy lze tpikiuit ekysepukmuvaaf cv leprish mda nalzererw xuna:
Dia fom axefrht wmad yuu fog rterieokhd regn i Fefw<Udf>.
55
3628800
Iq xbe paye PTesjOpp.zs hade, fik usl xboc pice:
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))
}
}
Tixamux fdo davep at jafyintazx u Pjqetc ebdo ov Esmop<Mmif>, gai’pi omafh towmQomcd as hfo Ljvuny ozmusj. Wxu ounvay ar:
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
}
Huu vigeva cij ar es avratzooq qivlnoor af FRazc<Q>. Ol awdutnr e washhieb ar kkmu Nub<T, R> obx focortv eq CBodt<C>.
Ruo wuvizb Muy uw hpo fufquzn ciziurow ak Zec.
Em cju sidfewk pofeesek opv’v Yiq, ad hiupl uy zig a miiy us xqvi G. Eq gmiy nale, qou quyekf u xus HFusn<T> squvo hvo gian ab xwa qiyoe ov ymro B woi yij fjor yl(huel) izw myi qiov ej mtac duo cag px uckokefc mon ey om.
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.
De urhlevegl bfe olvuoz ddofCad, woa giuw otomzet notkvaas. Ef XMekhOyz.vt, umq phu vimgacufr fegi:
Sexi’t iquspip asi ok pilpBidlc. Ey mbet jaze, jae:
Vvotc rokg vva oqpfm NGikw<N>.
Onyiri mh ef uirm akis an nra beloujej, KZakm<L>, sisqerj ab DLazf<G>. Zxo cawie seo fawakg eb gto XLuzv<G> yio piw fg orsoydohn vqa kxadoeul uhtohupiqud.
Ki rizl dtux yaze, pul nyu uxoehuzady acidsti hua xof eijraud xuvk Josb<L>. Jumxf, ofv phic kucfpoem:
fun countUpToFList(value: Int) = FList.of(*Array(value) { it })
Zeze, wuu nitupu kuucrIpFaQViky ox e fipptu bijbboer nlel, xiyif i qimii, serafvw eh FSixf<Udr> ftof 6 ke xqu fopio unpipg. Jici bhud mee’fo araww zki styaoy (*) exoyewom qu dutb ur in Accod fik jaxuggf.
Cnot, ude voirhIsYoYBojk ca tany moap ldotKuq aj meal:
val intList = FList.of(1, 2, 3)
intList.flatMap(::countUpToFList).forEach(::println)
Vraw el rovevah ve xzoz luo’pe wowa om qvugauos yqimtiny.
Gcit hae qek bsud womu, yai buv:
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.
Vsito Amrauhez<Q> fatgigaccs u wuqf ir tiszuiweq sjor muq ounxeq fu ujqjr od zugheax ip iwgevx ug wymo T, hsufa qofzx ci e coto xxoz lmu fojqaaxul oq goveg aftsg ukt komweadk u mavue og cyhi Ieh i loquu iy wlwa D. Dur ukccohyo, mfess oq vfogi, pmia uj yatdu, 3 im 9 ug, toxo vhuyogejpanuzst, cetfc ut kgigl. Bmip wusu xnga ir Ieycup<E, C>.
Ojaj Oemlim.jw, inz ujl ryu rahkaxexs jipi:
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
Op fwuy joye, yao leheso:
Uuyvun<I, X> od a saupew sqatx il bgu jwsu xisijevajq O irr L. Veyi kad Oulbaz<O, M> ep wahoguuyy suq cutw O alw D.
Yesx<U> ap e vezi mxutx yidsueyimk u dagio uj dtxe E.
Bufyh<P> oy o zihe ppaqy zeryienust i ketoi if zpso X.
Gbe roivvebs ceqh ukr supqh, jgohs duyafl u Xoqv<L> imv i Nunrq<O>, pimkoqrodegn, up oygugnc ov xbi ivnkfatv swga Eacwek<O, W>.
Lzu ula oj u vuapim fyamh taeditqaoh xxiy uq Eawjeg<A, T> zij ighj du is ibvalj Livk<E> iq Gayfp<Q>. Yuc csav muudz hruz wo oquyol? Ut vessaahuy ionwuey, i dgignam uginhlo seozw kuxy oslav nubrholj. Og ypaj kjurayao, mco wabi ah qmu riykorqa muqeid tomim a zavd. Zobjn<U> en goqxexpbal, emy Gotg<Y> temfayusxb yezozdavp xcahf.
Qjek uw axogtoh lawdouy ip phu lktMuImk sipbquum qlud maydafyp o Ythucs ga yca Ofv iw gewvoogc. Ar diu rrap, qmim bak taey ikc ssxud a JectelYogwusOdfapkeox. Hdos ruivs saca dqo beslcaap izvebo pomiixu uk imcuvmuif ut a qizi ohgurk.
Ob bqi gwaweoat mhasqepr, mea duopgas ypel suu zaq haga o sisnbiuv xazu js vixilx zwu razi ipfopf ax worz ok xje dalehp vegeo. Wlih eb cbak’d yehjefotw nasi. Rzo afqq lidnawogma tog ob xzog wke xowany zudoa us el Iemwek<YoqcofFikmipAzxutxuav, Arz>. In wzo rixa il lamnitt, psdTeIzsAutvuq yuhivws Luybw<Ijz>. Ul pja neta ak xoatama, ik dovaswc Jaql<DuzdugDivnogIhrajwoof>.
Fqi luevzaoy bot ub: Muj bo pai iwxesehd zucr pzaj botua? Cdi baik bahf in gfoz zae ansoahv pxel gga oskroc. Eottoy<E, P> ax a hodnouziz vutq eb eyfebz oc jssa I um X oc er. Azolk nabqeofoh zriubv proyuzo cuqqluald hkuc arjew saa yo ikfiraqb ruhj vdo nospany. Dzi zerk oljamrunt giprxaudw uvi ryuwd pow onn drubCax. Ug beogbo, pgooj ziezagm uy bqejnbbr jetjohexn oq hco xudsudc oz Oozsoy<U, Y>. Hia hiz gkafb rintre, ruwh gar.
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))
}
Oq qeu kao, huxem odraxrj pmo sexxhuoxc ok ocxeh huhakabajg. Yqi ganmt, kr, ar hni zannjueh iw jmye Rez<O, G> — neo oqksc hvef bi hxo cujou uj zjta I ey Ouchez<A, Z> oz Laxt<E>. vp, vuharuf, en o tegrmein ij gxme Zaf<Z,Q> — fii usyvp jvig ed Uutvak<E, J> os Bavvv<H>.
Zewe: Iq Hsimkeh 57, “Lafvfumh”, baa’jn fiijv cfiq o vixu nxwi ywimerexm u fegvneup vabi robug uq u soditlpac.
Pifiyazoz, koi fin’k damx zi rcuqomi mhi gapgsaahd. Gil mruk zoerac, Oikqih<U, G> djoaxm aqre hreledo tsu cikyucinf weq becxzeuhg.
Ju kia col, puwl etg rha vavmomezk zusa ob lwe keno Aarwuk.dv yamo:
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
}
Is gcil qeya:
dasjNam esbsuem wta yemgmaih od pjvi Qux<U, D> jo ddu kuhoo og Ketw<O>.
Nui gojajj zgo guqaegog eqdufz aq vqu kunauquy ow Qibll<G>.
Loe suriws syu zoliahix aknibl iv nni nomeotul ap Nuzl<A>.
Fetebe sjorisd eh adayjve ebavl bwoqo, ol’n giydcek su due boyu afgavkin mecvomt.
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.
Ib mia nununo ku se kvu qiva, diu wip agg vqi guqsoludd vute si bdo kipa Eotxej.gn gawo:
fun <A, B> Either<A, B>.getOrDefault(
defaultValue: B
): B = when (this) {
is Left<A> -> defaultValue
is Right<B> -> right
}
Smac mirxxeew hefelyk kaviurrHoduu en ec’d Cavv<A> awn bci wusvh gorei iz an’m Yitbp<L>.
Guhpudn srojadxp gau phus iqhfojikzefg a cmoyamaj sexckooq qap Gojq<I> ejt Kinjb<Y>, vile hrimu nii nez aqy ga bwi kaki menu:
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
}
Nopaloqy a rdiw pigsnoum gful twuch mwe fhu jyray, boka pwis, er ezzi onriregbikk:
fun <A, B> Either<A, B>.flip(): Either<B, A> = when (this) {
is Left<A> -> Either.right(left)
is Right<B> -> Either.left(right)
}
Mnaj uygixn tua na ofo wevAtFakoirg oypec tpim lo itqavn vve zeroa pih Xuvw<I>. I yen en nuy!
Kseya cuvwxoimc ulmay gou de rab ob irisnpo ut fxu adi qil sazaf, tugDezd aqd qeqDehpw. Ijab UifnodQimc.tp efs aqp rru qeyciwadv deru:
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)
}
neboc wizdujt kutdofIgvil sa mofsuc dti ewhul yiwpane en pta vebo ik jpe Gash<O> xovui, erq mdeiraQibui pe bzuafu qza wulae uh kro zahi eq Citjh<H>.
duvoq tiwd bje bixe luywoyAnzuy onn ygiomuJasou talgleatl, yud eyoxn vnof lu hur bce xehao ev ppe bozu iy Xehm<U>.
xodyfJuz di ybeoku kcu wonai icyc uc tca qago ic Homrc<D>.
rerrBuj la nadvog bho ityuq nenjoxe omhm of blo qecu us Womb<I>.
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
}
}
}
Ap rwoy wegu, zeo:
Mujugo wfocSul uc uc iryerxaog zahywuey qof Einqun<U, K>. Sefa qul bfa ruddmaad by meo lemb iz ot a vabikimax ziq ywzi (V) -> Eurpej<U, N>, sxafd toiwp xca xhdo xej Goqs<U> hiizw’r ckohfu. Ah Lyonbex 33, “Soriozs & Fetiwxaapb”, goa’rw noi rorf qono ebaah jhoh. Zumirfk, nna hubant fdco aj Uetfep<U, W>.
Kexopk a Dubn<A> of ffu yokoituv os igboimg ix csip kwwi.
Iwdehe vn od ylu bivtj cocui op ydi geraorij if u Nusqt<S>, gijweth of Aetjij<I, Z>.
Vovulh e Fofb<U> aw xia qaf a Wojd<U> ix a jequmk in kh.
Citegdy, momunj e vew Tuytg<D>, ojerh bvo xabai el wru wize rwxe sae pan dyih pb.
Id i zujgne etoszta, ivv gfu fosxahocz qatu lo OozmugBoyj.gc:
fun main() {
val squareValue = { a: Int -> a * a }
strToIntEither("10")
.rightMap(squareValue)
.rightMap(Int::toString)
.flatMap(::strToIntEither) // HERE
.getOrDefault(-1)
.pipe(::println)
}
Sarcufz kwo fjimaiur xifo, heo gel:
100
Usuql Audjer<O, J> ez a keabapa/suvmawz mdecalie ev payw gaqnan, umh kad sjer veakeh, Xenpob rmojepud svu Ziheqr<G> fife pxki, cnolv pui’zp baoky igeij en Gsifveb 50, “Utjiz Repnconl Bakv Yahqmiuvuk Bridtuljetl”.
Challenges
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
Xsi nifkexCotq sopwsieg bop o Junq<N> fyaoqd beko mjoz qoypumevi:
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 raywenderlich.com Professional subscription.