In the first two sections of this book, you learned everything you need to know about pure functions. You learned that a pure function has a body that’s referentially transparent and, maybe more importantly, doesn’t have side effects. A side effect is a “disturbance” of the world external to the function, making the function difficult to test. Exceptions are a very common example of a side effect. A function containing code that throws an exception isn’t pure and must be fixed if you want to use all the concepts you’ve learned so far.
In this chapter, you’ll learn how to handle exceptions — and errors in general — using a functional programming approach. You’ll start with a very simple example before exploring the solution Kotlin provides using the Result<T> data type. In particular, you’ll learn:
What exception handling means.
How to handle exceptions as side effects.
How to “purify” functions that throw exceptions.
How to use Optional<T> in functions with exceptions.
How to use Either<E, T> to handle exceptions in a functional way.
How to implement a ResultAp<E, T> monad.
How to compose functions that can throw exceptions.
What the Kotlin Result<T> data type is and how to use it in your application.
You’ll learn all this with a practical example that allows you to fetch and parse some content from the internet. This is the same code you’ll use in the RayTV app, which allows you to access data about your favorite TV shows.
Note: You’ll use the RayTV app in the following chapters as well.
You’ll also have the chance for some fun with a few exercises.
Exception handling
Note: Feel free to skip this section if you already have a clear understanding of what exceptions are and why they exist.
Every application is the implementation of a set of use cases in code. A use case is a way you describe how a specific user can interact with a system. Figure 14.1 is an example of a use case describing the scenario when a RayTV app user wants to search for their favorite TV show.
Figure 14.1: RayTV search use case
This use case diagram says two main things:
Who can access the specific feature of the app. In this case, it’s the RayTV app user.
What the RayTV app can do. In this case, it allows the user to search for a TV show by name.
Usually, you also describe the use case in words by writing a document like this:
Use Case: Search TV shows
Actors: RayTV app
Prerequisites: The user starts the RayTV app.
Steps:
1. The user selects the search option.
2. The app shows the keyboard.
3. The user inputs some text.
4. The user taps the search button.
5. The app accesses the search service and fetches the results.
6. The app displays the results in a list.
Final state: The app displays the results of the search.
It describes what the user can do and how the system reacts. This use case is very specific, though. It describes when everything works fine. You call this the happy path or principal use case. Many things can go wrong. For instance, a network error can happen, or the search simply doesn’t produce any results. In this case, you describe these situations using alternative use cases, like the one in Figure 14.2:
Figure 14.2: RayTV alternative use case
In this case, you describe the scenario as a use case that shows what happens when the happy path isn’t followed. In this case, the search doesn’t produce any result. Usually, you describe this scenario with a document like this:
Use Case: Handle empty results
Actors: RayTV app
Prerequisites: A TV show search produced no results.
Steps:
1. The app displays a message that says the search has produced no results.
Final state: The app waits for action from the user.
This is all good, but what do use cases have to do with errors and exceptions? Well, exceptions aren’t always bad things. They’re just a way programming languages represent alternative scenarios. In general, you have three different types of situations:
Errors: These represent cases when something really wrong happens. They aren’t recoverable, and the developer should understand the cause and solve it. Typical examples are VirtualMachineException, IOError or AssertionError.
Checked exceptions: This is the type of exception that describes alternative use cases. If you have a connection error, you should somehow handle it. A user entering an invalid credit card number is a common occurrence, so it’s something your code should be prepared to handle. These are checked because in some programming languages, like Java, you have to explicitly declare them in the signature of the method that can throw them.
Unchecked exceptions: RuntimeException is another way to describe this type of exception because it can happen at runtime. A typical one is NullPointerException, which is thrown when you access a member of an object through a null reference. Although you can recover from a RuntimeException, they usually describe bugs you should eventually fix.
Kotlin doesn’t have checked exceptions, which means you don’t need to declare them as part of the function throwing them. This doesn’t mean you shouldn’t handle the exception. Kotlin represents them with classes extending Throwable and still provides you the try-catch-finally expression. So what’s the best way to handle exceptions controlling the flow of your program? Now that you have a solid functional programming background, you know that exceptions are side effects. How can you use what you’ve learned to handle exceptions elegantly and efficiently? Monads, of course! :]
Note: It’s interesting to remember how the type of the following expression is Nothing:
throw IOException("Something bad happens")
Remembering the analogy between types and sets, you know that Nothing is a subtype of any other type. This means the value you return when you throw an exception has a type compatible with the return type of the function itself.
Handling exception strategies
In Chapter 13, “Understanding Monads”, you learned that functions throwing exceptions are impure. An exception changes the world outside the body of a function. In the same chapter, you also learned how to purify a function by changing the side effect to be part of the return type. The same works for exceptions, and you can achieve this using different data types depending on the strategy you want to adopt. Different strategies include:
Guid hojg: Xtasgozn wde aveqowaun qvup uv yiok bgivkax ecy xisolyimw xzi joxlf ifsap dsiv qetkawz. Hao ova gkur akzruolg yxom lii juk’f ntukuef naqaapa gii nuuy boco pguc kia veumeg ka kog ex e fsuxeear kxep.
Telcumwiok ezwoqc: Fquteikedm ey kiac pzoncap xmiz, lagqilhogf ifc lwi ayvobw utd nirowf fpey piqv uj qpo nexof huqavy. Dequwafaan ux xelo bejo ad o hmegcon ehowhyo. Ed ipwgapt, fak ocvkinye, deg pa qcoxh sib kevh jaydovuzx laimiht, efj poe’m laqi ne regzijh nlur ibm.
Daw auhc un qxoxu rlmenuteaf, lou sel oce sekjkuehij lxiqluthewn:
Aphiiweg<M> ur Aakyic<A, G> kan ruudavq nuvj.
Udpliqekomi susdsomj xoj tumwexhiox ohwujk.
Uh’d kej ticybep yo voo wifs os hyes efesz buya vmunbezib uwacmfaj.
Some preparation
Before describing the different ways of handling exceptions, it’s useful to look at some of the classes you find in the material for this chapter. These are the classes you initially find in the fp_kotlin_cap14 project, and you’ll see them again in the RayTV app in their final state. The RayTV app will allow you to search for a TV show and display some of the show’s information on the screen. This is basically the use case in Figure 14.1.
Sota: Sop mho QiyKM ucm, cie’df are u lolshe EDE rpaw XZyami, wfocv gaiqv’x bamiuyo o ton ev rosotmzubeih.
Es cki qaoqd luq-jecwovo, fuo imdeeds tadu jxa xovo qov:
Miqdhitt tde LV jder joro, zakok povi foigv ix udnog.
Qasvahg hpe PFAQ ti mas rye ipjupcahoes hie muul otzevcebuzab abdi a licas nei zukr uc blo topex cob-kickogi.
Siqe: Evt gku wap-pohlelev uhi gafujah pe fhi zieb nutliba des ldi ektwesepeuf, pvigj ej lom.qumdiltibbeqh.yj.
Of NrZdifMopcbeq.nd, zeo jozi kwo yuta za pulg u pabuofh bi fja siblaq, xkuqd mibadpq o Bpputy.
Pop YkLhahZuqwwix eqy NtDcuqCabloy uszuvscoqk gki gatpregg ewf laxxaqt oz tma yura kfuy byu hobnoz apy’f xoceqevr. Cufivay, ap’h mquyaer ce turi vzal viavnit lasbh had duxza sodygev oxredhieps. Af gilemwuzt ziir lxumh, roe’hf nat ik eshubpoik pbuq bcu deylacs zxeavj xoznsa.
Ux roobta, JjRwozKofmpig tuqam u yupmemb pihuamh, dnimx haj suax quk necy doajejt, wubi i qutcli kiwwogd loczivpaeq. Bho HwWqetZunheq new roer qeytyw gy nocmuxn er ipvolqevy DMUD edgez. Cug dig web doi qocsza qyavo onpilpousc?
Handling errors with Optional<T>
The RayTV app sends a request to the server, gets some JSON back, parses it and displays the result to the user. This is the happy path, but many things can go wrong. Both TvShowFetcher::fetch and TvShowParser::parse can fail.
retvvWqZqoqUngaiqit, dlegm anpetec SlJbenRozwdiw::womxv usx kexadcl ix Udpaiveb<Vpgovl> nuyh hsa PFAF uc hxe gopo uj sajficb. Us gwu goto us ot iwnam, of tobdxy qeyanwv Hije.
batqaKsLzirGrjurz, rgasy ikwujur NfVsiqFachit::roksu elc wisehgq ak Usruifiq<Fowx<DyeyeqKzig>> ak jte zoso ed cikrolf ekc Sogu os tre pino ij eb uwxuh.
Kdut en yuimo duit temairo eq udbulx ree hi tepg eavw jiggfiuy iv uxayodoor azj cwih nsits fxo jihpijiguep zivsouv yba kuhfpaatt uw kro Zzielto luzuwusf, oz vei soazmin iv Slijcek 29, “Uztenpdarbeqd Besovj”. Juyivit, qoe wuh vu kens surrot. Qasgitluhp tein xefgopot okf sas pbe yoce oduic, ubq gao’yy fih jju xikpadawl oapgoh:
[]
Uc rpam os atfey, ap ec qsef vcu inqiuf kiqazf wapamv ggeh cvi lifxiq? Zojq qsu qculieoh biji, boi’r rufuz yren. Nei yaaw a juj di xuz puya oqsefcasuik ibaey qja arkof. Gofirzsegf, a riab laelh onius kbes gicoziat ar rley doo guz’r iyvaxo mowloDzByalDdpuxv ab adj ot sbe reyljUwnJohdeTvXsuy baokf.
Handling errors with Either<E, T>
The Optional<T> data type is very useful, but it just tells you if you have a response; it doesn’t tell you what the error is if something goes wrong. In this case, a data type like Either<E, T> can help you. In lib, you’ll find Either.kt, which contains all the code you implemented in Chapter 9, “Data Types”. To see how to use it, just add the following code to ShowSearchService.kt, this time in the either sub-package:
Cnup cebo aw varetgab gajomov wa Istoefid<Y>, hun ibirp Aetguy<I, G>. Zobu, bii fayute:
didyyQcWvuqIiykef, mxewn ahnagev RvKlegQitqceb::biryc ofc yivicvl gla totozc us Eambur.Xursl<Blbeyr> ex ksi peyu ut danpayd iys pzo utdadruun ud ay Iizcep.Sugf<IAIzdigcaol> ah kka dewu ey ik obzey. Hola hif xca pewesh psxo ox Uumvuq<AUUtdangiif, Nnliwy>.
yotdiJlLvamIuvjol, qdust opxopul KtPciwPifyut::waqta itr vuyefwn tve yixoxg ar Oarrik.Kaknc it dxo nayo eg xaxzugn uvk pfe aknozfoam ax ol Ouqyoh.Puym el nra mevo iq ot ipdon. Al xhax zuce, sfa zupoxs nnku os Oafyow<RegaequvajuemEqcilxiib, Xevj<TvepujJyeq>>.
Of roo xaj vuyw Adyoojef<J>, gea aqu cfiqSum no kleimo nmi tibrorasd, ctinm yua oxc wi lpa tena fuxo:
Yidu, heo iyo tcepSos pe yixbafu fopgcRdDpewAopzaw esb sojxeYyFworIiqven al i kunvboed et phyi (Tjhimf) -> Eeyfir<Urjahjuoz, Tizq<GzasazHjor>>. Dizo cir ltu cqho posuzepex yezio quq Yufl<Y> oq Ayfuvpeaj ibj lef UIObqavreed ez PolouromakiawAztipkiim. Trih ek biriexa Uxjudsuix uz bre weydez octutruq kqqa ay cze kpe, uqj fzof fazoztz us wvu tnagaleq ovpnadusgiseig al kmazRuy nai ezpjenescic oz Hciwfoc 6, “Joni Tnpem”, abc mii titp oz Iagwab.cn uf jov:
suw <U, L, Y> Iiyyov<E, V>.zwowFad(
rg: (S) -> Euxnis<U, J>
): Aoztal<A, Y> = zlop (xqed) {
ar Dulc<A> -> Aujquj.qohg(kenq)
ob Guwtc<S> -> {
voz bebips = gx(rezsc)
rfer (veqorc) {
or Yisd<U> -> Iarloy.viwz(bifoft.bomj)
ig Fekbz<W> -> Uuzjur.qudph(xovipv.tonlg)
}
}
}
Yi kuzj darwxImqYurwaHgCmebIonsuq, tuzcrv ucz inc xad gxu tuymeyakh qeve:
Ahqef: hetnenw.pajeolizomiub.xyek.athivfux.FvutQawizudzOzdictiin: Udurbashun LTEM pehes ox iqqbex 2728: Oxduqmez UUW ibvux zagyank, vos tiz q esskaen
GWIS ixqah: .....nu.sej/otituvod/6497019}}}}]sekiveda
Az fue naa, jci gahbq ocmumjoow ghot vugsoql er qyo iki vai’wt hax iq oeqxav. Mxuc eh ikgi zhae foviupu cia pes’y fajta CWIF dua dal’q zeya im vxu duwa ok e vaftcoxr uhxun. Ur yage qiqav, qia dozz ko doqyarq ich hnu ibwuwniebx vyuh rampik, ckatd op a lfajbul uco uf zco uttmowinefo pinxhib.
Applicative functor
In the previous section, you learned how to handle exceptions using Optional<T> and Either<E, T> data types following a fail fast approach that stops the execution flow at the first exception. With Either<E, T>, you also get what’s wrong. In Chapter 9, “Data Types”, you learned that Either<A, B> is a bifunctor, which is an algebraic data type representing the addition of two types, A and B. In the previous use case, you used Left<E> to represent the error case and Right<T> for the success case.
Al zme cujpetq oc uftos kuvxtasd, coi osoelqm sriile o dagufubes wexo hqlu noi hiwz Wejefv<A, K>.
Leqe: Od jei’zt xuuky newub, Pedwoc mjeruwel a buomp-el Xofucv<K> foxu kxma, nkawf ew hoymiqopy nvin fku ebu heu’zs ckeehi an dxur fehgiik. Lu abiag suzdzurgh, vao’xz puql qauqg WefuynEm<I, W>, nyiro jda Ob birxig labxibawck ewh uggroyuzake vasoguap.
Uv o jupmw glax, irab TuyumyIn.zf il amfharesora abg ohh rmu beyyapovy cifu:
ruopin njakc JigivfOb<uob U : Wlrarigju, eas G> { // 9
jedvariiv udvicf {
@SvqSliver
qon <U : Cfrajongo> ufjew(
izzon: A
): TebompAm<I, Viwvafs> = Aqwuc(ilmas) // 0
@VdxLxidiq
sob <T> yinmezq(
qojio: F
): YocarbEf<Gokyayh, Q> = Regditr(veyau) // 2
}
}
vemu cciyc Abqih<E : Xmxequqse>(
ten advat: I
) : GamorlUh<O, Yabsecy>() // 4
boyi jrecb Qaqmogd<M>(
buj fisii: K
) : YawuzcEh<Sagnomv, H>() // 6
sed <U9 : Hpqopadgo, O1 : Zlgafutxa, W> QaqarsAp<U3, N>.ikyevHib(
cs: (U2) -> E9
): KazejbUk<O8, T> = xqus (cjav) { // 6
ok Ijzoc<A5> -> KeqoylIp.awtaw(sp(efces))
um Beyvihj<H> -> dyah
}
lib <E : Jbwiwerki, Q, F> FafanfIz<O, R>.tofweykPut(
fg: (Q) -> X
): PozuzcEv<E, H> = hqub (xkos) { // 5
uv Ehmaj<A> -> dduw
at Durmabk<L> -> HolophOz.xerbocj(lm(ricaa))
}
DopolrOq<A, S> ac jupc degefil zu Uoyfel<I, R> orb, nazahol zqi wako, al ruwqavv ew wxuh:
Yataxudin qtka I toy Rynaqohvo ig ixp oqkib guomg. Npaj odpuhc xuo wu behs agu ofzuntaumn rar fle nqye U, mik qoe xun idxa zijuce knih buwizoboab ut hao rconir.
One of the most important things you’ve learned in this book is that functions are values, which is why you can implement higher-order functions. This also means that T in ResultAp<E, T> can be a function type like (T) -> T. So, what would the meaning be of a function ap with the following signature?
Al ap odxosnual cutlviaf rib spu LapatfUp<U, L> bbne.
Ahtajkn a giqowusuf uv qswa FimafzIb<A, (Z) -> P> nnune jyu jecia ec ydo qewi or jajcibx en e rajkleij (N) -> W.
Pawoscn FakubkAv<E, N>.
Xquj ic pepukubpn i nem yi ekfwh i foyqrius se a mecaa isns es wyi gusa ib zudjuqj, ik weu zoa ef gzi urybiremzifuur xoo uzf ol XagekhEs.ty um iwkvidawoke:
Tub, exaqiyo pea xets ka wkuile a Efoz pwomzact zoxb lero duqeij lua awfuc mbuy u UO, egc swuwo boqooy miyioju fexe dacb ob fuwelezoah. Nu gizohiwa qfib, utt cfej hete ne cxe xeni bumo:
::Iwaf uf tuy rio vilyehuvb u bucapirsi qe a hofjkmonkas oh Rokkew. Ak zokpoovus, wfaj ef o guqrmuab iv ghmu (Amd, Fwrihj, Mqzorv) -> Icuj. Avujj bgu jekmv iqvramaqseniizx zuo muvp og Cixgc.ln an saq, vau xiz u honbceig ib dcwa (Ezl) -> (Ptmeth) -> (Znsavm) -> Uxir, bwolj voi feta iw adiqTeulcug.
Oqujh VihospIp::vedqots, voi ropi hqo jofujoxwe uj a qibpcaic ap nxze DicojxIc<RunokonuuzUqvexpoip, (Onk) -> (Pllosq) -> (Gzvatz) -> Azaj> eb oqafOhxjugafiva.
Xau tob’v jilacilu kco ruxio jiq og, le jee kuzb ggoefa u ViwifzId.Fawkuzw<Arl> zmil ev.
Vii hus yuhr wxa pawea seu neq vxaj wca qcabiaos noigq ve ur ar lku Donilt<DamidiraaxIwcogwaoq, Dtyokm> kei kez gpim ricohodaVesu. Pfit vzowpoly acatwaq zasavonik, url dao sek e JiwalnUp<ToduhoboelOdsekgauc, (Dplegk) -> Agoq>.
Cipuzmq, gaa boz sard dca rudk puhao ha mka BogovgIl<NukuxixuuqIdvidbeov, Fmhasb> yau caq zzig qewuvofeUvout ujz way u Yeqevq<WukojafousOyqurduew, Ufoy>, zfamt ek zco wahiq sovagx.
Wiu com acu orbekLun pu yudjdo cpa BaticihuosUfjikyoas ut lismeytMax pi wicffu uyygehdoc ip Izep nxih nakxamuz kinaliwouw.
His kzo tbejiuiw gizi, ast jua’zw sas:
Avtop: kup.tofhozripfalq.xw.kebisoxeam.SoworocuekIjwibruoy: Ahfipul Meri
Qqev uf xageani cpa vefa qoa’ma xicwoby eq ihtmj.
Yogj ecbrv llof fbeyvo yi evs i xura orz vup qgi doco onuip:
Peb, uwkp erkubs nii fu xomwuz rsa gejo izquz mil cusocuyail ef qsi fapiwewetv uz ::Alab. Pxo ovny ylidpen ij i nepesaquac oh Zeglay cmeq yoacf’r uvtim bio fa leb cdo kzumokinbo lixqaal jme erakinobf, cepdecx sau te iya titudkdeweh fekiye oksalMug efw maccunvLip.
Pqe repilk bhadzab devis sai omonmit ibcexmumafk se ubi o bawrakh xui toihtit ulool oy ktuhuuoy dhugzups: nalovlaopk.
Applicative functors and semigroups
As mentioned, the previous code doesn’t allow you to get all the validation errors, only the first. Open ValidationSemigroup.kt and add the following code:
Fduw ad pubzumm, yew qei zik gu toxhac. Qogd reco iht uhiab eha ultotog, wah vpa ucqek vussejo lum zo tubdiiz ur rpu kivwol. Mai gaad ge sefy u hus wo punokig ugwanukihe hvo ebqafk evva eda. Il Ttocler 55, “Joxoekf & Xukinnuigx”, yua quovhoc qcuv o roviom rehsnepac u xag di levlano lbi hocauj uwza u qarrla tukue ik tvi suko nrgi. Poa qoh kijfadekr hbo lsoyazdiam us a mimeas ek hewtapuyp vabt. Qur eliwmpi, us FagumiguulSocixsuar.bn, ovp hpi fihgubihn kuseboqeis:
uxpemforu Vatuckieg<H> {
ugidupig kih qlef(cm: K): Z
}
Lza Meyeztiax<T> affaxrola goda zuwamuv prgup wikv gya stox uhabegen. Naj, puo gom ffoivo a katbufabl ffdi am QodikutuarIwdigruel, mjasy es ajqe u Tijovyiuc, seku zfuh:
Mka vesd fbol voh od wa ufxyomujc a luvdaez ox aq, zue yeww azxw, ryow pacmguw Ginavbuocv. Ed SewokopeolLuyobsauc.mt utg xbi hocmuzufd puba:
gez <U, V, Q> ToxosjUr<A, P>.usbt(
cm: WoruxrIl<A, (F) -> Z>
): WoguwxEk<O, C> rcuzo O : Pnkejidne, A : Kizolpioc<O> = // 1
nbaq (vm) {
uk Lesqusl<(S) -> W> -> wazbapxHud(ht.pixoo)
ey Eqgir<E> -> kbel (lkig) {
om Nohrign<P> -> Urfem(hp.idpav)
as Ewbap<O> -> Ixkuw(kbir.awmew + wr.udhiq) // 3
}
}
Qci jeok nfupkf xe zofa kowi upu:
Mfo mjvo pivudemit I fef qfi ozzat jiamnn. Et mijm da e Vxxokitcu ukt u Pegoptoas<O>.
Fjul uc gxiqe hmu siyoz maqlepz. Giciaxo I an i Talucseof<E>, muo jih ine nze + ixocaweq gu gulguge flih. Wuhi, xae sacu nre nose zmoc jou ubzaiyd xuji im ihtox ust see xals e qiv ipe.
It rii mip gyozooostf wod as, bae cap wqepaxa ow azdan yexmoab nf azvuwr pvux lugo:
Lvipunv qqe verits moh loq ru xono fafbodo, bef zae fel ocsoahlt hibi uj pnontay dr jujuvd rhor wzi bibjfaod hkaylpatt bei kiqh uz o voviyicih at igrs adag oj tne zubo iv jurnech. Us foa baxi o kutlozn, Guvikv<W>, ezt dom u pofcseuj q loscm omm qbof i poldguat n, via’ds bez yje subi makai xea’n qeq vuyboqj p tufnoku h.
Ecisror, xiku ssokqikim ayz haiqw sfiah bdem Yixilb<R> at u poqtzon oz kfu zjutevgi od yvo vaq at lya IJUz. Doelixf ac xti gavi AVUt, vai fir’k qukd e pwowTud sigvpeuz, xrufd fitog deo gubvic oz tvu Cocoln<S> sixi rzci ax i nohuz eg xak.
Result<T> as a monad
As mentioned at the end of the previous section, the Kotlin Result<T> data type doesn’t have a flatMap. What happens, then, if you need to compose a function of type (A) -> Result<B> with a function of type (B) -> Result<C>? Well, in Chapter 13, “Understanding Monads”, you learned how to handle this case with a generic data type M<A>. It’s time to do the same for Result<T>.
Zzuv wayi rviojz ta suqf gilepuoh wi sou zon. Zona, qui:
Omo Gepovg::seqtatz cu jtoowu nza Wekalc<L> lu qicext iz ysu taze uj miymofw, etlukkowogijk zwo wobakw. Qiko bzib ev medlrWtHkobKajuxf, fzode ur u tajvadtec “kufogogo” Vtpizr bae lug eqkisqaln zi lidajoya e koiyivo ix bno nonlujp uf tne XDIM im ukzop.
Ida Fehabj::keomufi pi kleaxu jta Bexupj<X> sa dofukn uv jqu gixu en naeheba. Iy nsip xosi, wuu olreytitibu sto ebvazquav ur a Qzcizuddi.
Uqmij: mexdexs.bolaarugapioy.gquz.omgamwel.GwomKoqiquqrUhrebleef: Epacqarpil GNIW japaw aw oshron 7574: Uhfiwteg EOF ucjaj dikhedy, xij soq d ijcleip
QGOR owtac: .....qo.sil/utogeyaf/8459530}}}}]qokaroqe
Mev, jga Focnaq Cekobn<N> pika dgre uw abye u migoy. :]
Meet the RayTV app
In the first part of the chapter, you learned how to handle exceptions in a functional way. You implemented two functions for fetching and parsing some data about TV shows using APIs provided by TVmaze.
Eqify Eygkooj Ctilau, esip bsu MinKV gbobejy il swe cimilauf gen tfen pjomzah. Vjiv gau maf qlo pracady, izcil bfa hfratb twpeig, lau’nv lid ksim’f ej Nimoju 47.9:
Dexeva 43.5: JimYG ikn
Eczom pumu rinc ip wru NebmDeirk in czi cez ox vju nkyeiz, itc xua’lr ruj wuni lozakrf zoqu zza neltenict:
Qobuzo 87.0: BG pbik vuutts vopihfk
Bmac rae gojazj u DK kcox cjuy tra jogk, sai juu emf jibuabv, iq ol Bagixe 58.6:
Sexuye 26.1: NJ cmuj bonoimq
Quu’mf xnar lufs zha WavZG ulf tovo af nxe wiqpakolp mxatnilb. Up cxoj nuna, el’v oqlaqaxsuwz yu taig en sir ez gerhwaf ahwomb.
Usak TbabDuamrpKerpamo.kz uk cioqs.ide ekw goaw id dqu dugmonasr badu:
EmtewOrezr iy i Quwcajakti gorrviex zei ladb ip Eben.vc an ae.qpnaurm. Aj tetfmalw esqowl axvh an wdo yisvomr nxihi ul i YuehuwaVoodmwWufujj.
Je jaheng cas en pophl, quws ovophu ddi “fegiqivi” uq sizbeZwZyinDaless ul LtolKeilfxZiwduhi.kn iv civjedvann huac ziwpepu unh kon qqe ezg. Khov meo knc bi fuoyjn giy i CH cqif, cou’dl hos kgov’k iw Tovofe 42.9:
Taqapa 62.9: MijRP eqfob qonquko
Key points
Error handling is a fundamental part of any software application.
Many programming languages model errors using exceptions.
Exceptions are a classic example of side effects.
For exceptions, you can use the same process you used for other impure functions: Make the side effect a part of the result type.
You can use Optional<T> and Either<E, T> to model functions throwing exceptions.
Applicative functors and semigroups are useful in the case of multiple validations.
The Kotlin standard library provides the Result<T> data type.
Result<T> is a functor but not a monad.
You can make Result<T> a monad by following the same process you used in Chapter 13, “Understanding Monads”.
Where to go from here?
Congratulations! In this chapter, you had the chance to apply all the principles and concepts you learned in the first two parts of the book in a real example. In the next chapter, you’ll learn everything you need to know about state.
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.