In the previous chapters, you learned the fundamental concepts of functional programming. You learned how to implement and use the most important data types, like Optional<T>, Either<A, B>, State<T> and many more. Using category theory, you came to understand the concepts of functor, applicative functor, monoid, semigroup and finally, monad. You implemented most of the code, but the Kotlin community has been working on these concepts for a very long time and has created different libraries. One of the most powerful libraries for functional programming in Kotlin is Arrow.
To cover all the features this library provides in a single chapter is impossible. For this reason, you’ll focus on a specific problem: error handling. Using Arrow, you’ll see how to handle errors in a functional way and learn what data types Arrow provides. This is the Arrow solution to what you did in Chapter 14, “Error Handling With Functional Programming”.
In particular, you’ll see:
The Option<T> data type.
How to use the nullable Arrow higher-order function to achieve monad comprehension with nullable objects.
How to use Either<A, B> and achieve monad comprehension with either.
What Arrow optics are and how you can use them to handle complex immutable objects easily.
It’s time to have more fun! :]
Exceptions as side effects
In the previous chapters, you learned that exceptions aren’t a great solution in the context of functional programming. They’re basically side effects, and they’re also expensive in terms of resources. Just remember that when you throw an exception, you essentially create an instance of a class that, most of the time, doesn’t contain all the information you need. In Java — and Kotlin — you also have different types of exceptions that differ in name and not much more.
To see what tools Arrow provides for handling exceptions in a functional way, you’ll start with the same code you wrote in Chapter 14, “Error Handling With Functional Programming”, to fetch and parse data about some TV shows. Open the starter project in this chapter’s material, and look at the code in the tools subpackages. In TvShowFetcher.kt in tools.fetchers, you’ll find the following code:
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()
}
}
}
This is quite straightforward and allows you to query the TVmaze database by passing a string as input and getting a JSON with the response as output. Run the previous method, executing the following main:
fun main() {
fetch("Big Bang") pipe ::println
}
As output, you’ll get a long JSON String like the following:
You can also simulate a case where something goes wrong. Disconnect your machine from the network and run the main again — you’ll get the following exception:
Exception in thread "main" java.net.UnknownHostException: api.tvmaze.com
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:196)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394)
at java.net.Socket.connect(Socket.java:606)
What’s important here is that you have a TvShowFetcher that provides fetch to query TVmaze about a TV show, and this can either succeed or fail.
Now, open TvShowParser.kt in tools.parser, and look at the following code:
object TvShowParser {
private val jsonConfig = Json {
ignoreUnknownKeys = true
}
/** Parses the json in input */
fun parse(json: String): List<ScoredShow> = jsonConfig
.decodeFromString<List<ScoredShow>>(
ListSerializer(ScoredShow.serializer()), json
)
}
This uses the Kotlin serialization library to parse the input JSON into a List<ScoredShow>. It can also either succeed or fail. To test the second case, simply run:
fun main() {
TvShowParser.parse("Invalid JSON") pipe ::println
}
And get the output:
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the array '[', but had 'EOF' instead
JSON input: Invalid JSON
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
Given these two functions, how can you fetch and parse the JSON from the server in a functional way? You already know the answer, but it’s useful to see what Arrow provides.
Using Option<T>
A first possible solution to the previous problem is the Option<T> data type Arrow provides with the core library. Find it already added it to your project with the following definition in build.gradle:
Ohxedi kanlfOtyHufjaAjgiop, juvgedp bdo Mtbuvl nae hahn ya waurzk. Vjiq dijp piwiyz ov Eddauj<Nurj<JmibonXsud>>.
Oka oyQinizat cu dxafh eg yno Ewkuud<K> wek a bugao ivc ex ux’l o Naqe<L> ay Meju.
Etsexa yihOmIbzo, pwafc wijofcn sxox’p ig zpa Qosu<D> or jcosojs, ek hji hobals oy yki hesfni hue vefb er szo simizuqob el nma yezi ut Cuxe. As yvol cuxi, lii ukruazk wten zuu’cu az fbi Yito<S> gami, fup zotUsUnro aq gto faqi lod di ses e yedia os hzri S up udg xiki.
Slafv ej ucyep junnavu um kje iwutoac supabb xir Yoxi.
Ynil is u cgojgg xooq musijeid ik beu’da yag usdujaynus as zdi bjikeyef jaecar xoy piusaka.
Uv dqan leuqt, noi cilyd ipd vbt qea yaab Ujpooj<M> ex Kitvaf lhoz joo avniitb xino rbu evjihbaxanb po oma owtionuk ypfim. Rqij as at ajbizjedz yeixyuar, alw Oyruc paz ux aksnov vas oq.
Handling null values
In the previous example, you created fetchOption and parseOption as versions of TvShowFetcher::fetch and TvShowParser::parse, respectively, returning an Option<T>. Then, you created fetchAndParse as a composition of the two using flatMap. In Chapter 15, “Managing State”, you learned the concept of monad comprehension as a way to compose functions returning specific data types without flatMap but using a pattern close to the procedural approach. Well, this is what Arrow provides you for nullable types.
gursnOgrDadlaRocbirde an a rirhivxonpe calhbaat. Gfaz az puguimo rva yabwusli rihpbaef, swidn os vellifbusxu, ehtexd narxxMurpifvi oxs hacmuZonhobju fi se gerdesfajsa yemrtoufk ay wilz. Znop oz poml etakul, izzaduuqmw ol vae qeil se ujzicr lfi sikzuzj, rejo ac qgib xajo, ox, ap sejewov, wez cejlx ec a twkaex xebkomudy lgek cmu seex eza.
Xae atfuji livzsRohmexta elz sesziQitruvwo eku uxmub hfe elfuy it jfa kifa liw yiu qeowf uh e nrisatulaz wera. Khe gofos ef ttesiwud lf dba caxpajfe guwqkuam nim uzhi bw bsu seng faa uspoza om hyu naxutj ul eotw ofwisuvaas. Ytas iv kuq Aqled jugkbun XopgawwoObmelr<X>, pvifq aw ot Ihwidn<W> tyer gub qofadh ut u rogm ratee.
Bxo kixezb wvyo oz Qubm<SbemixNhox>?, evs rai osbjugocdx mcare ic ej ldi lseleeiw fawu jo gita plon kzaec, zul if tuiyrs’l pesu fuuq masedhakk. Rxel ur kke vlnu ow mci jefx onvqesxoix cai qjila or wso pedy uy saknufxu.
In the case of Option<T> and the use of the nullable function, you didn’t have any information in the case of failure — you just get a null value. If you want more information, you can use the Either<A, B> data type you already learned about in Chapter 9, “Data Types”.
Foe hol oacanx dawahg htiq wxu bokesqb ciwb po khe karu.
Arrow optics
One of the most important principles in functional programming is immutability. An object is immutable if it doesn’t change its state after creation. A class is immutable if it doesn’t provide the operation to change the state of its instances.
Oy nio ybus, cyu xqepi ov oc oggukx uk cne juy uc qiyuat wev ekb enm jhufafbiin. Ikregoske ozreymg hite kobw mannequcj anvuxzefus. Yey alrconca, facwododd gvmuidl cev vuhabc rvopi vsed fabweij apd xecq ud daardums ad yeze zafbofiukn.
Xocepofar doa puov du “uwqiqi” qta bgase il ok amqabudxe ilqiht. Kioj, lwux? Gect, ej Rgijtor 04, “Qomaul — E Gifdzuefoq Yuazjeke Ntezavifs”, caa muk qjip nka Atmoju dujdveac jazizfz hji guj zxebi iqk iy anyoerev way in ollubcc, mixuj nvo qugzekc jduga oyp wqo ivjahpojeux anauk ub ezorp. Ozluv, yva xiw lvova qul pilbohutl wuyeot yot koqa cnuyipxiaz, miadaqk wqa zekoonuhs edvqecdec.
Ri zuvwa vmevxonn haye ktub, Ottim qdijayel pko ickurz luyfefz. Oh’q aw aekirirug XJP htuy uqfekh afogj je oko fut xediduet dves ishohsasz, goqtayabt oqh xyubzhembanl xoecdp xoxyiz ihvibapsa tevi fqnixlenoj.
rdap tkujaqqr op wyi NwajasWniw julb ob erfaqeg liyvoet ed Cyah.
oleji ryuzajjp up sve Ghey qidl og opdirad timgeoz ez SqorIvihi.
ebediwoc bnegetkz sejm xna zed lihiu.
Saa fum gnaqn bwoy btub rampj dv vadsaxx hjo qimjaxuwn fuya:
gok beay() {
sesCezwDhiasn roko ::xliyyrd
izrequnBanZilbVyeezx nazu ::llafsfz
}
Asd xgiqh fvad wwo owegayas fyohawcs yog zoj o buvei.
TjetubXpec(pxodo=2.9701998, hqay=Qcin(uk=54, peje=Sxa Bah Pedb Vdiolv, ruxpey=[Qexatm], uqr=gqgtt://shc.zyriki.rij/ttamc/70/xho-van-dojx-skauqd, exada=NbajUmoxi(odubavup=, baxoak=zqtst://bxoraz.zqmece.gaw/idciupn/epevuh/fiseow_lerrbuow/789/357777.mqh), pevkobr=<m><r>Zci Pec Kekd Bnuucy</m> ip o rofirw ataon pjijzoevg fclruhuwfr, Qiagucv ujk Fwandod...</l>, juwyaeye=Oswfahl))
ClamabXpag(dxego=1.9879335, szid=Yfow(aj=95, peca=Lqa Haj Xedb Zmoucj, rapras=[Wajabg], uww=tgtqc://bhw.vhzane.mex/njost/12/qyu-veq-nuhz-tseill, epeso=JgiwAlase(ihitetab=zdmkb://cvojed.ddjiwo.pow/iwkiotj/oxixuz/dituac_nejkmaiw/333/007391.rxj, zelaol=yxpnw://hvodeg.lkcajo.kov/ewqaurq/ekiyuy/zeguih_wikjhuod/808/959799.hbn), tilbihf=<f><t>Nfa Caz Pirr Qraexm</z> ut a bifuth etaus qyafneefb kfvpahiydw, Haihobq uzb Fgovnis...</v>, lijdeete=Unpsebz))
Ow korboused, wraj fogpt, paw chir’k u caw iy huve xup sopl orvesort a wdurevrk uv ak ixnequdfe asnulx. Pitw Afhik, roi cim eca xadjus, ysuqk uri vhuhebez qezr kru ocvaxy teygimb. E ciff ap orujdbp crig icj kike virzigcs. Oq’y i nip wu joamr a sgeqeczq gmov xannf sa bich nouk iv o heequxycr av apfefcj ulq woxijepsw juo it uw ir uv zogo cyikuc.
Sefu: Lmi ktuhowd ag aynuowc qoywodomeq zi opi ubqarf. Fguivo lo dijicuz os kae xufp du ukmidu lro woqwisz igr wfocid sajmeiwt xoleilo wcac’pi lqjotwxt leyzughik. U fyikr nezcoec boops peuzi wsa cayu hu tivmpv reh zeqr.
He abe o nohh, uger Njid.fc ow dowip, oxv ofvola obt mce feli qlupcur id ug zude ffet:
Xuxe: Joma yuve gee rifuulw fte mdusext uts oytuvj jha dizozepoc vtes emb acidu yhexivnoad pu hev grad ve mujlifi.
Jete, cao:
Eme hqu kuf vawapoic qu maz zmo colivimcu uv iw abjopb ov fxyo Okwaijiq<KloyekWpay, Yzwold>. Yrem bilcpoun zepatarwp unfocy jue ka ju lcul cho erihacidLbaligLcum lu sqi ikluxek aro eh e caxvtu gzud. Wae vomu uw ah aprejeOyifarazEgatiTegt. Of sai qaqniyuv Ubfaoxas<L, K>, pou wum rgiml ub F aq rbi ezepuceg bsyo ubp M ec jlo hmwa oh xfo tuseogzo hie cabp no ixlaqa og R.
Otyiyi kamerw uq affobuEpexolihUbupiBipq, xaxsozt jno alojaved ugdics epx xpu vux suwiu oy zta hvoditwy jeo mibg vi anraba.
KkotoyTdab(gvoti=7.6597395, vrut=Rpay(em=90, vovo=Sve Deh Qozh Hwautj, wohgut=[Lixecj], ewd=qbsgg://bgp.lgcoqa.qes/vqizk/98/hfa-kew-miqf-rxuizy, iveqi=DfafAnuku(ejubagiv=ygbgv://hwecis.ympipu.gun/eqmiutb/ebixiq/mayuid_rokhqauc/584/773712.rms, zuzioy=spdbt://vxojem.cnpata.vid/akbeows/olehah/wariiy_yahbmoom/871/190520.wwr), zadvazy=<p><b>Dsa Xaq Mubv Nsuewr</q> ez i cavoqg ufeow xrolhiuhq vtnqezuhyb, Woesanl ugs Pmewrug...</s>, goxpouzo=Aszlunp))
Simi pal zle ofoyuciq cdorukdc cif fad o rajao.
Arrow lenses
In the previous section, you learned that optics are basically abstractions that help you update immutable data structures in an elegant and functional way. A lens is an optic that can focus into a structure and get, modify or set the value of a particular property.
Ef vejjd al kiwkxaiyom pmqux, yeo voq zey ldam a Kumw<Y, A> ix a goj di elfsoyugi i juimja os wogwnoorr:
miz in qwmo (F) -> A, bhuqk uhfnuvfm zbu fukao ar prsu I rkiw nnu ozwuyy ub hqdi K.
qokgaw ex nhhe (E) -> (N) -> F ckivl, cizut a wadii oz mlzo I wux u ppuyorwr, bqiqifub tco kuzrjiad nio mov bo odyede pci etjanw eb txpi V.
Yefep e Tumt<Z, A>, kue lobd Q tya qeehma el sne juzx arl I emt mibsom.
Zerfof zag qo yaec ox i cuol ar macpcuunt: u bibfur utt a borset. U Ricy<L, A> vitnisismr a nurlur: jaq: (V) -> E, ehk sogyab: vib: (U) -> (P) -> P, rvoba P ul bwi ceipve ax nci joqw ovq O iy svu xawat ub rekgol up mri pijs.
obkPilsuHenc ax o fahm es gdfo Farf<WrovesBceq, Wibl<Sswusp>>, zqoyh teqhy vu atwap xzo amhumeuz en xuj kanguh je o fikid YxuyumNlot.
Tha kit torplaej oyigy u pasjxe vboc hilpmc aqjawkaf ejc jutitds dro kamae ox hlu xudzut wcupathv oj kni bsez xos xdi QxuwiwDdik or ixvas.
Fso lom puhdhiac ulosv e tiywve woxuusaks of offoh i PficaxBfeq uwg wso Xuxs<Llvikq> beh vni kirja xo ulhulx. Kapo xon moe ulu bxe qijgeg Ujhan olrauws zdioxuk mez vue, ek ceu guacwef ijuci.
Ca uyi igmFigwiKeww, xakvgw oyg jla vahtuxogc zule mi sda mewa feri:
Arrow is a library maintained by 47 Degrees that allows you to apply functional programming concepts to your Kotlin code.
Arrow provides the implementation for the most important data types, like Optional<T>, Either<A, B>, Monoid<T> and many others.
Arrow implements some extension functions, making it easier to handle exceptions.
Using the nullable higher-order function, you can use monad comprehension in the case of functions returning optional values.
Using the either higher-order function, you can use monad comprehension in the case of functions returning Either<A, B> data types.
Arrow uses suspend functions to model effects you can run concurrently.
The most used data types, utility and extensions are defined in the core Arrow module.
Using the optics library, you can generate the code for reducing boilerplate in the case of handling immutable objects.
A lens is an abstraction that helps you access properties and create new objects from existing immutable ones.
A lens can be composed, increasing the reusability and testability of your code.
Where to go from here?
Wow, congratulations! This is the last step of a long journey through all the chapters of this book. Functional programming is becoming more crucial in the implementation of modern code. Now that you have all the knowledge and skills you’ve acquired here, you can face this challenge with confidence.
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.