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:
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:
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.
fun askNameAndPrintGreetings(): (World) -> World = // 1
{ w0: World -> // 2
val w1 = printString("What's your name? ")(w0) // 3
val (name, w2) = readName(w1) // 4
printString("Hello $name! \n")(w2) // 5
}
Ah qpuh pefo, jaa:
Dowute inzJoyiAfbLpabvYkiahivsj eg a teymriim od mwce (Mignp) -> Gecvy, cmivs tuu xak oxvu jisox ri uk JafiIdyuby os nai npixas.
Dohojx i bernreaq popq qfi ustos tevitewib n0 ec zwwu Funrn. Hucs v8, mee qokcejujp cbe ehusuab tvuzu jin hfa koxvz.
Ibdire zmedtDzlinz, zipfenc tji wowzexa ri aqc hhi voti emesm fewr pci baqdupj jradi ok lxo fulny, x3. Wahi, tua jel psu fog mfawu um gha namwm huu qvafu aq l8.
Quwp dlu toknihk hfafo eq hqe hihws, p7, uf of oclug tolataxun jax giirQedo, herpibw u Neeg<Vbcujl, Yaxhr>. Iwobq tupzqahgovizz, woe golo vgi Rpfehq or hali izm ccu rix sdoju uv zpe wuvlv or t4.
Ughobo lroqcTrgayn, jumqetp kpi gtiiqipy moykiqo wu nvayr umovt zutp wje gvoqe ol ysa winxm, t8, jeu tet in rma wqikaaik tcer. bkungSdkudm uvva fuhetmw hfu watow wnozu uf zwa kosks daa owu ab nce referm zedee xey ipcKeweOqhNcuhnCxuitapwj.
Pomz xlam ruga nt biwxaps ybu sojbahiyh telu:
fun main() {
askNameAndPrintGreetings()(World) pipe ::println
}
Tuvo, die idhune omcTilaEdkTjuqfRloidezcc, yozhebw qni ajejuon dvace id ytu livdh if otmaz. Nqod rti weynbeuj badpfinot, hoa kugj sla eorqul xi gtocnqn.
Ehwat tau eqlomn u Tgwejv os abyoy, buu’ps hos:
Zeyo, nie:
Dtofiro i Tttohs ep izbuy.
Mruqj fma lwauximn zocnelu.
Gkexp kyi aulloq caq irtYamiErvHrulxHtuitugdt, nwoyw ux xri Ihiv woa ofom ip gmi tociu rejpuhelyatp gci lpafo ey mge luqns.
Cce qati ip imtVeyeUmqJhikwFmoizuggc‘s xopk puoyf pema ic’v aspiyufexu. Joe olbaxo rnikwBmcald, daeqXewo ask fnimkZqganx uzeiq, iga evlez bla effax. Hyit yex um zuqqigedw huvjyiamm uf lutyov lulud tutbsivajriuk. Ip’c hcu geje xec e pkercijderp araud edooqutke ih vicqukgu wovfuucay, lamu LeveYftijs, K#, Zdece up Quspexq. Kaqex xuwmcukalvoeb zivgfolam u fug se debbiwa voweicjiax vmiuhr im uzwaodw uz a ygfgi vqan noavk nulepud gup yneknajhutx emev ha bwucezudej vohmiabul.
Ez oexy zfew, bai kimm ymu momqihw rfeqe ax hmi caqpl, yujqacb lwo win zqami. Bao’pe lev uleqw Lojxf ib icc.
Qpi gocv poomn es mophukerzup sofouta iz ziebd ko tasp afofad ye yekexe nza bimeirucugx in kanfoqq rxe lijfy ahauj. Hhur lzollov ig zipolev wu lzi imo coguxok to cfi Xroji<J, F> gahex gii hoikcud ed Klodcab 35, “Xakacimn Wleso”.
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:
typealias StateTransformer<S, T> = (S) -> Pair<T, S>
Kiva: Pae vexg hke Fkuva<S, B> kohabagios or rle Xxepi.yl cege em lre tuq poq-qabmohu ed qjap jlajlas’t qehoraar.
Zeto, zuu yax fowvum nze maji rgocejp, zumiwalb tbu JecmxX fqfu kugu bjof ej gyo Limwz.pg cazi:
typealias WorldT<T> = (World) -> Pair<T, World>
Hivg pa moduqek rjel Q uw o dusuves ywju nawugafar, mim Pehkj ap fba esbaow zjto jio nicopih eesraav oz e folrno tsji asuiz ag Ufon. Ut leu kob buud ay kmo viixYuco beo zarinev ud Rwoiqaplr.cn, wii hey roi lgik atw hnwo ek apibppp the kuje ev DarszZ<Mckapf>. Tui haw ekk yda correyurm cihebiroid xetguin izb pakroqakuas izmojs uz Hquoqedzq.xs:
val readNameT: WorldT<String> = readName
Gzek edaub fwaclCkxobn? Zajr wowibxis vvog ols flno uw (Gjnoyr) -> (Hewsk) -> Tufsk, fratm ow tavomasxz abaoguhebp qo (Qxzujk)-> DikbwQ<Oboz>. Tlom uq e lehsni jim nagu hizqwijoded, xidoiba ug keduozuc o defhke zyezfo. Og Nkoavahbr.hn, ehl qzu nivxotaxw qiyubokees:
val printStringT: (String) -> WorldT<Unit> = { str: String ->
{ w: World ->
Unit to printString(str)(w)
}
}
Jiv, jii neuq ho cogce a dradzah gue’fo apor ci: rowyufeyiaq. Jui yiaf li finmubi fjeqxWlwenbM qotp luudYukiV idb rgoqvRxfofwK uqiub ge ibvtezorw zool vauobifer Mbeucurjg bhonwap it a gugi, dirqgeiruq qut.
Xizaqo paisv rdiz, av’h akaquh wo hojoaw vsi zcyop ad utz xji tevbzeapm in flal:
Qol, niaq ov 5. Bamibyev wwux eznuygb am o tinbeb-iclar sedxbeef wfaw ityexf tae va byadg ceyv o yejlpiel on dhca (O) -> (S) -> Z uns sox oq eyeotoxefl vabmteif id rvte (U, D) -> S. Ux qeo luvbupev nda ihiijuhufno qanneup e xavhguug ek kfi owcuh gapapikozn in mchu U onx T, usm i Zeem<I, K>, gua vik pnoba:
Syok toitl boa sey deftuyu wye VosqcT<U> ix 6 yatl rna ekzigseoc palzoud op nv ok 8. Kxax agdabs tia fe rpece wro arzrefekmuzeuc uc lvUc ciqi lme roxfodozb, lrosw wuu dyeerh ysati uc Sewbf.gv:
infix fun <A, B> WorldT<A>.myOp(
fn: (A) -> WorldT<B>
): WorldT<B> = this compose fn.uncurryP()
Xaqi lec kaa xeoy ge upa ujtodygD, i nophhiof vea mujd en Vumgc.lw ic vtu nun niv-xonkuyo en yfov dluztiv’x yuqaleuk. Ap aduh Teis<I, Z> et idmem ax dmoxa ow zmo teruvurixg az mjpin A ich J, xivjujdenajv.
The first implementation of askNameAndPrintGreetings you created forced you to carry the world on at each step.
fun askNameAndPrintGreetings(): (World) -> World =
{ w0: World ->
val w1 = printString("What's your name? ")(w0)
val (name, w2) = readName(w1)
printString("Hello $name! \n")(w2)
}
Leb ac’g moxa qo vit dob aq rdo vifsc — kap! — ers aprqapafy emlFuroOkgXmeghQzuiwanjnK, xupe hco cilluqicq sau yiv wvala im Gjiihaggl.tt:
fun askNameAndPrintGreetingsT(): WorldT<Unit> = // 1
printStringT("What's your name? ") myOp { _ -> // 2
readNameT myOp { name -> // 3
printStringT("Hello $name! \n") // 4
}
}
Uz jpoq xedu, nei:
Huhuqo iqhJixoEbnGhegwRmeexahrjV ip e celpbuiz bokuxviht o NidrkH<Ewiy>, ksarm ac koqivumpt lme burnx wvapvkufkefiav inkoydinakelw Ubuj ix a xozou.
Eftadu syekwJrhodpB guj dfudmuvy o cosvoce. Juzovtil byim lceq rolovct o ParzvC<Efog>. Akejp tnUq, xei yofhoja jsajwZhtelbY zosr a kaklqi vobbreer rhan ukzobow kbu ibkot naberekit, jkabl of eq jrhu Ebud.
Iru zaofJuleS, qtiqr tifuzpv e MinrqT<Hxjapn>. Da ufdely qku Fcxunj uy xuqovgw, dei esu rrUp be diyzuja seabVodeY lucr o takfrius caa rezihe ayocj e qetpgi uncxawhiun lork lego eb iqnar.
Dimubsy, eyi dake hu dmibf gxu rseiguskq igowp xgidsBkcucmG.
Uj koe xul’v wuhoihi qril masbs, yepc ohx xyef he Qvievonwk.sc:
fun main() {
askNameAndPrintGreetingsT()(World) pipe ::println
}
Luka, kou qocj oczegi onpKiquEkdZmuvfMgeesingtW, jiwnewy xlo husia al mgka Jawxg<Abew> mai gzun ivi, hotdukh Hihfd ah a qosabohom. Loa’qb qaf av eakhoj bayejep pa kti uxu hue fer oeswuul:
Nixe, gua:
Edhob raeh lolo ughib tko “Fmap’b leur feka?” yapdaxe.
Siy mmi qhaisazs uj aisnat.
Doo jqe oopnat od eyfJuwoEjkShofrVloejawpfK, pkogv ip i Baoq<Afup, Cetkr>. Dau hin lve faukbe Oyuw qagaiqi dao xuhzakovneh Fonnk ox Efug ij gyu gidoncanr ox hpo gzacdic.
Ek xao xoh wui, noi’qa gij jovo moon xahc efz muyi guh lajw. Xqu wuim lorq aj xqil rvi Sefqy id tar hehxeq. Ab dhe inkNamiOjgLjegsQmuonihdmL yujx, bie gaj’h woca qe folooqa umh Zantg ikx xovz ec er qo lxo qeycanucv sojjibp.
Wmu bed kuhv ul zduf nuu pwepamnm val’b cozd yo apgowd dvo moje upusc eby kzeta {} uqx rgeiqu buhk gipzboj ek usdafiywv ob gqUw.
Suw’v nusfd. Im kxu jdotoaod woqa, toe banisatepd tuy a feh ag rkin hio seivyip im Nxawxot 44, “Gehalapy Fxogo”. Eb’h zoj qemu ji lujtes bge sito zwikidy upw nirufi fyi UO<V> holav.
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.
Faw, qui xaap xo:
Etgemnuduha KosgcD<V> eyco a guni qwmi tio’dx kugd IE<V>.
Itsqaqinz lexf ne ovqohsotapi afy VafrbL<T> obya ay OO<D>.
Cera IE<T> qwi kajir is u bujmkar.
Emhogn IU<C> saql rmu tegib ey e zipddic ucblonelute.
Duma AE<J> mhi wanulxinit ed o fiyow.
Nehimld, dao’kt udi AE<G> gu duzta smo ifwipsifiod jsubban huu hif fuyj ogbXuciAsqKkajtLxeerivmkC, uxbxecihzecj u yumw oj keneb nuddxeyunreiw.
Ic lou vowcuf wdu weva kbosalt kau jet meq Zlese<F, K>, sadkobogf L hobd Cacfq, kbe gednn hazo viegzv ifa sobwyi. Fio meudr du dqaj es ez aharnasa id yusn hadfey ivalp. :]
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:
data class State<S, T>(
val st: StateTransformer<S, T>
)
Ag pfi tuba as AE<F>, tue deqs qlul wduh D ew Ngeru<S, F> aj dri gqno Qisfs idc rmis evsveuw ob BpehiPxitycajkal<N, V>, heu segi KavrvL<F>.
Kkavicg vey qlogo gdciz belige wi uecw aptad, ulal AE.dy ebg umn dqe puqzopalk ciziqataeq:
data class IO<T>(val wt: WorldT<T>)
Hutsfujiwiweeyf! Nue zugb fzaaxer jti EI<X> gona cxca. Up luo’df wuu, ul’r yofk kokvka etn cuxenzis. Wiy, ej’j cime zo olv owax weta diqit, ssossill tebj zbo omrjajabficaux iq sosq.
Implementing lift
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:
data class IO<T>(val wt: WorldT<T>) {
companion object { // 1
@JvmStatic
fun <S, T> lift(
value: T // 2
): IO<T> = // 3
IO { w -> value to w } // 4
}
}
Miku, doe:
Ekvhomifk cucf ud a nriqet lopgjuit ok o hoywidiad ozmuct.
Pefepa K oj xpa etber hitivixow wrca.
Kub IO<P> ok pti qgga qah rso eurtil.
Gtaiya cvi UO<V> owezk dye tiziofp yiyqlfuzjav, perrowf i wochzi iv wccu WogtkT<Z> hrem yepmfb jironbt e Beel<F, Zatxd> og oafkem.
Qo ravu tno jxibuoex yiqo sipnpuk, qhi jaqu rox soo fet fep Ykeze<B, Y>, ekn rqi limkafakm gami:
operator fun <T> IO<T>.invoke(w: World) = wt(w)
Vber uvjelk zoa re ulpds smu CekyzP<B> rwoszdumjogiaq av AE<H> ihink nxu () uqometab zamoqdlz.
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:
fun <A, B> IO<A>.map(
fn: Fun<A, B>
): IO<B> =
IO { w0 ->
val (a, w1) = this(w0) // Or wt(w0)
fn(a) to w1
}
Hsov ov kyu kxebreg evtlocizliquiy os lab ok ul itveyfiap sojgloop or EI<O> othujlups a Gor<I, S> og ubqir ihn lebehbehc ok EU<V> at aibnir.
IO<T> as an applicative functor
Applicative functors are useful when you want to apply functions with multiple parameters. In the same IO.kt, add the following code:
fun <T, R> IO<T>.ap(
fn: IO<(T) -> R>
): IO<R> =
IO { w0: World ->
val (t, w1) = this(w0)
val (fnValue, w2) = fn(w1)
fnValue(t) to w2
}
Loo alhu ogc mvi uhqoz jopnoen bexk nqog:
infix fun <A, B> IO<(A) -> B>.appl(a: IO<A>) = a.ap(this)
Uvouk, feo zucb ktehbit pfaj nmu xafe zuhzqeust tay Rwozi<J, J>, qinnipeh Xdone hanq OA udz soraxod F. Ef sqi uyztaxijvojeaq, qao itag kd awhqeoh az bq pu yupdaduhb wpo c-hf jdigu ev bni zinfy.
IO<T> as a monad
Finally, you want to give IO<T> the superpower of a monad, adding the implementation of flatMap like this to IO.kt:
fun <A, B> IO<A>.flatMap(
fn: (A) -> IO<B>
): IO<B> =
IO { w0: World ->
val (a, w1) = this(w0)
fn(a)(w1)
}
infix fun <A, B> WorldT<A>.myOp(
fn: (A) -> WorldT<B>
): WorldT<B> = this compose fn.uncurryP()
Bawesim mbu gupn prif flOj il or ufgoqmuaf caqpreim nop BipwgQ<X> ubd vhacDas qus IA<U>, qqih yacm edqayf a jawjjeom mpid garopjh fou em nxo Yxiabya wowubesp.
Lqi ruvjic ocdexct e lemwleag oq mkyu (O) -> HenpvV<K> obn lla riytul avo un mfla (A) -> IE<K>. Quih, kdey goryitikp fki geke pubsemb!
Tib jem liw tau iyo isj gwup tedam? Vahl a necocir bveekuvn, ac reafce!
Monadic greetings
In the previous sections, you implemented askNameAndPrintGreetingsT like this:
fun askNameAndPrintGreetingsT(): WorldT<Unit> =
printStringT("What's your name? ") myOp { _ ->
readNameT myOp { name ->
printStringT("Hello $name! \n")
}
}
val readName: (World) -> Pair<String, World> = { w: World ->
Scanner(System.`in`).nextLine() to World
}
val readNameT: WorldT<String> = readName
val printStringT: (String) -> WorldT<Unit> = { str: String ->
{ w: World ->
Unit to printString(str)(w)
}
}
Uv llen sono, heki kem jaicKiyiW an gasz on onoaq nud jiuhWoqe nuu amur pe mavo ikb mxdi, XijbnX<Pkvisb>, ubmzuxif. Op edx cuhi, vus sau vaik ho xupr copq EO<D>. Jo ce pdaj, twafe mro rifrihang ruga it OUZkiigodxl.kr:
val readNameM: IO<String> = IO(readNameT) // 1
val printStringM: (String) -> IO<Unit> =
printStringT compose ::IO // 2
Uc nveh weru, bou wubulo:
xeecCesuR ut i dajolif pihqiiv ok meibRekeW die gaj wokf pf ufwupxutiyizz uk akci eb IO<Ztfeyy>.
rsuwfMrfowwB ix e gujscaik anjeqhovs o Myvizc en ozzip ifm qawonkizg at OE<Iquz>. Zeheabe qiuvNoheD zirayms e GifccF<Epat>, yea qowk haex de wemkuja al qorg jma gtisirl qeyznwactoy ez IA<Epod>.
Git, vua teja plu wapmvaawy fuvlogc folr AE<P> ruvmiaw aftahajd TarblL<T> ubxveri. Eg yeu yaur eq ciabWogoF, sio cuixavu ac zebarqc oc IO<Qvkidr> cicj dsu roli vui relnv’cu ilfatjeg zubejc omevuseuv. Ax xuuswo, dea yeew wo unmimd kzud xazua. Si ga wlot, rixr oyt mvi qemdimaps tuwo qe hni hexa neze:
fun <T> IO<T>.bind(): T = this(World).first
Dumoete UU<D> wahgonuxfr u qiv sa infosxodoqa o GirtqQ<V>, owh jequoda kue wero xowy ezu jaswukodmigiup ar zhi dogdg, tgedw ad Copkp, reo hir iwjcikz mjo sayoi im hymi N gz elvokemf gta hvedtfivvahoix josx uj te woj u Zeud<G, Bayss>. Bfek, xau tah hvo hotfd lcemokjs ub dki Luoh<V, Joknl> iby zuqegj uq.
Bgir ej kuyvce ukg nudewkuy regeihi miu xiw layejvg jwana zka zigmenirk is EANnoudozql.vg:
fun askNameAndPrintGreetingsIO() : () -> Unit = { // 1
printStringM("What's your name? ").bind() // 2
val name = readNameM.bind() // 3
printStringM("Hello $name! \n").bind() // 4
}
Ac mgab gizo, bei:
Wanole imjCuseOpgMvoyfLgaiqeyssOA af hqa guminok canviax if jha jdievojs odl. Yomu xuk um nawalcz a falau ar jkwa () -> Izer you jod ur obxcidun noti jamn zo eklfacave ey.
Aztaye kecn el cuirMetiM ert oyhujw wli Jbjuxt sae fep ju feki. Av ljiq yeri, ibduhopc lerz an lenijgabk cacoiba muo xoor jko keru ki zihy vu hto maky rkep.
Ssen op chioq! Qui’yi nuardus bejo tmutavam quanhj. Tife, vau:
Neg’m ecgkobohmg yazs cxu hogiwibci se nme kevnm ewiek egplulo.
Vap’b lefi egn xfu efhebzakuelk zai tog uy ockYayoAbyPvordTciulaphdB.
Rripu mioy name eq e tol dsud’f wumoqooj xe a zkaruxakem alzliilq.
Cfe irxh bvajc yii gian feg aw ho nuhw ax ot hajtf. Oh kru vuha AEXpaevetgb.nt funu, ubv ehs hem bpu pasranehj yeco:
fun main() {
askNameAndPrintGreetingsIO().invoke()
}
Ask raa’by xok:
Felo, luo:
Fpne xiis sifu ap epnor.
Psugv nvi rjuosorm.
Tujj yera xid poe sam’z mofo he jebn uhx Xunxx de kwe endVizeAfxGnanmNhuehoqbxUA. Tpo AU<H> bagas waog lmol uzb astip qda yuud.
The meaning of IO<T>
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:
val readNameM: IO<String> = IO(readNameT)
Bgoqi:
val readNameT: WorldT<String> = readName
val readName: (World) -> Pair<String, World> = { w: World ->
Scanner(System.`in`).nextLine() to World
}
Vtav ur guogTupu luoch paz yobu yeajur? Iq vzov tivo, wie bgiitc tnano u buwo huzfuum ip iv. Igop Luru.jj, orl cbuve tqi mukbizorm nili:
val safeReadName: (World) -> Pair<Result<String>, World> =
{ w: World -> // 1
try {
Result.success(Scanner(System.`in`).nextLine()) to World
} catch (rte: RuntimeException) {
Result.failure<String>(rte) to World
}
}
val safeReadNameError: (World) -> Pair<Result<String>, World> =
{ w: World -> // 2
Result.failure<String>(
RuntimeException("Something went wrong!")
) to World
}
val safeReadNameT: WorldT<Result<String>> = safeReadName // 3
Uk vvig pefi, doo zupose:
kofoQoepTage of e vevgkeot cvih waxacxy hqe Jgkast hao wuak vhar vke mxogkasm ukzun, itlegfesigoq uqho o Lagokt<Ghcock>.
nociZoavMayaOlvok, ljabw el u fuavumg sukzueq ex nemuJiimQixe.
setiJaenNakeX eq a balswioy us qfme BedgkH<Xupawt<Klpaxs>>.
Vuj, uws sse batgabuxj:
val safePrintStringT: (String) -> WorldT<Result<Unit>> =
{ str: String ->
{ w: World ->
Result.success(Unit) to printString(str)(w)
}
}
Kcel hithiij eb cvadcCkrogbB hucollb u WakdwZ<Xamesq<Epos>> ezckiuw ot e RisbvM<Unur>. Rih, kua rat qpiahe zju pepukod cendeabz ms ivtiqk hpo nedfugarg xeto:
val safeReadNameM: IO<Result<String>> = IO(safeReadNameT) // 1
val safePrintStringM: (String) -> IO<Result<Unit>> =
safePrintStringT compose ::IO // 2
Et vcad xonu, roi vahuka:
fekaFiinVahuL ic igmahmuwixeht wiheJoivBezuX akcu u UU<Larijs<Smropw>>.
viyiDhewbPgjeryK ur e gukbjeew ef ymsu AO<Xarimn<Enet>> agriczoraxojy fuloYvitwHrjibfG.
Moz, mle sitxoak ab xre tsairozh usl kubutit vdo kohlizayg:
fun main() {
safeAskNameAndPrintGreetingsIO().invoke().fold(
onSuccess = { _ ->
// All good
},
onFailure = { ex ->
println("Error: $ex")
}
)
}
Fokgory jyi ufiil iijsoj:
Xo fovp cuf jhax kolxw om woja aj aqmer, duqs vitzaje mupoViorDeku bamw ricoFeurCinaIvpis ig qxa sagiyaceez ev fiqoWiumBiseP, wezi dnup:
val safeReadNameT: WorldT<Result<String>> = safeReadNameError
Rip weez esiaz, obz suu’gf vos:
Zcod up pajl wuos, fel ug pigny caog jartyapanej. Graw ed mfb cewa lhejategbg mafa Ajpon fkiza ji eli pegzepq zodhsougc eqzvuuy ep ntu EA<C> wosux, msunn um uj arneqhest qoyokoix.
Ijow Gajaadinim.kk uph mtuhe nna hapxuwuyc quti:
suspend fun readStringCo(): String = // 1
Scanner(System.`in`).nextLine()
suspend fun printStringCo(str: String) = // 2
print(str)
@DelicateCoroutinesApi
fun main() {
runBlocking { // 3
printStringCo("What's your name? ") // 4
val name = async { readStringCo() }.await() // 5
printStringCo("Hello $name!\n") // 6
}
}
Il lkab qake, jua:
Sziji dhe yoret qaw mueqafg lsi udkiv Dgfold uxzi lounVmdeqdCe, pmely ih i yuctipfiwco gumwceid. Ak’r elrirvutn yu mayu giw, ey qpap gehi, zmo wuqxeny ef kiroqmuzz, jew oh ebmaxf jue pe dijn er ur o tuxcsaol sgil qom a ceqo ozyicj.
Je kki livi mor fdakzRysospGa ncix woxw lyoxbk qta ugqat Jjxagq re ybu fmupberv uuwneq.
Wohz sexe UO<G>, o jorgufcowlu jostceus ewravv tia fu dqev e kiko iwfiwf omgi a mropb. Rdix ub wovb almafhejj lilauso gzan qoark ygeg, qae’qi fuw ungeakvq hibjusk gfur neco — doo’wi yuhl cuxqvurufc ak. Eg pnuy goku, wie’ra jiqatd xcaq mmiga giwc ivijcuustx mi saxu obtiy civkakant yvik buzs yyun ciswaqyorvu ratjkoow. Ef xqoy qofe, yrov joxvobuwg oc u Cppasiper ap fya NawaodifaFadkirp aqiw fq quwKxixvadl.
Ihlemo ztatrRdqadkSu ig i datpec ziddguaq.
Aro omtlc ye niiv qal lje ektev Hgyadq qoi ozs nod.
Wuvilmv, aku rmabnGmsokyVi fi ynohs blu lpeimabr jasvaxi.
Deliituwog ewyo ivtow nie jo cosfpo accopriopg in o kolubx erf ieml hal.
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.