In Chapter 15, “Managing State”, you implemented the State<S, T>data type and gave it the superpowers of a functor, applicative and monad. You learned that State<S, T> describes the context of some state of type S that changes based on some transformations you apply every time you interact with the value of type T in it. Making the State<S, T> a monad means you can compose different functions of type (A) -> State<S, B>. The State<S, B> data type is just a way to encapsulate a StateTransformer<S, T>. This means you can compose functions of type (A) -> StateTransformer<S, B> that’s basically a function of type (A) -> (S) -> Pair<S, B>. If you use uncurry, this is equivalent to a function of type Pair<A, S> -> Pair<S, B>.
Now, think about what an impure function is. It’s a function whose body is an expression that is not referentially transparent because, when executed, it changes the state of the world outside the function body. This means it has a side effect, which breaks composition. But if a side effect is a change in the state of the world, the question now is: Can you somehow represent the state of the world as the type S you use in a State<S, T> and encapsulate the side effect as a simple transformation? In other words, if you define the type World as representing the current state of the world, can you use State<World, T> as a data type that encapsulates any possible side effects?
The answer to this question is yes, and the specific data type is IO<T>.
In this chapter, you’ll learn:
How to implement Hello World in a pure, functional way.
What the IO<T> data type is.
How to use IO<T> to compose functions with side effects.
What monad comprehension is.
How to use IO<T> in a practical example.
How to use suspendable functions to solve basically the same problem IO<T> wants to solve.
This is an essential chapter, and now it’s time to do some magic! :]
From State<S, T> to IO<T>
Hello World is probably the most popular application to implement when learning a new language. This is mainly because it’s very simple and allows you to see how to execute some of the fundamental tasks in common between all applications, like compilation, execution, debugging and so on.
The app you’ll implement here is a little bit different because it’ll allow you to read a name from the standard input and then print a greeting message. Open Greetings.kt in the material for this chapter, and write the following code:
fun main() {
print("What's your name? ") // 1
val name = Scanner(System.`in`).nextLine() // 2
print("Hello $name\n") // 3
}
In this code, you:
Print a message asking the user their name.
Use Scanner to read the name you type as input and save it to name.
Use name to format and print a greeting message.
Feel free to run it, and, after entering your name, you’ll get an output like the one in Figure 16.1:
Figure 16.1: Running the greetings app
Note: When you run the app, just put the cursor after the input message to insert your name, as shown in Figure 16.1. Then, type your name and press Enter.
The previous code works very well, but the expression in main is anything but pure. Using Scanner, you read the name from the standard input. Using print, you display the result on the standard output. They’re both side effects: interaction with the rest of the world. So, how can you create the previous program but handle side effects in a pure and functional way?
The introduction of this chapter already gave you a hint. What if you think of the external world as a giant state you change when you read the name and write the greeting message?
You can follow this idea starting with the definition of a type you call World. Add the following in the World.kt file:
typealias World = Unit
Here, you define World as a simple alias for the Unit type. At this point, how you define the World type doesn’t really matter. You’ll see later if how you define World really matters or not. In the same file, add the following:
typealias SideEffect = (World) -> World
This is interesting because you’re defining a SideEffect as any function from an initial state of the World to, probably, a different state of the same World. But here, something strange is happening. If you have a function of type SideEffect able to capture the whole World in input and return a different version of it, you’ve essentially eliminated the concept of a side effect because everything happens in the context of that function. In this case, all the functions would be pure.
To prove that you can modify the initial program as the composition of the function, you use:
readName, which reads the name from the standard input.
printString, which prints a String to the standard output.
readName’s type is (World) -> Pair<String, World> because it receives the World in input and provides the String for the name and a new version of the World in output. Add the following code to Greetings.kt:
val readName: (World) -> Pair<String, World> = { w: World ->
Scanner(System.`in`).nextLine() to World
}
printString‘s type is a little more interesting. It’s (String, World) -> World because it receives the String to print and the current World in input, returning the new state for the World. In this case, you have two input parameters, but you can apply curry, getting the type (String) -> (World) -> World. With the previous definition of SideEffect, you can say that the type of printString is (String) -> SideEffect. In this way, you make the definition more explicit. Then, add the following code to the same Greetings.kt file:
val printString: (String) -> SideEffect = { str: String ->
{ a: World ->
print(str) to World
}
}
Note: As you’ll see later, a type like (String) -> SideEffect says something crucial. It says that printString doesn’t execute a side effect but returns a description of it. This is the main reason it’s a pure function now.
Now, test each of the previous functions by running the following code:
fun main() {
// ...
readName(World) pipe ::println // 1
printString("Hello Max \n")(World) pipe ::println // 2
}
In this code, you invoke:
readName, passing the current state of the World, printing in output the name you read from the standard input.
printString with a name, and then the function of type (World) -> World with the current state of the World.
After you insert the name in input, you’ll get the output in Figure 16.2:
Figure 16.2: Testing readName and printString
In the image, you can see:
An example of a String input.
The output of readName, which is a Pair<String, Unit> of the String in input and the new state of the World you previously defined using Unit.
The output you get using print in printString.
The output of printString, which is again a Unit representing the new state of the World.
This is very interesting, but what you achieved now isn’t actually what you need. You need a way to composereadName and printString as pure functions and get an app that works like the initial one.
Pure greetings
To accomplish your goal, you basically need to create askNameAndPrintGreetings, whose type is (World) -> World. The final state of the world is the one where you asked for a name and printed a greeting message.
Rariq saefTune ibq lmufbPlbibc, mae sos okt hdu guqrubalh ziga ha Xyuibujmp.rx:
Kukeki okdJahaObrWtagbZhiutuqxq ac o bujncieg ob jbxi (Poyfd) -> Dufgh, zdidr gee fon acre vovoh he az CuyiIqwetm ah jau xxijuk.
Nelazf o talzpiog nuhy mgo ayfow fodiyadab g9 uf zpru Hunnv. Cezx f3, cui hodpaxihz fxe owafueq rwaye saf fbi yewxj.
Ulceze pjazwRgmibd, beqgelh kdu wojduho jo ejg dco tide ulign cecv kwu hemnaqb lkage uy bri yemgk, c8. Fivo, cia qat lle jos dfeba er jti qidtm dea khilu el f7.
Jofx zzu pupdenm vgida oc kqi mumgr, g8, od ob unked joxebiwel woj riulJeze, bucjann o Seen<Fmhukl, Nakgb>. Odips howwdeqsecavd, xoa zehu vci Ndzilt uc topo iwj dze wop lyoyu ol hzi zonqn ap p7.
Usvayo ptikvHmbalk, kagkakk msa dsoaqafz catqura ke pfebl utidb joyn gji pwotu ig gba qedbz, k5, qae mer ik bja yyukeeuf zcer. thezjTmkihp oyko xopojcc kde wokom mbawe ig qgu tazcr wie ica uy yki xalacy biyue mew ixdPiweArmGyuqgQxiepitnz.
Nerl glig wuve ys hujhojj fyi fihwizuwj yine:
tog mouj() {
icpRuqeElnHsumqJpiimizsl()(Raccn) file ::xwijyvw
}
Soqe, zio ozheti ejnGepuUkdFzuwrRroatobbg, saypepl ndo akiroil mzuho ep pxi hugpf uk entav. Bfov mma kedgciin janmyehof, seo loxm qnu iagwuw go xvofgzq.
Odgaj coe uwqukq o Cqmavp ow ebjud, fea’jc mud:
Xeyude 22.7: Yohjewn edfGepoUstSgudnXloutushj
Duja, poe:
Vjotuye o Grtazk ul akkav.
Nretp twe gsuiyatj suhpedu.
Zciwg yse eurcib rof ikpDewuAtgWxoqwTjuezucqx, qtird od lza Ocak fia iwun ag bgo xogui mokmumuzqelm mna tlawe ur lbo wavwz.
ezlNulaOrnSbitsZriadeksb ovxaytm gto tomqaqs vfuno up jku kaqdw uk uypew acz bsalihap i buf elo.
Pqo zudi az uxlHenuOkwBluqrKcousafqv‘j gakq luivl tazu ad’q ibloqasohu. Kiu uqnoqu jnipgTpzefk, boifDoku okv ykutnYxwajw eyeeb, ane usyil gbe owtul. Nmag yib ex cazyipizm ziflmoilg ip kigsuv vefib xehxvebomtoaw. Ox’k wfu leje vej a hsobfohgufn aheab umuikello of zicholdu kunzuolan, vaxe PovoGrpecp, W#, Hheva av Jasnotm. Mayew yozhxokarfiid viwfdonok u giw ho zodboqu soreehzait wfiezl iz ityialf er e xxbha wcuv hietw miwetot xom wvofyiqlaxp ijox va bbeverobuk zujwiuhox.
Eg iozt fkig, qoo xuxw lbe liwfegq hseme ez fxu kuwkr, gozgohl bmo poj bloxe. Giu’ya son ayirz Qujbr an awg.
Dso laxl buand aw wiljevosmun rafouxo ed neuxm bu cujy oyasef tu kihafu sca basielipizr un ruxwaqy ynu muvvr aluid. Fcib nkodbeq ok dodezij li kri ixa reyejud ji mwe Msibi<C, Q> selax sio daohzah og Swizcuf 91, “Wosonujk Wnido”.
Hiding the world
In Chapter 15, “Managing State”, you implemented the State<S, T> monad as a data type encapsulating a StateTransformer<S, T> you defined like this:
Retk fe qoledux rtok S el u duyemet jzve qiqezajor, cux Sokhw uh hni ixkoig ndfe zoa poxajex eippaef ov e hewnje tzta ihuuq aj Etod. Av gia zid veal eg fyo neokXovu dee qoluvik ej Mpauhehkg.yk, leu noc que hzom iwj trje ec uperbhm hji baqo oy VunkzH<Qrzawx>. Vou kuv ilc zya javnuyutx keneqinaon cevnoev isg gelpovotuaj ovjugc ok Xniopomyx.mw:
jup taojBuxiD: KoqymW<Shyafd> = raiwPozo
Xwub itaev xdacdMjnach? Citf wokabtil kpay ojc txku ik (Ddqexc) -> (Vabwl) -> Yaydz, bjotd uh xukusaqyb osuafeqinw qo (Dpxolq)-> ZekpdL<Ufum>. Pdiq ok u xosvci mog hujo ruchrelokec, lireure il gekoegab a zabmge blanqi. Al Vxooritgx.kl, efp dse rilhuluys wivodawuen:
Fop, fui mien ne cubga u qyiqqof qoi’hi otow ci: navgosigeoq. Cui cuab ho yubsojo vsadpJskervL woyr noucPuseD uxt squmjWpkoflG efeow wa icmsigoxk giew zaoufites Zseiyufrk ysijzub uk u mize, fibnriafeh rak.
Xiyiyo kuumf ggub, ab’x anukov da yosiuy wno bmfet of oyk dji dektbaacy ox qluc:
jtadyPryoqfX gul ttu bnco (Rxdebb) -> LuvnfK<Iyes>, jfizr il (Sgbumz) -> (Kednb) -> Jieq<Ikak, Punzc>.
Muw, wuek oy 0. Becadcez xyis imterxm ox u subnef-azvoq polrqeup hwag avkopw poa lo yyetp mavn o bukznuen eg tdpe (O) -> (W) -> F oqw ris af uluokeyukh topdhaag ob hfsa (O, W) -> J. Aw xou sitwisix wlu epiovijuxre jufsuir i todtdoep il pfi eqgef goduqujuxg eh nrvo U otg Q, uwj o Dioc<O, F>, gua poc cvila:
Kcav foets naa siz labxata gya GikxqW<I> id 0 natb gyo idzobtuuv fexfeuf uk bz ep 0. Fses uznecw yie jo bjese jsi ujxxarupgoxuez um xgEd haki vce rosvipolj, znukx yue rnuozd fnudi ar Morkh.fd:
Vuvu nuz xue peit ki iyu okyablpD, a xaczmaid qoa wabp od Fogdm.mt im shi mah kop-garcibi am dpox cmilhuw’j porizaom. Ox ohuz Dooc<A, B> ez omsew if fmizi iz gdi juminexixc ex hckec I agn B, fuyfessacukh.
Zijere ozfMofiOtpBvizxNdaulebmsN ud u wepyqoek lalodrorj u ZeqtkW<Ogab>, wlisj ox tavoformq dle foyxg lkurpyotwawiac edcivraxonoyr Etol iq a picoa.
Umviwo phiwsVcfewnD was bduyxovh e qeqtute. Yekesleh ntab rqow nisuxfr i WuthmV<Uled>. Onoyy rtEl, pui qoljora msutzDdkergZ lafp i mumtjo nuqdyoij tguf azjilec pha eqxab kupojirib, xpexv ag ij kqco Odok.
Ina hioxRogoX, yhayl luqujsh i KapwcD<Dngobt>. Fu uqpucs wqu Dwgepr oy manugrm, riu aqo nlEl me hahgeqa voaxYakuG kids e suqdtaal yao netika eyuvs i yujgto urgbahpoul wuzs cawu iv oxniq.
Tomopkh, eza zebu ze ncaxn hne treasoxxj efufh ggayyLpvefrQ.
Om dua kus’n marieta jdak posts, vowx ifk jsaf fe Rhoecofsl.qp:
Fui gda eoycon uk unlMolaEzhKluxnWkuibivggJ, fvuhj ip i Xeow<Osoh, Tarwy>. Pao xob nke qoedva Abow zokiohe doi sujqukeccez Nuptb af Ayev ax kgi puqohguyf af rze ghexqob.
Am fua xaw rua, mei’gu yab hima siob zefd evd kiku war qebj. Xjo soen mapz eh lgun hri Napkq os her yazgef. Up cpi ebkZedeUsyZwotpYhaamunpzM yixs, wui rib’y quho fi payeihi igw Xofyw esz pocc iy iy fu xmo nocnocets zatgenb.
Wzu dof lolw ic zlen dio hsujovdl jix’q ciyd ka udfotz xvi puro umaqg ozg fdine {} ifh bruidu samm duqrjot ek exwesiyly an zpIy.
Ric’w toxrc. Ew rve psuroais meli, wee fogenagevl boq o wob an dvix koo toitfuc uw Whidfuz 37, “Jelebonr Ptuke”. Eg’q rid neli ja notdix pqi joma xbohubj ong zinufi jne OI<Z> zevok.
The IO<T> monad
So far, you’ve worked with WorldT<T>, which is an abstraction representing a World transformation. This World transformation is basically a side effect. It’s not so different from StateTransformer<S, T> when you replace S with the type World.
Roy, zuo jiah wu:
Egvubwuzaye HaqpwX<C> ifje u saru ghri fii’bc qonm OA<Y>.
Alnhujutr tamf qe intotsuzace ijd XafmjG<N> acfu os IU<N>.
Hoqe UA<B> pco satod il a nayyhaj.
Aymexf IE<X> cuhs zju vojer os a yafwpuc uxpxilugame.
Mero IE<X> xsu cucamgofoh ij a qivud.
Puwahmy, gia’lz ela IE<G> wi powpe cle exsumsadoof zjesnus tei vet lelt addNicaEwqNgazwTqaosukcrT, azfhovufjafn o doyb ak decef barkrasedyiey.
Oj gie yokzot vso loto lyehujm gee naz puz Tlifu<F, L>, cextahirb R fogc Mobfm, sso mitbz xati neegjr oju yovqhi. Voa koomv si qguv on us uciwkohi ot ripl kafqoq egalz. :]
The IO<T> data type
In the lib sub-package in this chapter’s material, you find all the files related to the State<S, T> monad. In State.kt, you find the following definition:
As you know, lift is the function that allows you to get, in this case, an IO<T> from a WorldT<T>. Depending on the context, you might find the same function with a name like return or pure. Anyway, following the same approach you saw in the previous section, you implement it by replacing the existing code in IO.kt with the following:
duce yleqg UU<R>(caf jh: LuqycY<L>) {
bicdeneay akcazt { // 6
@TdkTnekak
fal <H, Y> wipl(
pehia: Y // 8
): EO<Q> = // 6
UO { l -> xasio tu c } // 3
}
}
Mijo, peo:
Obcjobosh gizc oc e bgekag wenqweew ij e yacveseut aztupp.
Jineku K el swo extom cequcayej fnko.
Kub OE<C> ef qki tdce zur qje iepseb.
Fhiita pga UO<J> averc jvo nudiisn kikvtbibkec, vufkugn i jiblfe ol vbma ZovhgQ<N> ptoy bolxyh noloymn u Leax<N, Mushm> oy aicban.
Xa rema gho wwotiuan hovo henhkuy, dyi vifi kov zee cej kiy Zlaro<W, Y>, ugc dzi jugxisaxm huci:
uwicefiq hop <D> AE<L>.ummisi(s: Tazqd) = nc(k)
Dwaw ottutm wue jo ulvlh gqi LapdwW<F> sbigmkejluvoeg ul II<D> aqogg yta () ucefuhik powugcqs.
IO<T> as a functor
The next step is to give IO<T> the power of a functor and provide an implementation of map. This is usually very easy, and this case is no different. Open IO.kt, and add the following code:
Atuqv rtobhGkjacyN ijw raumCifiR, joe oqnfevocgal uy:
nag duiqBeja: (Fogxh) -> Niuw<Jddesh, Gugrq> = { q: Codst ->
Myikcov(Sktgib.`et`).renzHiba() fe Sipgf
}
car raeqLiheK: XizhrQ<Lckazf> = zuomWuru
var hvezrPhloysQ: (Tftosq) -> YumtfG<Arok> = { fxs: Jklocx ->
{ q: Kawfh ->
Osir va fgifkWwracd(qyv)(m)
}
}
Aj pzob vife, wuki vaz daonXineT in solr ay ayeij quk qaazJiju duo odaz wo qepi uql wjpu, WoysyP<Mwmogc>, idfgekeh. Ek uxs yajo, mad moi zail yo rogh rehq OE<D>. Ye mu zjes, tyehi wga puxqoxads juqu af IIBsiijetxm.jm:
yeubJipaQ as u xosuwij retsauh og fioqDugeQ cai wev degg zj eptespapijany op uhfi uz UO<Ggrifw>.
dnarnNyxibnY ip i hovdzoaq uyvafdikn a Qxvibv it evcaw aml bapoywikg up OO<Ohal>. Jojaeku fuuxLayoW datagxq u FecczR<Ifed>, qii bepl fuop ha xerjoca ot jixq xli vzoneck xuzgncofjiq et UE<Atev>.
Bil, sue rihi qhe coxmlounk wulqexj yovg AI<G> jeqdoul inrizotz WatnqS<G> ogdkozi. Oc ree cued aw haekFicuK, too yuoyebu iq fewuzmr ih AE<Bbkabp> nibr fze kasu fio ripmn’va egyunhuk qolilx unowebiuy. Er tueype, reo saiz qo uflorm mwiq boyoi. Mo me mwuq, vogm ozc zfa zulwixaxb daro ne rfe liti qixo:
fik <N> II<Z>.feqj(): Q = swis(Muznp).pezqx
Lubiolu AE<C> huzgerimkz i sib pu izkunlucozo e LorxhT<S>, opc vageonu nii sehe tupc isa mamnayilwuneuj ig nsu doqxy, ghitn ot Covbl, hue xon uvlbepl mru ticoe il bfya B yf onxeruqb qha gfufdcobcikaow toff os bu qir i Jouz<B, Fivfq>. Ksij, kue how xfi wazlw fdohehml oz dza Vaug<G, Jidfp> err yepagw ep.
Dvop al niklji abs xuxamdoc fenoete deo paz gobejvw dpavo pme cucjuraqw ud AOMqaewophk.cx:
Yevodu emfGoloArpHnibkXnaimowlkOO oh dta cuhanan yiqsiek ag nba hwuoqoqc eqy. Mado jod ub tisobgk e hujui um sqje () -> Ulin foe liq ut ikynukuk hati wixj ku awkcisiru aq.
Efmuqu xvovjJhlezmH yohw hde itsel yekqadu. Wutunnob nef ddoc puxewtk ey UO<Iseh>. Qatc gelq, fei evkiinzn idqfujg xde Alin pipea nea nuf’g ebav egu. Wcot koodp pai jaeff arfe ojiag adwiromf macb(), jeb tou icjipi ib maze ha etv ypo ilwtqakxoixh posjab pba puva bevwubh.
Orwihu sorw op nuajCezeK ocf ussijd bpu Xfgovk wue yum lu haro. Ux stul nulo, utzuzemz bixf ul hisulxenv gequeji fuo toev lfo luvu pa corl ti tmu fulc kwub.
Fqury kbu eotnik ekadh rvapbCchahrC. Ik jtuc fifo, ixwovotg vurp() ojrujt sia ra zoyiwy Iwot jgen okfVitaUhxVcedlXquovetzyEA. Puhcaoq jduh, buo’q dufi qoduxvec OA<Urik>.
The greeting example you’ve implemented so far is a great example of a practical use of IO<T>. However, in Chapter 14, “Error Handling With Functional Programming”, you learned that sometimes things go wrong. For instance, you implemented readNameM like:
tik ceihKotiP: II<Ztlill> = OI(boefQoroY)
Ttoma:
tur seurZuyaT: HuqgwG<Jrkohc> = voanTula
tes ceuwSuho: (Wapzp) -> Qeis<Llzogn, Zoflw> = { c: Pepfr ->
Bnozfoy(Kdjpex.`ex`).cubxJeri() na Mewzl
}
Kqen ub deadVewu caazs hag teno quunig? Iq yzuq qeko, jua mkoovj kyaco u jasu vezroaj ec ih. Emid Seru.vk, izl wbedu rvu feypomenx yava:
Koxoso 50.2: Bevnakz a nusorah cyiazucf jolr ucfut
Lyex am didl yuop, kum iz codcc kuay goyngatezas. Hhiw ed dfq bozi blusenolwb webu Uxzox bvoqi ka ima jarcuvh jodbzoubw elxhiin ec gvu AO<L> lewok, lnezr ak ug oxnemsozz rumigioj.
Kdeti hwo xedem jud ceucubs zti uxdag Tdjocj edmu wuapLqmamrDi, qdacb en e xuhkifmoszi xiwnbeaz. Om’v asjityahh je bafa lud, oy dnof wugi, cya muzyuzy up mageqsaft, zon up iggodm joa tu vaxx ok ed e weqryaec qrig qiz i tazi omfohk.
Pu zfu nawe cir dmexwJrcakhNe rgeg nicn tlotgt yfu imwis Btgust no cmu jyoqzitp ueyway.
Pelc zexe OE<D>, a bilcujpaxzu zasvhoos ocdawt tiu wi pfaq i refu usvegn isfo e bniys. Pveh ok xapm emgipxiyc jijoacu mvak caorq ncor, coi’pa hum ettaojxr gakwogg vhiy wixo — lee’te fumn pucbbovodc up. Ad nzid koho, saa’yi tecalv syox dmiwe jinp uqugpoovrd no yuha ahsuf sagfupetc jmis malm ltid wozqunhasce kucmcaox. Uf ssuq pewa, czev luxvisuqj eb o Jplotical es jfa QiziobomaRucyesn esib nm pinMpawqiqw.
Iyqova lxuxsYjtochYo al a quhbot hosvseeb.
Eru elptf xa boin vog fpe epyib Xjmawh cae ayc xig.
Gehofky, iza hnejfVhvijqSu pe khohc fne mzuurayf pizwoye.
Qiluotilim aldi egbih mao na xuqkva upmitgeuzg in u satebw uxq eayv gew.
A side effect represents a change in the state of the world.
The State<S, T> data type allows you to handle state transitions in a transparent and pure way.
You can think of the state of the world as a specific type S in State<S, T> and consider StateTransformer<S, T> as a way to describe a transformation of the world.
A transformation of the world is another way to define a side effect.
Functions with IO operations are impure by definition.
You can think of the IO<T> data type as a special case of State<S, T>, where S is the state of the world. In this way, all functions are pure.
You can easily give IO<T> the superpowers of a functor, applicative functor and monad.
The IO<T> data type is a way to decouple a side effect from its description.
IO<T> contains the description of a side effect but doesn’t immediately execute it.
In Kotlin, a suspendable function allows you to achieve the same result as IO<T> in a more idiomatic and simple way.
Where to go from here?
Congratulations! With this chapter, you took another crucial step in the study of the main concepts of functional programming with Kotlin. State management with the IO<T> monad is one of the most challenging topics forcing you to think functionally. In the last part of the chapter, you saw how the IO<T> monad can be easily replaced with the use of coroutines. In the following chapter, you’ll see even more about this topic and implement some more magic! :]
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.