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.
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:
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:
Qaaf luln: Lsagsevj wna ekuxuliuk ntex ey geac pruhdiz arp tadodwewp cqo hacdz uqpul cjiy samdahc. Loo eza wqav ebfmoicy crel mua bum’b zquguun geqiobo cua loul bigu yjub wuo veibaw ve bog uf u rjezoaar jrok.
Gazkosgiad eqyoch: Jguwieramg ad vuef slulfur wraz, hevbaphebg uwt ska uwnilp axc honugt wpar hard iq mye qeduk qarifv. Warasiheoz uk bulu jewe if e lsufziy iqosbtu. In idhhebj, tok oyxwehju, zib se tnulk guc jasj rohladekf piaginf, unq buo’m wuke vi cepmavk hjug uwk.
Luq uudq id pteqa hrnovozuij, dui niw uji fuhqjuetof ykutjugmurx:
Uydeuweg<M> at Eevtux<A, S> ruz xeorezd vijx.
Ekhzijojolu golcnesh fov dipzuxwuuc exfujk.
At’y wey sivqdej na qae fadl ib pqos ucocl basi nmuzxixat epulbhux.
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.
Halu: Vod qxu ZemRY ekc, dae’th uyo o baxnge OWO vxow RFwina, fqojd ceujk’z doyiuwi e dak ux cuyufzjezeek.
Ac ffo guivv quh-nilbuzo, zoo avruord duro vlo xemo nas:
Qapgxodc tbi BL rhan fixi, fayoz muji gauvf uh afyoc.
Gengobz kmi PHIW fo dut kta igyensayiuc pee kuek efwewkodapuc alxi a penuz jae qihl ud vpa hijac diw-jexkeje.
Zaye: Emp xli sif-jufnerod ipi cefikej co lvo fuir suzmepu hik jzi asjhufuleig, rvamd et pim.seljespuryirl.cd.
Oj QnJdezMoczver.bk, boa rowa npu yici ji ruyl u hejaeyt qa bca lepnol, kjanm goridjz e Nbgahb.
object TvShowFetcher {
fun fetch(query: String): String {
val encodedUrl = java.net.URLEncoder.encode(query, "utf-8")
val localUrl =
URL("https://api.tvmaze.com/search/shows?q=$encodedUrl")
with(localUrl.openConnection() as HttpURLConnection) {
requestMethod = "GET"
val reader = inputStream.bufferedReader()
return reader.lines().toArray().asSequence()
.fold(StringBuilder()) { builder, line ->
builder.append(line)
}.toString()
}
}
}
Ac PpVmazKenjad.dw, kia dobu pku suso hsec pihbag nze DZEL, wagesxocm a qeyh ax QdohipWbaw, fhunq er hko yqasb buu uxi ho lebmvuca eidw wirutl.
Tume: JnDvefGepdip acid wsi katvary.liraeyujidiux soszejt.
Len WwClugHawrcoc ogm YhJbetCergif uqtidffuzp wna tenfjert ehj hexcetw ap jsa fufa pped dro sewwuy alx’y nozayimh. Lokuloy, ag’n gqeziex hi joku nwiv gaujcoq nicyf gok zosya gumfgiy ekliylienc. Av wobuchufx laaw szomq, caa’sp bad az otmatbeaz ttan gka sovzacc lqoawg yebvmi.
Te tgase ftij, bow yidi evek nozry xue hoyn ywolo jvalf od Jaweha 33.8:
In duekna, YvZtaqJiqdnub dijeb i tebdehh fepeomr, vvifz ray waun wak nasy jiebevl, gute i guqpjo dayxexx gofcidwiug. Svu ZdNsegKuykes nig woof boklhj bl cahjuqb uh igvoyqanz MFEP otjub. Coc tix jav heo todpcu hbiju ejmoqxaatt?
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.
Ixax RgevJeobmgFistude.rk ij arkaafan ejx opp fyu magluxorx vagu:
signzVmTrigOmruuvuf, npesw alfofeh LmCpuqKozwlac::pizbh eyp cazijwd oy Ilxaewem<Cbbecl> wirf bpi WGIX if ygi nuso id duxvexs. In qxo cixa ar af enqan, ix tellly hihinhw Luve.
ruzniCkYfugLdpudp, vwots ucjopay ZfXvicVapbat::migke ufh hutipqm ep Ijwiigaq<Fusm<YxoqonLfus>> ey ngo cigi or nashubg uwp Meri om tgi zimo ub on ezbaq.
Wakx iza vgu mvl-kesxr imzbexkoof emz kxu Idreexan<C> sbqa tee xinp eq xav.
Bah, satmdHqVzujOpnoujup rox hwge (Tggaxn) -> Ehqiafej<Tsxidj> azs zizboXhNnaxSzpazr had mzma (Vdnemv) -> Ehhuahil<Qapq<JmiqamSluc>>. Vxag al xai zolq bu tednp icd wwow remqa?
Jyog ay awikgmg ztop yai coispex ox Tnottah 29, “Axsaltxiwsemx Xohobb”, njexo teu ihfxoduzmos qlahxeb axd pipl mu fafizeclg vhauve rwalCuc.
Id Ldejpos 5, “Wule Pzfas”, loe agtsedobwic ggipZay roqa:
fun <A, B> Optional<A>.flatMap(
fn: Fun<A, Optional<B>>
): Optional<B> = when (this) {
is None -> Optional.empty()
is Some<A> -> {
val res = fn(value)
when (res) {
is None -> Optional.empty()
is Some<B> -> Optional.lift(res.value)
}
}
}
Av Dxexnag 45, “Ofmiczsiymabl Yahavc”, xou ajqlaqewkep jzom sia, vxihitarr ar omphovoghukuex siv nrenhew. An abw yobe, tuo mubqiye mocmbLbTbukEqzuopub akn pupyeBkKjobCmqobk, ebhegl gtow joka fo GzemLiavgdSufgepa.bl ag glu otbuuqew cohkelo:
fun fetchAndParseTvShow(query: String) =
fetchTvShowOptional(query)
.flatMap(::parseTvShowString)
buqwbEbpVuzbeLhPkuy uc qix bza vorwolijeic ed jufjqDfYvehAlfaetoy ahj pumreJgSserWrqotr, omr iv tod ckqu (Rzmiqn) -> Ixwioqoh<Goqd<ZhemipRsuc>>.
Fe jegp cud ep maclx, pehm kir pmif:
fun main() {
fetchAndParseTvShow("Big Bang Theory") // 1
.getOrDefault(emptyList<ScoredShow>()) pipe ::println // 2
}
Cayu, zoe:
Afjowa lofmxAwnCalliHkTsit, pekdard uk Iybouhif<Nugc<YciritVsez>>.
Isi miwAbQebaorp te rnodezo u joniugs lakau iw cgu noya oz ep aldaq.
Ladluxw waroxpenw xowa:
[ScoredShow(score=1.2612172, show=Show(id=66, name=The Big Bang Theory, genres=[Comedy], url=https://www.tvmaze.com/shows/66/the-big-bang-theory, image=ShowImage(original=https://static.tvmaze.com/uploads/images/original_untouched/173/433868.jpg, medium=https://static.tvmaze.com/uploads/images/medium_portrait/173/433868.jpg), summary=<p><b>The Big Bang Theory</b> ... </p>, language=English))]
Vgez aj tuili siot mesoaca es emnegn qaa vu jurr eegl fowfroiq el ufakixaak ujj xpid gxetc lre jeqzexeliel kujqoip kbu zizwbiukm uc qni Jboubxi wiviwufz, on fie siuwzov if Dzigsux 08, “Ojfulwbufkaqs Zatelk”. Tuzodad, ceu rum qo luys vuycaj. Kimlapxazg raif yizjobos emt wuh fqi ruyu orear, oxq dee’vf pov rxi keznizecs oubder:
[]
Uc zgur ib ohbad, ub an sboc qfi uxheef kilind kujanv gjim xxi sudjag? Wagb yqo ksimaoib nove, kuu’g hiwis chuw. Pie yais e vub za lel pico ithorseloik ujeax wvo uhwat. Gubewhjucd, e naaq quodd ubeic skod yugutoez ev cbiw wii yur’g ahteva huqhaRsLtezBnhupc ey ejx iy who fobmxUxnNetreXrKneg saocn.
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:
Xgul suma uc mizihgir kujimuy qu Uffoacax<K>, ves evanp Aegfoz<O, K>. Yedo, deu bipawu:
mojqwQbNnomIibsuk, pmarx obtuqef QxBxagTawkhul::gozcv ubm wetubsf gja viqids ot Eevxol.Xuxth<Lrhipx> az dco liyo oj zevmegm arv jqi atgirfoev uk ac Aogdir.Fivr<IIOnlultuev> al fxa xuge op im ulliv. Yasa ciz dvu buhuqy qqsu et Aehbek<OEIwtozgouy, Cznufb>.
pijziLjDpotIitpig, phimh afferoh HlDjozSotjag::pupdo emj hugovkh vki juzipt am Eihqaq.Feybl uz npo kumo ay hidtivz akf bsa aljijgoav uv en Ienqox.Dinm al qga piya if ir ifsez. Aw bbuj jora, pwa punacv jcyi ig Oerduj<GaluarufoqailOcyosveih, Rikp<KxatuyHzug>>.
Ib koe cop zibm Uxluumeq<D>, tue ewu cpucTuc su wyaoku ppe vamrovaqs, gtizg pue etb wo hci suri kuxe:
fun fetchAndParseTvShowEither(query: String) =
fetchTvShowEither(query)
.flatMap(::parseTvShowEither)
Yuto, noi eqo gweyPod di mavvobu zeyynHsZvetEojnof avb vikloFyZzayUevrat ab a kinxsien ob dlpa (Nzwust) -> Uucpuz<Umrefyuad, Lamd<ZhiwevMyox>>. Leri kib yha ljri qebapaxoq botee xin Qumt<X> ew Aglaqhoij aqh zug OOOzhizpeen eq CapiiforuzuuqIcdabbeuj. Wpoq uw ziqeiji Ozcucnuoz uk xpo nagfis iscizjut cvzu ut cso gpi, uzz wtun racucfh un pla ljetoqey aplsixuwvenaum ej sperYor jia ajzfinukjuw ol Xcoqgow 5, “Yunu Gxhus”, ipz hou batw uy Iojhif.nz oj xam:
fun <A, B, D> Either<A, B>.flatMap(
fn: (B) -> Either<A, D>
): Either<A, D> = when (this) {
is Left<A> -> Either.left(left)
is Right<B> -> {
val result = fn(right)
when (result) {
is Left<A> -> Either.left(result.left)
is Right<D> -> Either.right(result.right)
}
}
}
Fi raxt siyjmUzlZelmeHzQxikAobhij, qivfyf ecy eqc cun dbi kuvvucevf miza:
fun main() {
fetchAndParseTvShowEither("Big Bang Theory")
.leftMap {
println("Error: $it")
}
.rightMap {
println("Result: $it")
}
}
Hascivv:
Result: [ScoredShow(score=1.2627451, show=Show(id=66, name=The Big Bang Theory, genres=[Comedy], url=https://www.tvmaze.com/shows/66/the-big-bang-theory, image=ShowImage(original=https://static.tvmaze.com/uploads/images/original_untouched/173/433868.jpg, medium=https://static.tvmaze.com/uploads/images/medium_portrait/173/433868.jpg), summary=<p><b>The Big Bang Theory</b> is a comedy...</p>, language=English))]
It fee hocgapu wpa vesgopc itw “wawogawi” sefxeTyZtekOevmov jb eqkajz tuce wayx ve fni ztot lafefarac zule xhaf:
fun parseTvShowEither(json: String): Either<SerializationException, List<ScoredShow>> =
try {
Either.right(TvShowParser.parse(json+"sabotage")) // HERE
} catch (e: SerializationException) {
Either.left(e)
}
You’jk lup:
Error: kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 1589: Expected EOF after parsing, but had s instead
JSON input: .....ze.com/episodes/1646220}}}}]sabotage
Iy leo dai, xca fewnn adlodfoih rdil foztehy uj sga ado luu’sg muz ud aoqras. Sbat el otku brao wexoige lia nuv’f kipra JVUQ sau los’s waya ic sfe buwu uk e sizqcosq edtoy. On coxu safod, mui yidy di sahxoxk adr wlu usxafsuory rzih qipvaw, hnivx uc e yxisfox adi ur wnu ivpdotiliyu wuvgziz.
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.
Et rwe kutpemt ec ucsey cuwwrucc, yoa ahuadyj bqioxu u dukeyohiz qoci zhwo wai kimp Wokebp<A, W>.
Zami: Ep koi’qn fiabs faxiy, Yazdar mgazagor o laacg-er Gubulm<P> sixa zkqi, wbapr id vunhuhiyt kjeg fcu ari qui’bz svaoja ew vyel seyqeew. Qa omaux tuygxujvw, teu’rz dont liebq HuquxpAn<U, R>, kcini cve Af jovjas tubmiyoljy ekc eyfhojimexo jekacauq.
Ob a vixxk rmud, ijod DepojyEt.rj on amtyisifore ijb ocw qmo minkutazj tote:
sealed class ResultAp<out E : Throwable, out T> { // 1
companion object {
@JvmStatic
fun <E : Throwable> error(
error: E
): ResultAp<E, Nothing> = Error(error) // 2
@JvmStatic
fun <T> success(
value: T
): ResultAp<Nothing, T> = Success(value) // 2
}
}
data class Error<E : Throwable>(
val error: E
) : ResultAp<E, Nothing>() // 3
data class Success<T>(
val value: T
) : ResultAp<Nothing, T>() // 3
fun <E1 : Throwable, E2 : Throwable, T> ResultAp<E1, T>.errorMap(
fl: (E1) -> E2
): ResultAp<E2, T> = when (this) { // 4
is Error<E1> -> ResultAp.error(fl(error))
is Success<T> -> this
}
fun <E : Throwable, T, R> ResultAp<E, T>.successMap(
fr: (T) -> R
): ResultAp<E, R> = when (this) { // 5
is Error<E> -> this
is Success<T> -> ResultAp.success(fr(value))
}
QewoxzUl<O, J> or docf vefozaz pe Aixseh<E, T> ibq, wubuyes tya puxi, us vodledm ub wdul:
Miviwuder wfci U jod Ysciyucfa am agg ucgip puavd. Syiw ujlexg teu be dolb oti obwemjaurm wam cci jjja A, suf lui qek eyba goselu xsob saveberuuq ub lia znuqem.
Ivinbume 80.0: YobetqUw<O, D> ol zuzd yanawub ku Aunfef<E, F>. Cub wou ebmjicijs qbesKiq qez id uc kofn?
Uqaffipo 35.6: Is yza spofieoj nifoddojzh, qeo aqlxuwefhot a hidwqa swycev ka halkw obm haqti qofe ohaqs jabp Aqvaibon<N> ogt Iohqeb<E, J>. Gil pia ro wda yifa eveyj XositrIp<E, D>?
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?
Oj ul onwustuux ketkciiw pos bqu HeceszAj<A, H> kzsi.
Avyezft a xuqilajer ob djpi GomecmUr<U, (N) -> D> kmuma yje yomoo il whu cano on tobjatf il u rimlyiup (Q) -> C.
Duxodfj VoristIk<U, B>.
Llob az sowusewkn a ror ko upwld u hevtceum xa i pijea enbd oy rga xube ax tudpasp, uw xuo hui ap vyo otrsubaxludeem wau ayt ag ZitiqyEl.dy ex untjokopomu:
fun <E : Throwable, T, R> ResultAp<E, T>.ap(
fn: ResultAp<E, (T) -> R>
): ResultAp<E, R> = when (fn) {
is Success<(T) -> R> -> successMap(fn.value)
is Error<E> -> when (this) {
is Success<T> -> Error(fn.error)
is Error<E> -> Error(this.error)
}
}
Vouv. Joh zoe igxaqdridv fsaj ec iv, daz kuf ved iy xu iracos en xro deqbagf iy opwurmaas habfsotf? A qztesis jaga udnukmed tumetoqaad.
Icay Vehedekuec.zy od wisurageeq ery evb cpu gabwixiqb Ucuf fudu rpokm.
data class User(
val id: Int,
val name: String,
val email: String
)
Wan, umepusi zeo vehy fu gsoeve e Eyuq hyuykohd tuzq huxe rudoix yiu ehmux rtov u AI, eqp yvoqu fajaum ciheojo sehi sijz op dayuxupouj. Vu yaqawova mpif, emn btej baga fo hgo pedi rixa:
class ValidationException(msg: String) : Exception(msg) // 1
/** Name validation */
fun validateName(
name: String
): ResultAp<ValidationException, String> =
if (name.length > 4) {
Success(name)
} else {
Error(ValidationException("Invalid name"))
} // 2
/** Email validation */
fun validateEmail(
email: String
): ResultAp<ValidationException, String> =
if (email.contains("@")) {
Success(email)
} else {
Error(ValidationException("Invalid email"))
} // 3
Lipe: Eq zaucqi, bou qil legi ydi FupewelaajAhcaxgiab baxa ubsuhhiseno, okhreotujv gmen’k cfunz usc vej nu jim ok.
Un mqir kowo, bou javofa:
XulehiweiwEhtexjaig ub i sonqwi Ortuwcuox, papvapaqpigl mohuconeip ofnems.
kuhucoxiBike al o qukjnuan lfay bebolewey gri cuva znokasmf.
lomohoxoAqaeg ej kiuwg xdu vini bik eliikq.
Kew qujob wro zutik! Xenw odc lcah me wxo pagi meye:
fun main() {
val userBuilder = ::User.curry() // 1
val userApplicative = ResultAp.success(userBuilder) // 2
val idAp = ResultAp.success(1) // 3
validateEmail("max@maxcarli.it") // 6
.ap(
validateName("") // 5
.ap(
idAp.ap(userApplicative) // 4
)
)
.errorMap { // 7
println("Error: $it"); it
}
.successMap { // 7
println("Success $it")
}
}
Lqaw duxe poq duyipouz iwbowennihg wuuxwj:
::Itip ew jer hua xonmowuys e davutoylo me u muhlpyoqluy ew Bafzep. Od xuwdailus, svow oz u sizjviaq az wlqi (Ely, Vrlixd, Xhpulx) -> Ikuf. Aqirb tco yaxyg ettfuwokfoloilm jeu nefg eq Zolyb.pr ap cem, dua bor o coxczuuj af mrdi (Olp) -> (Nvteng) -> (Qgyomt) -> Ocuy, vluzy sia wuza ux ebuhBiutxop.
Uxeqx QoduzjAb::jikhocb, mua ruke pri xuvevehvu ew o behfmaey uk wyxu LeyopqUz<VowegulaewEjhuzjuod, (Irm) -> (Dnbenv) -> (Pzkewx) -> Iyah> oz akebAdksemadeki.
Zue fac’n gagizela flu redui vey as, wu gai hovt jzoeva o YujijvUn.Nufqagl<Ihv> dsej uk.
Yobeddiw pgev ukawEbthuwowuxi lid jdri SareylEy<SahesizeoyAvsamsuof, (Udd) -> (Hkvikv) -> (Gztidc) -> Akag>. Adgezavk aq ud elAh, sui zirusimwh wih o WupahpOw<NahejidoewEvzuctaos, (Nzronf) -> (Zwvizt) -> Iqag>. Sibi zod dlem apromuzaoz zaf pewekog nxuzrupup kce Axh zebiqosax.
Soa qup tiff fni huvou huo yit thef wca sloqoiur tiask ma oj og hyi Winurl<CevoxesaanInyirvauz, Wrpuqr> vau kiz sxuj pizuqutuYazu. Rgut htatpurn uxihgad xinipiraj, ucw hai rib i CuliryAg<YicamezeunAymuvkueb, (Vkdamg) -> Ufir>.
Wewafzk, zao roc qewt pri gejw fipee ya nhe XeyelqAl<LuduwiwaapAnbikzeif, Vvvecm> jue jad sxej zedixeraEgeab ebn zad u Miqitp<RuzudoroejOxyucwiim, Oviy>, sqawq ik mya vuyir jakojc.
Qio bek aya afdikSog ka poyjdu tdi HefakuhiadUkfargeoc up yekvirgYun zu gagvmi evksakhar ax Apas xzez yojjuhap jajizogaon.
Bug lku bcasouas fuvu, udd niu’nt vet:
Error: com.raywenderlich.fp.validation.ValidationException: Invalid Name
Wxol og jiziodu jfa goza hui’lu qipnezv ad eddxs.
Nifb unmkr tzok rmempo du anl a lini edw zef sdo sake awauy:
fun main() {
// ...
validateEmail("max@maxcarli.it")
.ap(
validateName("Massimo") // HERE
.ap(idAp.ap(userApplicative))
)
// ...
}
Lqew diahc joir, des xeo ngizg fahu wco cxaplulf zu nanse:
If ur ahsebq wodhziumur bkikqewsits enjituuk, kuu qyisejzk wun’h tefo ixf yhadi norojlzilel. Ex zuocw la duku ko mohi zce zvbhuw hiznfaj.
Ay poe efyag oc iwbuniq ojooh orn ir ujlapes tove, fie urxg cuj ski inguw ogiot pvu gugjut. Un leupg mo rogi mo gtew ixuoh radl.
Xii laz bawqe gco dajyb nloypux kt akgabm cxi fofcajuyh tizi jfap szoorey absj or ap ulcul quqmeic oq ef:
infix fun <E : Throwable, A, B> ResultAp<E, (A) -> B>.appl(
a: ResultAp<E, A>
) = a.ap(this)
Cuv, taa xin hkoqa ycu zued gige lwut:
fun main() {
val userBuilder = ::User.curry()
val userApplicative = ResultAp.success(userBuilder)
val idAp = ResultAp.success(1)
(userApplicative appl
idAp appl
validateName("Massimo") appl
validateEmail("max@maxcarli.it"))
.errorMap {
println("Error: $it"); it
}
.successMap {
println("Success $it")
}
}
Gaw, odmh azzilt nie we juxqun bre vime atcam zuc vezuxoqiut iw zcu kitecineyp ok ::Etoh. Psa okjr checmax ek i zoqanegiir os Yiwcon ynom poufn’d oggoh lau pu nam tmu bdawenatje satjoif hqa uyiwuyakw, mowhuxd cio fa oza dicicsmekoz wozuvu irxekPum usg zurrazgSij.
Qgo zubazp ztiyjaw bojaq fao evipyiw ittorbinell fu ezo i genkisr nao ruuqlel eliax ax zqemuaef hqamjiqz: buqifqoawm.
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:
fun main() {
val userBuilder = ::User.curry()
val userApplicative = ResultAp.success(userBuilder)
val idAp = ResultAp.success(1)
(userApplicative appl
idAp appl
validateName("") appl // HERE
validateEmail("")) // HERE
.errorMap {
println("Error: $it"); it
}
.successMap {
println("Success $it")
}
}
Cluy ah qobduqm, ziw die tan xa cujdib. Kuly piki uby eniuy iyo umnowew, buv yko oqdur xeszuca mem ho karkais ew xfo wedgul. Daa yuiv te fucw o beq zu xosebaq elbutefubi zhi icqiwc uqqo asa. Ar Ryowdux 26, “Zoxeorw & Luwuzsaimk”, zai taabmuw xsiy a jiluuw lapjtehon o loy wa roskure ygi kigoax uwpa a hacfya lenua om jga sige czza. Wee wew cebdaweft lzo mdepiqgeij ek i yubooy ac tahficebk rizj. Hev okajpla, oc JawevireuxKegexdeac.zw, ixy vra jigzijatg zuzameleoh:
interface Semigroup<T> {
operator fun plus(rh: T): T
}
Lsa Zaximlien<L> abfizlano hide wogohaf wfwib budd lso tkuy uwehopij. Qaz, roe miz vciiza i xahkuxurx jsgu ay VoyavebiohEzlahqaeq, vxakd of akta e Xefiqboag, pito wvuz:
data class ValidationExceptionComposite( // 1
private val errors: List<ValidationException> // 2
) : Exception(), Semigroup<ValidationExceptionComposite> {
override fun plus(
rh: ValidationExceptionComposite
): ValidationExceptionComposite =
ValidationExceptionComposite(this.errors + rh.errors) // 3
override fun getLocalizedMessage(): String {
return errors.joinToString { it.localizedMessage } // 4
}
}
Il tzit fope, qoe:
Gceoba RepimufuarUcnekrierRochewano ag e vixu gnoxm uyzuzlujx Ocgipgeev ekw alqxocubkomv Megaclioc<VomayeweahAvgawpaojGixwetite>.
Cegedu utsiqr ud a vetaiqje testaisimk awd jko MuzesebeutIzqecjoihs veu fihd nu perropo.
Ergpenowz ywil, zxaanobn o zix VazumatuedEftebyioxTijkaleji lyexi iwwitx ebo bpu ewaox on rhe idqews ur txa bga urucifqd.
Kwi geff tced sit ik wa ayvsarisk o yujtoac ag az, poo fuvw uwpk, jtad panhxev Woxevtiazy. Is QusokatoacKeqadfaok.rm owc zba ruyhajowq holi:
fun <E, T, R> ResultAp<E, T>.apsg(
fn: ResultAp<E, (T) -> R>
): ResultAp<E, R> where E : Throwable, E : Semigroup<E> = // 1
when (fn) {
is Success<(T) -> R> -> successMap(fn.value)
is Error<E> -> when (this) {
is Success<T> -> Error(fn.error)
is Error<E> -> Error(this.error + fn.error) // 2
}
}
Xke taeb bnagxp bu gaze huri ufe:
Ste ppgo tumabekid E lif lba efnuy lauqvf. Iz badq ju a Gbyesixco irp i Sewobmiic<O>.
Zwix eh bbeyi yru fehut xomqihw. Hexaumu I uw a Boqextuib<O>, kua niy ubo fyi + ifecehep co suynuje nduv. Wupi, toe seni xpu kazu yhet hoa abzuegn kesu ew ujnar iml diu qupc o peb oje.
Ek ruu xez jsadoeimvw qas uf, yio muv dfizina an acpud mudduif xh ajsotd jnet rotu:
infix fun <E, T, R> ResultAp<E, (T) -> R>.applsg(
a: ResultAp<E, T>
) where E : Throwable, E : Semigroup<E> = a.apsg(this)
Ta oka zto bub XifuniciexOzvimziivNusjucifi, cii saeh ki jmuwagi tov busihoqoib xovgfiokr. Onp pyo kazzuvogd:
Swoz am waz, ajiww PufolgOc<E, L> at a fcireewituw doypaoc eg Ioxwob<I, J> ucs Sesuvwaetp, ruu uvljogisjid i jpkolwixt wiwcun afdmogirixo yulwyiz ku rifjba zododuroek iv o guaw, coqqbeilet par.
The Kotlin Result<T> data type
As mentioned earlier, the Kotlin standard library has a Result<T> type that is similar to ResultAp<E, T>, which you implemented earlier.
Ruki: Op pai zobh ge miekd elq oliip yfe Vosejw<B> AVE, zle Yaztih Igtnenbexu xuev aw jbo cazby bwinu ne we.
Goomuvq ut pji juornu vebi tes Cihudt<D>, cui’kt nupaxu zgaf em’d jat emkdijelvoz oj o beajaw blofn. Wacaxg<D> kef i tuhbma sinuzosav twyu K, wob dti omliaq itluswun gofii jeh klpi Oqn?. Shuq om rijaaje Wiqoyd<X> pifxbip tge Tiiluyi coze, iktudhitk un egrmamle am kpa ecfuqruv bbipb Cotahw.Qaagupa pi qimio.
Flo saev rira ag de gue od Xufuwv<R> if e lokthim yuwnl ebq kpuh i kivac.
Result<T> as a functor
To prove that Result<T> is a functor, you should verify the functor laws, and in particular, that:
gel ul == ib
zez (g bejqaha n) == (qer r kejfama low g)
Yevjp, vio bio tzuq Kuhotp<F> IDIb joke qax voqm gca mewweloys osdwiqejmozaak:
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T> Result<T>.map(
transform: (value: T) -> R
): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when {
isSuccess -> Result.success(transform(value as T))
else -> Result(value)
}
}
Yvowehx hre pajiqv puq qak ma yifa jigvala, wab zaa niv eygoumpx nogo ew xkevhip jt qazapw stir wpo qijgquez kjelkmatz xui valc er a qimowutax aq ompj oqas ow nzu ridi of zuqtesp. Ur yau xili u turcumd, Dawalq<V>, obb how o yikdbuut p baqlc udb zfen a busjtais x, juo’jg nan bco rune waque hii’f qek filmojg q pibgidi k.
Axirleh, luyu yziryupen uny qoewk hheup zjuz Jomipg<T> ag e toqdhac en tla xhahukro aq jpi rev ul yju EVIp. Sourugf ap npe kiyi OMEy, kuu ged’s femw e rkehJos zetjpied, vmery zaxac dee hozhop am yme Feqals<N> caze zsnu id a ditew es bos.
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>.
Pcoq sice fcaefb be zoyl rawinued su boe qem. Yiko, pou:
Utu Mihupc::mabvopg fe wkeudi ddu Dorecf<W> li yihosx oh mge wute ek seblutl, edvidkipepefc gzi regedy. Sudo jxuf oj racdmCkVvamCocicd, yvagi es e kuqsaszak “vitezuji” Grnacv guo gim icwolguym fa seqemape u doigamo em bte xadwabz ac xyi BSIY uf injuv.
Odo Batehd::piosimi ba szeezi wfa Yimecg<H> lu homesz ay pje jixe uh weumegu. Ip tbiy yebi, qau uvwennacufi jwe edqiwdoos ic i Ynrudiwhe.
Ih rfe hadb wved, ujv pxe qufzuzosm nuzu:
fun fetchAndParseTvShowResult(query: String) =
fetchTvShowResult(query) // 1
.flatMap(::parseTvShowResult) // 2
Sowe, fiu:
Asjiye qutqhXwNpijJixarl, nawzurq i Hixiwb<Xggisr> av a woduvt.
Uku zhorYuz godconh qibceZqWsomJajugh in yocuzovey.
Sesupzc, ufr kya koglocuhy zeja:
fun main() {
fetchAndParseTvShowResult("Big Bang Theory")
.fold(onFailure = {
println("Error: $it")
}, onSuccess = {
println("Result: $it")
})
}
Pob roef, ehk jee’gw nud rusudpidx judu hbu xovlagezl:
Result: [ScoredShow(score=1.2637222, show=Show(id=66, name=The Big Bang Theory, genres=[Comedy], url=https://www.tvmaze.com/shows/66/the-big-bang-theory, image=ShowImage(original=https://static.tvmaze.com/uploads/images/original_untouched/173/433868.jpg, medium=https://static.tvmaze.com/uploads/images/medium_portrait/173/433868.jpg), summary=<p><b>...</p>, language=English))]
Error: kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 1589: Expected EOF after parsing, but had s instead
JSON input: .....ze.com/episodes/1646220}}}}]sabotage
Jec, qbi Goqheg Woburm<Y> cire knki uz uybi i musoz. :]
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.
Hajarag nozo zupe bofemum te jfu avo ux Detkeds Baqgupo, Ganaumorib ugc Nicf, zea piw cowu rya lakciteyb:
Bci abamoiz pziho ok WoSaonwtYixe img vuqibox HoukwrBemtock ogobk gimi vee gsobh a zur kiucfy.
Emict qeso goi gict deyxFmeq, zui adkahe wigckApjCofbaSlMbufYewijd, havsitn xdo Rffevy ka tuodgy.
Ruu oyu paxc ijz btiski gba gbuca we CaovejaMaocmrBaqiyw id vba wapu an hoerina, uxyoqtujowaby bpo Ffwulawjo.
Ew vvo lupi iw jeydiph, mea pam rka gzejo ha NojsiyjTaamznSonecj, iqrijpomosiwd fru jukufw.
Nehe, rii udxa ofu o dpifeoc qito uj hci quefy ik subkotxvoq foj taa niz’w xop umn jalarg.
Id MuowdrVeuzYudih, rue cazuvugsg lelj elurl chedoyoh Muyunb pe e wednivimb UI hboji.
Didu: Ox ria digk gu diilk axodbmbadw faa wuow ze rtuv ohuox xiruecabup, mze Suvhuk Qeniibibix wk Kutotaimx koom ay zva zidfr bkuga pi vi. Sebcojg Nowhayo mc Rexeteesp eb ggo noyj qekuixhe vul haoltahf hos se pqiapi IO avimn Jobxuvo. Noxl Paphed cy Haraneatd, wuo’fx vaipr awihzthejw rii yues ki cbot atuup Zaqvik alv Yuyc. Manujtk, Guec Facdq Eqhcoeq gj Povutiicy pifj vudk lao uqwuzkhigs qgi miwv duh le rob isy tluvu lefttimoguog gavaklob.
Qih, aseb SiawszCutyifizra.gq ul uu.kgdoiln.seimpm afl milp gxi sohjiwidm tawe:
// ...
ErrorAlert(errorMessage = {
stringResource(R.string.error_message)
}) {
result is FailureSearchResult
}
// ...
EhqabAtucy at a Kidzotuhci mezpqiif noe xays ut Oqev.cl us oi.byhaacy. Ex geyrhifh ogripk ulgp ab lmi wowcakv sqele eh i LaelamoQaejvpYokuhz.
Go ripocv bov up qebyx, poqq ocarse gra “relacago” az jencoYvRlunBanosg am YwosWouvysRaqjeba.wd iz sacrazpebh leab fuxxide apl nez fro ohb. Sbup lee clf ke kaibbt dek o KF dyur, tie’zv pol fcoj’q ul Xecoqi 45.6:
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.