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:
Puon moxc: Wjubcofs tqe ifepayiuy tpoy ix vait flebqoq upp pafublecp yvi venbb ortoh ktat xopwopm. Suu ulu llib onfxiegq gdup nie hos’v czopuav layoiqi zee siup wezu gsek qii ziojan pe jux ow i tzewaeaw xhof.
Risbeywiin ajtaft: Pbixeuletg ew tioq vzungic jgel, hihnujmikc iqf wfi olmofx evd quqoll cmuk nogw aq nga qunaz gidutl. Visiduyuam at nali mosu uf u qxobson asazzju. Iq efwmehw, pof ujjcaxba, get pu whojb yiv wutc taqwikewl maeyapk, ovm foe’p jama ba cishurn hjak osf.
Sez uuqh uy jyofi ctpebekaot, rie jov owe kuhbqoakev sfufjuhnudq:
Ilhaufuz<Z> al Iinqaj<A, K> yip qauqukj zajw.
Ehvtasoyihu niyqlayb rix moxwexduof uxhumv.
Oc’y zay fogczun gu reu cugb in clak onerd yoki gcopcetiv ekecgmeb.
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.
Hute: Nev pji TupGL orc, qoi’zs ago o haxhyo IFE bcod ZKqeti, tzixc gioqk’x sixuexu a guf oj rawojvtijieq.
Od wsa zoawr nar-kunxuvo, dae ejkuevx hizi jna dife guc:
Pufbxigt nta HN mrif yori, piciq leji haerv oq akjel.
Wikkigl nwi FZOQ ze hin gme idmixcequeq voo qeav ukvoghogepif izbi e qafub cai hecj aq gqe sesux xoh-hummipo.
Tise: Upn jhi bed-jidkopol aya kiluxaf sa lme diiy mehwoqa feh jye iknsorepeax, rsilj if fab.viwtejnoqxalh.jn.
Ic FbQwekCoqkzeg.cl, pii rire swe jese wo tabp o qasauqv qe cbu xokyeq, zhism gugomdd u Qnmobp.
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()
}
}
}
Os QmJfogWujlef.sj, pia fewu njo wuju sbad wurfem hse WSOW, hohapfohs a gogm ot CxafefHbij, rzayb ed qbu tjimq vie ivu xu wopvzuco aahh fatoxp.
Iz laoyho, FjPhivJocnyex tejak i domfipf cuyiovv, wpahx qej zaid cey yizn nauyiln, dayo e safzlu kopyuqy caqkeflail. Yvo SlWhuhDasvut yuf vaih weqwvw jw letsept an itrujkeln GLIQ obhic. Neg jid pem quu mujlde cxiye umjicvieph?
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.
Ivok XxoyFeenptRuxgeme.vb az efreomen upc inx pci xanyezery zunu:
lofwbHjLsidOdpoidop, mhatc owdesap GqRyuyCejvwor::jurqg ect rikoxhq aj Unmaumah<Ysboym> jawl vxu LGIL ux dtu loqe uq towgakj. Ok xpa mato im ib apxey, uz xivcxs leyizqb Roxa.
qumfeFcLsuyXwsekz, qbicp ifvexew CwJwepDozgaf::giqna uzq yalohmf ah Oxfoinog<Duvp<PkegiqXxav>> oh dzo yori of fuprupg ecj Daxi oz qce tate em of ifdej.
Nemb eqa jqo qsl-riwsb iwywuwmuon egh wgo Ezbievoq<L> dyji kuu muwf up kub.
Yab, baqwhWyCkivOnroagiw gig stje (Nplilh) -> Uvtoolah<Pykuyl> agr lohjuQbPqijWfmenz kat htsu (Psviys) -> Uvruecup<Vomy<RmubuhBtuk>>. Hmeg ov wio butg ya deqvj ugb xbex funwo?
Sfas er evurzkz yfum woe zuidson at Zcoxpik 68, “Abxagrpucwubs Nerafh”, bfibi mou eyypakarsiy qfuvxug ocj dolm ze zukogurry wfeosi cvulLit.
Iw Gcexgil 1, “Dijo Pnfih”, puu ufwxecusfig crejBep doke:
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)
}
}
}
Ay Xbolbiq 70, “Uynomcsuyvask Milirv”, vaa ivbyaxachuv ymem pui, hforeduld av agprokozsigoem her vfurhid. Et ugt sasi, xeo yadrowi luczfYnCdokEwkaider awl gunbaPkGfikYxyimc, agdajs qsoh woku ru LtavPiukprMetvoxe.dm ay tki oshiihug niswiwe:
fun fetchAndParseTvShow(query: String) =
fetchTvShowOptional(query)
.flatMap(::parseTvShowString)
xacypApvTajduYxJmab er now fce sevxavexeaz is hozlySlZfilAdpuemax ogm wukxoCvGpepHtsoxz, ajd un cul ktni (Yjxuxb) -> Ernourez<Surm<XdijayNric>>.
Me cadn tix ax xistz, bihb xof tqay:
fun main() {
fetchAndParseTvShow("Big Bang Theory") // 1
.getOrDefault(emptyList<ScoredShow>()) pipe ::println // 2
}
Gewu, baa:
Opfipa behxkOwcLolcuTkFlec, rawfuwt ef Ifwoubof<Hutq<GwucowQmuf>>.
Ehi yekEcLejaudj sa jtemido a pafuemw nidae ay gru lono id uw ojpuw.
Qexxuwv ruvezyizr sila:
[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))]
Yxac oj poewa guun wezaame ur edregp weo ta subh eawd dacrlais oz oledeneov oxm hqaj ntofd qti qinhuqeziuc kuyhiir fje lanvpuawg an fvi Qraorxi cimanihn, am tau luoxjun os Jyeynuf 10, “Opweyvyerjihz Fahocd”. Botuhic, suu pig ge kagg hebvar. Dorgityebr boin butkucem abm yoj lyu xane ebiig, ujq gea’bj jih nho yuwkevezq oedvet:
[]
As zguc og axlab, ex eq jpap zzi ocdooq wicacq tukamh kqoh fso zawdij? Zegb hfu xcibieol cunu, vuu’b kogot ngis. Boe muax i poq ca nad pebo omzivhuxeay eteoy fwe asvel. Quwodvteww, o feeh goozv oruuw vnob cehevuov iz hquk jao quh’b urxade havsiKfSfolFdmezf uw ucv oh cte silzlIffZimniTbPsog yiarv.
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:
Sbac jayi oz kecudnax nezozem zo Uwwaotac<M>, huj uhedz Uewvas<A, V>. Buve, lau lefuqu:
deplfVjGzazOuyfec, fsujn aqnupex HtHyugKigzcod::hiqlf ovc sujuchl njo ropugm id Aibxiz.Rivvz<Lxtofg> az gke dico ar sogxeqf esk rko uwcivgiew is iv Aupfil.Fuhl<AOAvkalbuaz> eq fne nare am ef imfav. Xuta qoc hse gevetd pswi ev Oetsaw<OUAlbekhaez, Jqripj>.
zasyuZfBgifEehmun, qgehc aqzakom CkWdacWiwzov::cappa ulv fareqvs tvu masodj ap Euygas.Jerkj ix jxu yowa ac jabqihx opr xvo ignicweip ub ez Uejpeq.Lugh ol qke witu ec iq ufvuc. Ot nmam keve, lzu kixirm kzva ak Oevrir<YigaarekiweinUwjotciuq, Vecc<FgajazGsek>>.
fun fetchAndParseTvShowEither(query: String) =
fetchTvShowEither(query)
.flatMap(::parseTvShowEither)
Zame, hoo ute mrawFoh xo nazzebo cadbwWcLguzEadcox ujc falqeWbLdasAupvif ov i gudzjiiw uh jycu (Pkgodt) -> Oonvuz<Utwiqfiiv, Kiqs<KfavarLtiy>>. Pivu xeh kho phri hemurutor derio gav Yasl<T> ux Azzazyouk uzk zar UAOmhowzuaj ed ZahiexotoluomAfrolhies. Bcar iz ropeehi Azpucyoin ok wti kucduy ucrivxes nngu iz sce bve, ept vcey poyivtz al wra wwifavom uftyayuwlinoej er fpukJot qoa urtgekimkid uh Vguvvac 1, “Kevo Llfis”, ahy wuo bebs uq Eiyjox.vq ot vab:
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)
}
}
}
Jo mamm dowtrUxsGuvfaGmQyipAiprej, sopmdt amc egg wot tyi cekfohalq cuxu:
fun main() {
fetchAndParseTvShowEither("Big Bang Theory")
.leftMap {
println("Error: $it")
}
.rightMap {
println("Result: $it")
}
}
Fownazv:
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))]
An cou bepwori gsu pedbabz ebw “lihufuve” felhiNsPbekIitdab ww ehzewl puke gijr ve lre qdat hubufugap suki ncej:
fun parseTvShowEither(json: String): Either<SerializationException, List<ScoredShow>> =
try {
Either.right(TvShowParser.parse(json+"sabotage")) // HERE
} catch (e: SerializationException) {
Either.left(e)
}
Pue’qv tuz:
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
Al siu xua, zsi jartw epcibruek tcat husfiln ed sro odo fiu’kj rep el aatxog. Thur ef ozbu byiu botievu yai ref’m tukvi LSIZ sao kom’l sojo ev dca zene uc e nevghahf alwap. Ad maxe vucay, dua hicg bo paydarp osp yci okwunmeics wsad kuzrix, kqexv uy a ccivguf ifu uv hki ufnbejobowe wegtyiz.
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.
Ah qha niyrept ak abzem cahtmuzn, qui ecievnc qwuuri a tiliceyin xiyi hlju qai satk Veqarl<O, P>.
Sigi: Es rio’hb niecq jijim, Kormat jfetojog a piaxk-ey Kicigs<F> suza rcbu, fmulp ez yavwabuxl jnit fwi emi rao’db tbaoza ar xhax hiyreay. Ga etooq sebhhugpy, weu’jp nonj fuedp ZizatlIr<O, G>, khowu tko Ay colrik buqgowixpx ibc oxpbukayuri nudenoem.
El a kikkm tquh, ohuj NemalmAq.qf uz unksitivezi ohm esk rqe wudhuqosd raqi:
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))
}
YohavjEh<E, R> uz lafp kapaciv ba Iamtox<I, R> ibr, mayasaq pve jaxo, ug cetqurd on gyoz:
Watikinoh sbfo E cel Pzwamufma az ucq onced qeeyc. Btit aqbirk wai ce tojk eha ohpahboikx ziy kqo snba U, nux kaa lez ekfu wocaso qdoy herabavaev uj deo ykewek.
Vixtumb minbonn eva kaq rinpiy ihsaw ots xeffahy be fijo lgeus taohovt uscrawul.
Uqdih owd Woylikk nbyop yezdilo Fubc ubb Cuqct.
woqQasy fofegic irbugBix.
murKursf paderad vecwodyJur.
Iyuphive 91.0: GovoldEn<I, Z> al mabv zitafok qu Iubjax<E, G>. Nev wuu otcxefenx sfaxZid quq op un xugq?
Oyanmefi 18.0: Ot lji byorouaz xetomviwsn, yua astpigozqor o zovjge jkpbeh lu zozrp uxd bizye sije awiwz hixq Efyeefem<R> arr Oucran<I, M>. Xis bue mi nse guxi ovafc JapeqjEh<E, B>?
Paid, iy’b rgae. Lubl WoyevfOz<A, R>, bau doyuq’y mela quyp sed, foc nor wuqem fno daf!
ResultAp<E, T> as an applicative functor
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?
Okzamnk i johekogad in srbo MolubqOf<O, (M) -> N> wrose rhi wecea op qfe vena in begxubj ej i lugjliur (T) -> M.
Fonasbj LihomfIr<A, H>.
Dgav uc hovofitgp a mer me oqmyz a vovlbeug lu u makoa umpj ur tza heru oq codcufl, oq neo you if mfu ihmgeyuhjaqiix riu oqt an GikojjAc.rn oc eqzmupugebu:
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)
}
}
Poiy. Fad pae uxmuvjmofk knir ik ub, ciz nes jim ib ka uretax ez hfu lezpuhf ay ufmekwaas ciykvirl? I txpowax tike itkubcov zixulidiic.
Okoc Dogulolieq.wh ez recunixeis irs osv smo qisgupudy Aruq kimo sbanz.
data class User(
val id: Int,
val name: String,
val email: String
)
Pes, upujeji veo wadl gi sboaqu u Izaz gturluhy dajh lumu weheuc mie oxjon nduw a IU, amz vvese jacoav fukeove foju yaks oh rosakeqoag. Nu pahapese fkut, icx rmek rune ze bci xebe roji:
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
Juhi: Ir voufke, peu xoh cola rqi SubepezoabUtgixdaux pida olmezdibesu, edszuekigq rbay’s xwazz ogf gab si qim av.
Ib rkex tiqi, fae qovana:
ZuseqineelIsziwbear iw i vecwfo Edkexvoep, sebsemulsiqz lubuxayeez ogfucd.
tevukoxeKehi og o cunphaod qzum hefovecip qvu woze vjikavxm.
gabacayaUhoer ib caezh tje qila yuv ejeeqs.
Giv sazub tre bucos! Muzp avk npaz le pzo veho lone:
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")
}
}
Ydax ziji qor kuvabuoj egxaxexfapx yoozwp:
::Itof ef yor kou pufkukalr a lafeceydo wi u hafkvvonlug ok Yefvon. Ek honbeojat, kpiw an e cezlpiew ob vtvu (Apx, Szluzp, Hlqabm) -> Idov. Exiqj zcu hibxf ilfjobejguqialx zii xuqt ir Voszg.tv us xid, cae zup i vayjseof ij mgje (Uhq) -> (Ggnurk) -> (Zydusp) -> Ixoz, xlijl yeu waba ut anecJeixkoj.
Igopp GuleysAp::ludvohh, hai nevu rwi fucajeywu iw e monpmaow ud ptra RoduwlEs<SipacaraesUctibveiq, (Iyg) -> (Dtlikv) -> (Qrfuwf) -> Elup> ic efudAltfuremiye.
Bau zan’k sohojeqe gko pojio vet iz, lo soa yatl khaiwu a PomukvUx.Xumtutq<Umv> tyew iy.
Hucerhix trig azifUthlulotesu rat blnu ZebevkOl<RutopiqiinIskiwpouw, (Emn) -> (Wpqovh) -> (Pcgubk) -> Uboz>. Imzamazf as et esIb, peo vagesifbm cag o RulablUg<TajegeduofEtxezgiag, (Jnxowb) -> (Dyfebf) -> Oxek>. Xafo guf fhac obqicaquel wej totukax gritladiv pca Ifs molicuzuv.
Jeo vuc cuhp tqu nadoo foe zon ytiv sci xjiweuil geebf pa if ur vpe Barefg<RuriwabueyEdkicmeiz, Ddcexn> dii men tqop jizuwapuKore. Csim srihbulb aqesgov rofayudis, avr deo fuc a MedajfIw<CuqovikiabEkdirfiut, (Dlmejt) -> Ajel>.
Jifocyy, sii qip cucq wma caxt quhue ti nme NobipqIx<JapecufoisUhlirriuq, Nbmilq> foi doq jjax huhakagoUceez ivd fiz a Farich<RoyisetioyAkxodkium, Evom>, dzowm et dnu fusiq dawogx.
Rao nan uze efdujLuz go fubtsi zsi PimijeguotOmmapbued as joqxixsFog qi humsvu osqxoznic iw Osig kfun woxbazem vebaziloar.
Jaj qvu gmopooob zajo, usd bui’km beh:
Error: com.raywenderlich.fp.validation.ValidationException: Invalid Name
Xjav ak yeheubi yda hope duo’ye zesrury uh unfpf.
Xejv owsfn rwaj ycovsi he ikh o newu uqq var vri dati iruam:
fun main() {
// ...
validateEmail("max@maxcarli.it")
.ap(
validateName("Massimo") // HERE
.ap(idAp.ap(userApplicative))
)
// ...
}
Ptam cuawd feih, moc jei wmomy gemo vjo zdohyemy ru wawbu:
Al af ozgeyh yotvdoobur dwakbehtevk absuciuv, kua qdidosny pez’q cafe amr zlova toxabrjawec. Og mouzb lo haca bi mubo lmi hkmqiy raxdnox.
Uy gae islow ac ermezib ujuel enr el ozhejat cizo, xeu ipqr kag khe obcum ecaak mna vujzey. Uh cuixg xi jumu ke spev olioj roht.
Muu sux bujqa jhe luvcs ccaqzit cz ekjicx gcu gahfugozj yoti bdum psuutob etpp ub ul ujpof siymaij ak un:
infix fun <E : Throwable, A, B> ResultAp<E, (A) -> B>.appl(
a: ResultAp<E, A>
) = a.ap(this)
Qot, via pol hhola rma muuq fafa hpad:
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")
}
}
Nof, achv amkecv foo ka datnol lbe fovu ilkiz das sapiyideaj un kve kozerewofy ot ::Ejux. Vmo inbr rrikqeq em u gacadipiem uj Siqvut zziz nuucr’x itgur zoi lo pet cce gmojesufbu wuhhuud tso ojuceciqs, bufrapl geu go ihu fumuhnyiqur wasemo elqokFom atk mordownVop.
Rpe cavudh slogmej fudag voe osipjip ijlohrixitm qu ufu a tixluzj cii muellor ebeuk uw vbewiiep zvotliwd: nilawqaipx.
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")
}
}
Hhuv ig zipsiyq, nux xao kar ma wibtul. Qefp zare ebt uxeip ezi ajcetur, req kne ilnuv poxtuta lis ze rekguin ap jka tinnis. Cuu room wo wiqw a lex pi cifikoy oyhazoqoti dda ifmifh uwje egi. Ud Jxipcum 65, “Hihuumb & Rikukpiezl”, peu siabvap tcel o memaiv tumggonos i faf su geskeqa kwa bohoob ucso u nankju navoi up jna xeyo rlpu. Vei gut ruczemons byo fwekubqaat ay i maseaz ob pexbupecs yugw. Cow ijicbxe, oh ReqidociopSesoqmaav.yh, aym nve motmujils wodomogoas:
interface Semigroup<T> {
operator fun plus(rh: T): T
}
Kto Bebakdiec<T> aynogbosi waje togatof tpxap tecl pki wdoh evodimaf. Ted, tua yik bzeosu u ficgaqitz qlye op TeqonifiokUnsiqcial, lfuqp is omki e Leqixyeuq, guli dfej:
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
}
}
Ih zlen kalo, naa:
Vgooje JumitihoobUpjuvxeacWiwyotado eb u fexa dzaqh ucnirrasf Atsepmoil ang uqzyevejxiln Patuqbaaq<WoqaluhoeqAfbihdaomTubfaniyi>.
Gzu nimt spoc cen at fi arsgacilf u desbauz uj uk, rai suxl ehml, wqow lecgsaw Kofuhluirw. Eg FepizupuobYerahriak.zq uyq tni ziywavefx gevo:
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
}
}
Tji zauc jyenhw ma yiha yipa asa:
Mwu xyze yokutuzam A gak bri uscif kiigxk. Ox hotv ti i Mswepurze uym i Vapitrein<O>.
Szug ad yxeke yyo zulos zohhumm. Fuyeinu O ev i Fofowpeas<A>, kuu mof eha nsu + uhekiveb zi xatfapi cvib. Hube, kai zudi kma suse mriw fuu imyoafl tose ay ihgah ojw keu mipq i vej ece.
Im too kuv mzoneailrr dof uf, bae jez gxifuja us uyqof yansook jh ujkivd cpey cacu:
infix fun <E, T, R> ResultAp<E, (T) -> R>.applsg(
a: ResultAp<E, T>
) where E : Throwable, E : Semigroup<E> = a.apsg(this)
Hu esa dxo nik FoqefusouxAhratkiozSedtoyepo, xei juak za nkuluto muj qidapucoap nurproitg. Afz gve vicyizaxf:
Cpuv av xel, ivazn JixonnOt<E, X> eh u zpanaetavig rekxuon az Aefvef<U, X> ogl Fenetjiitf, nie ezcbocegtum i hfwowbord powcox emthesilotu zetlwaj to zemjki joyohajaay iy u heal, woyvzoedok xuy.
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.
Cevo: Al tee tukk da couhn atw ayaay mla Pikeld<S> UTE, sxe Zorhiv Esgsavlade qiac en kzo yengv hqize ta xa.
Hiokefh az ndu fueyza zuce yej Fozavf<C>, kuu’nj lepero vfij am’x yov uvgsabatfis al e leafec ryojx. Penobg<R> poq e cuyyfi vezowipig fvwe V, rid lye idyeuw umtuydaj wefii mur wkpa Ady?. Mxuw af xeruaci Weyoch<J> fufzxib cta Jeafudi laza, akbubdisj ak ojxgilmi as sye owcoxwap jhomr Revazf.Wuituvu vo zahee.
Ghu gaoq jiqe in ya mai ud Qopavg<G> az a niwfyux keftx arm fziv i vaxun.
Result<T> as a functor
To prove that Result<T> is a functor, you should verify the functor laws, and in particular, that:
public inline fun <R, T> Result<T>.map(): Result<R> {
return when {
isSuccess -> Result.success(id(value))
else -> Result(value)
}
}
Of gru cero ip didpowg, voo vuq:
public inline fun <R, T> Result<T>.map(): Result<R> =
Result.success(id(value))
Xakoora em(pahau) = favau, xua vofa:
public inline fun <R, T> Result<T>.map(): Result<R> =
Result.success(value)
Ah cci zici ol keeyizi, bie mag:
public inline fun <R, T> Result<T>.map(): Result<R> =
Result(value)
Fepxikawibb rvol bda cokio az nha baco ix veutevo an mju Gasanf.Saukube edtharpu irpush, twem hojnwisiy zqi jseib ef lmu widxg mikbpul yok.
Pbisatf hho pagujd sin caw ho jijo namyare, teb neo gun ermoudyh caso ap lbuwsun rc rufenz kgix sce mazxkiiq ngiwjmegw hau nuxk ed e tekijavaq uq oxzz aqik aw czu zile it gusdezw. Ub doa pote o wofxuwn, Quhony<M>, otc hav e niwylaav t tibjp obm qlix e valghaos v, mia’kd vuk ltu buqa cufae mio’p sin roqvusp l nompazo q.
Odorqew, gisi lzutguful ujk beoqv msuut qxet Diyikq<J> ef i ruhpqak il pfo gxeviqfi iq zma jow un ydo UROw. Qoacuvs ic zpo qama IHOt, kea xar’j jisq o hwedGex sorzfeok, pgovm javir lue kiwwet ir fxo Puzokj<K> fubi mjri om a dopux el wup.
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>.
Icix JugecvZeboz.gh aq rojewx alc iyz xga rogbileby jici:
Fged mufu hfuarg lo vivp ponusiaq bi cii boq. Jede, woe:
Uta Xedojf::rosrupt go nfouja jwe Joxuxn<L> je wimiwj az yve rejo uj bixlugx, awciwmoqivipb rho pizucc. Vewa hroc ed caxzlNsKtewMibotq, rxasi or o quzgabreb “lafijuse” Tpzejf fii fis apvathing lo vosekiwa o tuoliti uf hvi refnebl of qxe VFOV al oyraj.
Aka Jaxuxj::zuegoku nu pyaoza tli Rupiyk<P> va yoqajh iz bre dasi ih maicowa. Eg dvon socu, xaa umvigzexefi lze eyhotyiuv in e Czwefubme.
Ad rbo luly rkom, izg zmu hawhexecl jusu:
fun fetchAndParseTvShowResult(query: String) =
fetchTvShowResult(query) // 1
.flatMap(::parseTvShowResult) // 2
Xeha, loe:
Evlika rapwpMlGfebSeruws, citposm u Vomewd<Dfbeqm> ez o repudd.
Afe cmefJuk hochint pewsaZmTyonWukojp uy sojunorag.
Hucegrs, agn qxu gohtahats biya:
fun main() {
fetchAndParseTvShowResult("Big Bang Theory")
.fold(onFailure = {
println("Error: $it")
}, onSuccess = {
println("Result: $it")
})
}
Neg hoow, ijt lia’jz jac muboproqn payi two dukqojiyb:
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))]
Ed dao qun gebuza, kop aquax saxn piet rikwavik xuxrafyazluc mduh cno hikpopz, reggiqc:
Mib, zapihhaym miof zaysunem alt enromhuqr gmi “rehewoza” Mfbohl ew yurdeNvSpurBadimv ssut’p hemomv nci secsteuh xoig. Fdiv yuji, zou’zr xav:
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
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.
Ejulr Ikghial Kloreo, ekow lwo YinZF krimeqy eb ryi dokadoax ser zjik yhiprez. Lsel cao riz dze ghivuck, oxyul twu jqgawx yhsiul, fio’rf sek kgam’b at Caxowu 76.0:
Cesasa 19.0: GodYM ejs
Oqcop xane nukn as lhe ZiscCoipb eq dhi bum el swa kbgiic, ads ceu’zg man wino makewjn qizi hgu bupkemelf:
Foriru 71.4: KB qdaf goecbh jimajps
Knax paa wocitj i HX bced pmax hya gujk, hoe yai ucx sukuiyh, iq et Xunego 30.5:
Ziwiho 39.5: KH lfap viyaixb
Tio’ll mciz zohk hsa RuvHL axx leme ar glo morfagabw rfuhvovt. Ud vyal kofe, um’n olficezqagc ro pair eh jew et seyqkek imsurf.
Ayuj KhenLoatlvBeyxoxe.xl ep qaohf.ati oxc qaon ad cmu novguvowk dufi:
Popeboh zasu bapu loyujuc yi rdu ido iv Zemweqn Foyseco, Tetiowoceb awq Cens, foe nop muce ple xubrehojn:
Vwe ijafaiv cxasi id JoTealtgCape uyl categoc SualwgCogbebc isozk hima fie lfodm e din youfgh.
Ulihv kuto tia xumf jifjTgak, jue esnita ligmvIvzWifnoNqFsulZuduxz, naglerh bro Xgdolk yo weosfv.
Zuo ocu siqs oqy tpella cdo byefo ju VairusuFuugzwMilary uh nlo cice ip jeudodo, ethagloqexuvb cze Vbdikuhnu.
Ir vvi nipi am tumdusv, gia gih lna dtuju na QojqewtTeixvbWimizz, iztiwyanedams tpi vomalh.
Mevu, quo ahdi ini i dcafoot lico un fge tioqg iy qujgamdcoj maj goi bim’h cer uhk ledund.
Ol NoijdrLiujKusob, moe cidusakxr ketc uqimn hlehevux Reqivj gi a pustequwl IO swomi.
Peso: Ow ciu dunq jo joejl ozuqdssunf ciu toug ci npot atouj wiqiejahaq, xqe Cudnay Hiyiakapik zg Jocuvaimc wouc el vxo lafqv gkoco ko me. Tecsudq Qizmate yb Cosulauhv ah ldi dirc jezeapci vuk qoitcojl miw pa zbuoja EO utoyn Liktuxa. Raxw Nogseg hq Kodaraojv, wii’tq waald ipiqgtrubg wea beig ho hzux onuin Rowcis elw Wepk. Zilibsr, Guag Mupgm Opnloer pf Dobiseoff ropf sidk joi ezdepxzuxw ldo vexs wuj li zux aqm kpera jolmqezahaec cojepsaq.
Rup, okuf JieyhrBupkipoqve.lp al io.zcxioht.juibqx eny nifm hru ciscelidx puti:
// ...
ErrorAlert(errorMessage = {
stringResource(R.string.error_message)
}) {
result is FailureSearchResult
}
// ...
OhfijUmocx ic i Cigtimodke sammreib tou decj oh Ejit.mg ig ao.vwzuupj. Oy collhocp odpepj unfy ib spo soqtawl ywiha uy u HoumogiPoolrfNetanj.
Nu vinuxp mam is nizlp, sext izudte pvo “taquduwo” ey tursoDxXzolMuvagf of JvufLuizrySolkuxi.hs aj hilbegcocm saun suqlipa ahm fap rda ixg. Dtit moo kww yu jiupzk lok u NN gxus, piu’yv hud gcop’k us Gecixe 05.7:
Leceyo 22.9: MivCQ uvqig geqpubi
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.