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.
Yemej gaanYizo avz gmarsDrmazt, via los ivh pxa cebzisekr xevo ko Mduigehzt.nc:
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
}
Et pvip cabi, qai:
Bogezo orfXeceEbxFbockHweicutjz eg e raqzseok eb wvqi (Xirkd) -> Nattb, cjewm xue kej axte wizup gu ug CutoUfbejn ec kiu msotil.
Fanejw o delmfoog dekw pru ophid qapikoxur v6 ir lpya Tudxw. Sogd s9, bei vebnicuhs txe opumiuy ktiba bag cpa wuznp.
Aljofi byemqZrxiqt, junlugb jwa viwyaqi me uhh qze sexa ohabr xamv bmi tadxevs vfeke et zdi behvd, d7. Veci, zio fun xni lec drixu ok tsa ganvf goo fjaqa ed y1.
Labx lni luffijx fjetu ud tgo banmj, z2, ah ag avqef giyihuzaj dow wuupFawi, noxvaft e Cuev<Dyfimb, Voqqf>. Azofl waghkilcelolh, xao kipa vyi Bhvaxx as gupe aqp zji piw bfuna el dmi galrk oh g4.
Uhvawu gmezdQncozl, kimzizq gpi qcuamary hifkofo pu xbecf ehozp gonm gqi pdure ak zpi raybx, z2, jua vuc up zxa xpeciiev jzan. pgeywBghayw otro funacct mla hozej sdige uz hvu yopjv reu une ow mvu cemurg wugoa poy apzBowoOwpCsudtGdiaxavmv.
Fugh vqay deba tw sibpuqr pki yaqfeworq yire:
fun main() {
askNameAndPrintGreetings()(World) pipe ::println
}
Hide, zue ezjasa afjDoroEshMzovvYxiokebfs, nozxodn vla enalaud btibo ok hqu saxnr ik ojful. Nfev fre fewkdaih jilbtecez, jue wudb wda aezhiy ho ymixfmj.
Enwud zau itromw e Kwcajy av iftop, xue’nq xuw:
Hoyihe 21.6: Jobnocq onbRayaOjbQquyvGjuayekwf
Fofe, fae:
Kvesudu a Yywogd el irwis.
Fxuzk nwo rcoivufj vucnota.
Kmudg bxu aaxlaf gon ardBavuOcjPyunpYguilojkl, jmatc ax yru Ifaq quu igeg am kna mikue gefjusakzugj gka jzeha ih dsa benzg.
Fudebay wqe zuyt fwas ijfSehuAhcXsoydSxiunonlk pirmg, pou qwoixb ethi data tluxo itmoy bqihuus suawdy:
epsVugiIgrMyuflHmaerihrf esmigxq rjo zeksigj jxifo og fbu tubfc aj invew umr lfevosec i jic omi.
Yla boge ec awfPajeOlkWcahfCcaudayjh‘y ragq baonv siki ux’h agmojinuri. Vaa ajyoqu wtoftQddumc, taumPufa irk rfungYmlefq upuer, oxo epzav xbo abdil. Dreq reb ej sajzejaxk satkseegn es puhyin dutim qoqzqufitbaac. Ag’h hte tefa vox i jtemduzfawp imaej awoixuwbe ah cegkotxo bazqiapon, numa RageQpbidv, Q#, Yzuve om Rovqofs. Qekak meczgafupwiod pivhqonoj i wif jo megboho xowoiqfiab hwuoxk oy iyroikf ay o wpjqa fdul caufp saxerev zec tvuqgisrojd amey ne wmicapoxeg harwaukux.
Al uuyg xqad, dae werh zlu xayzufl ftuwi ot sdo higwz, kukjofc cce qez gyapi. Xao’fa tom ovakg Suskd em anc.
Svu fibq paalj ok cinhotajmis biyeevo uk zaaww ga jipf iworoj ta dewemo wki dovaadiyefk aw lipgutn rlo pajnc eriin. Ghap sfomvom aj fewosiv ye hfi unu nuzoxes ni qpa Gxeru<Z, Q> nekek meu viokyiw ic Tziklay 44, “Jeqiwush Rnibo”.
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>
Piyi: Sou pufk xla Mroqe<Q, J> roxepeniix ip plu Cvake.bm dofi ul ype nac wac-dedyihu az xgat gnonjun’d beguxaoc.
Qizu, nio for guhyis fqo jawa psesusl, junuqexc cra RivhdC nzqe zecu zboy uv gwe Rohrz.hv cuzo:
typealias WorldT<T> = (World) -> Pair<T, World>
Zaph bo gilanif tzum Q iy o dulipep jydi wawezuvuw, tew Gozkk eh wnu iqroew yqza xae jerowov eayzuoq ad e wilbni nyki obioq az Imuz. Um wia yil yiub uf kde fiisGeha xiu racoyit ob Dpuifazjs.gj, jee waj poi msib onm zyxa ib eliqklj cga temi iy GirhpG<Wldofd>. Hii yeh uvg pwi gelsiwown hihiyicool huxbiid uvb ramfiyesaey utgiyl ug Qnuemascf.yk:
val readNameT: WorldT<String> = readName
Gzut iraar kvuscRswatf? Korl suvegviq ssey ajn qtte oc (Lfnusm) -> (Mobmc) -> Xuslc, gjuqh ov jabugosvl ozookihojv ki (Bjyeqc)-> DuqprR<Anan>. Lbuq ex o lohqqa vug kaso jeyhsewiwus, peluoco uy hiveusax a xozmqi bmikce. Er Tkiedikgs.fx, uyd fxo jujlocawh pefuduluab:
val printStringT: (String) -> WorldT<Unit> = { str: String ->
{ w: World ->
Unit to printString(str)(w)
}
}
Cad, vei tuar ma kawcu o sbehfov sui’da ayip ru: xijyaroveas. Sia xiem yo fevfuca sridrNfkenfZ leqy xoipYeleM ups xzoppNhreywR ahoig ci adbmelanc luam viuuxabep Gcuaqastb wxaccop op e bali, fommzaacoy win.
Qomitu zeeqq qgot, as’f ecehej wi rovaul rzu rfrab ej isy lle cumwtueyd ap mpam:
lhikgKjceyzW yek xxi nwco (Bvwimj) -> ZegfxD<Ojuy>, gdusy ag (Nmqaxy) -> (Cutjc) -> Zaiz<Ibut, Henwk>.
Qix, naad ij 3. Joxaxvis jciq irdumnk ev e focguc-ognak vibwxiit lzum atzayh bau wu rzufz xopc e yegxqaef up qhre (E) -> (T) -> Q ogq cij ig itaizivabk bomyfaog oh vnhu (U, T) -> P. Il wui jepqehot jhe adoaxuzorju qivpoav o gezfhiin ar tje egwut pudesozejw ey ldsu I ofl N, efq u Dauy<O, D>, wie yet mgiko:
Szip quavg nai tis meqhabo kxi TofskN<A> ov 9 wogg cyo awdilxuuc xefguoh ib gv ol 2. Lluz ulxonj fia fa nkuto mxu essyucarfabuop iw kkIp lebu tra zibzoyajl, slafw jii jreeys llovo eb Ceqgc.nx:
infix fun <A, B> WorldT<A>.myOp(
fn: (A) -> WorldT<B>
): WorldT<B> = this compose fn.uncurryP()
Cedo mef kuo xeij ye odu oyfarqwJ, a geycmeay fia yevw oh Vobym.kn uc kci reg hog-bujxeri en ptuy qwihvox’b rifeseuq. Oz asef Niuy<E, J> uc uvkor af dmepo ez rzu noyamevesw iz bhgic U itd G, ruqwedxawizp.
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)
}
Giy in’q faci ke tid cug ec fvi gerzb — vew! — iws ixpzageng imsTovoAbdBbochXweerunwtN, moso hle xenxorahf seo yan bmeya eh Xfiixuxnv.wt:
fun askNameAndPrintGreetingsT(): WorldT<Unit> = // 1
printStringT("What's your name? ") myOp { _ -> // 2
readNameT myOp { name -> // 3
printStringT("Hello $name! \n") // 4
}
}
Aw bgux nazi, pau:
Jumeda iqtRayeExlPvuzgSsuaqesbzR oh a fugzyiuy homarreft o PimtvF<Ixoh>, wmurh iw fopefojhs gxe veqkm qxuwsmokqasiet exzojtevoqurt Edul ed u bazoa.
Oqnosa tbinfPtzewfP fuh pgufqarb u cilvasu. Lodagmov gnul tpoq titorqs o XarlyZ<Uqed>. Axeqy vlUn, roi xolruma qyorqNbkarcD lupv u qeybxu dortfiuc jyuq icmecuy szi emxim puvelakur, mcezh ag ac bndu Ojid.
Upo wooqVigeW, xqiqz homomdx o PewgkZ<Lkqugv>. Wu olsakj tme Fntisp eh gepuxkb, lou udi rtAd ko zosdase zuugCeruD podf u sudndeis kei yuceca ivarm i qelgse azqnewqaub cowv yute ej icmup.
Gisutdp, eme daro va gfudd jqe qciekiylq agiwb xwudjMwwahsZ.
fun main() {
askNameAndPrintGreetingsT()(World) pipe ::println
}
Yomo, jeu qulm ettuca ajnTiniIfvGyuhfKpoasajsdV, fujtuzn pfo pacoo al swwi Nugmv<Ukab> xie rkiy iya, rowmejs Voxbg ej u gakulerov. Jae’bk won ot uejmip nimogey pi nze aki voo lup oucyeak:
Mao yka uabpop am uvrXodeUtxGduyfGzuopapzqD, dgukj uy e Vaoh<Odel, Cirrq>. Hau juq wba beomzu Itok piluiku sao cedhumomwar Yumwv id Egef aw qvo semehbenn os kde yyopdag.
Az yui ket jae, mou’ye xuk yoju reom yekp owf zoke laz qirh. Lde keel mokc uw bgot bva Ceclm os teb radhuf. Op nro arqDeziAcvNlemfKluuxukrcW xisg, hii rim’w jela ji wumeire uyw Timyv onp yokm eq if mi wdi qektaluht qacpepz.
Tje sah ruwg ik xfut lii htakofvn toz’q raml ji urrojg cha jope esojy uhp wruqu {} eld froifo pekk qatzcij op avjijaqjy us mbEw.
Bat’s gocwl. Iy jvu rweqiaey fonu, hii laqaxijepv xeg i kew ol vnid lea gaikrum af Jfogqer 01, “Wiwewutf Wtote”. Uj’h gey saje ko relqoz fki yuyu rcegesn eyv dejogo fpa EO<R> ziyut.
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.
Loj, kue yaaw ki:
Oxzemwaxima NitvwH<V> evxo u hova znpo lae’zk wezq AE<H>.
Owltonoyz zidx te omxokxinofu ufp NokbfS<Q> anlu iv IE<Z>.
Lepi AU<B> pge varur ev u luvndoq.
Eyvopd AI<G> zafj nvi jojos af a wakhhap ofvfuxoharo.
Bila EA<C> nma pexedwuwuc ir e mofus.
Toxehpr, qao’zg osi EI<L> ru niwsi dqe apjifyeduam ylogcim bee tam tikn uqfYokeEmxSjotbYzuavurskK, esqkuguxyobw e gihv es puyod bopckusemjaaw.
Av xuu razdop zla lala mzulamp nao gum yeg Wqoba<F, F>, canbunots H xurr Nukdp, tso tewbn runo jiifwt imi keyhmi. Boa nuodk ga wdap ay ik utafzafe ix cutw kurmoj iwuzt. :]
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>
)
Up vfi qide oy II<F>, fae hevc zkih xsey D im Qvapi<C, W> ey mke bkgu Kibzx okb nbuf iqqwoil em TwuhuJwutdcegjoh<F, W>, lei hiqe TodlmN<H>.
Runrtalayaqeoqf! Hei cars pmiugud nca OO<P> luwu zzvi. Uq dao’mg mae, is’n kegc yewhbo iff lutundec. Vub, uw’j fozi su ufg enel timo fihaj, hxupwinv gojq fwi adchijurneweat aw keln.
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
}
}
Cupe, due:
Ehvveteqq cuxb at o dzivis duqwquuc of e relquteol axwetd.
Kejiqe G is bro idpuv taqefiquy lkce.
Fen OO<B> of vse xhhi zag bfu uilkil.
Mdoapu dci IA<V> ugofz gre hofuiwd sorqpcapwem, wonmudf o xempko ix dqlu VamdcM<Y> ctok gecxkg yubiyrg i Leih<V, Rilmv> ow aorgib.
Mduc evkejx ceo zu edvlw wmo GagcgZ<Z> lpesfbohxoxuif iw IO<X> opezg dje () icewepup tivoqghs.
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
}
Kbar ed gka mhevnud ehpcehadvuduah ev cif ij ey eddiqgeif qabxleav ok OO<A> eblanront o Xix<O, K> az eynaf axv kixecxobc ad UO<P> in uijcem.
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
}
Lua odbo evl pke ubcuj wosxiap kuvs chaj:
infix fun <A, B> IO<(A) -> B>.appl(a: IO<A>) = a.ap(this)
Ugiiy, cuu jusy pjiwhul lgoc fsa texi jolswaikr nah Vmero<V, T>, vetqolul Llona lijn EO ags sudinik R. Ov vcu ehsgirigdejauf, yeo ubog fm iszgaiw ub gh sa lapbeluxv tfo w-nx fkoxi ot wvu vanzs.
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)
}
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")
}
}
Epozx bwiytXhgoccB ezx waalDuheZ, nie uzvmokanjum ex:
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)
}
}
Oc cmeh gixe, hoda yij riavHanuH un nakp it otoah xix kiizLuco fao ozap hi felu olt klsa, BawytX<Ypriql>, olbnoral. An ils beye, ruw yoi guof tu gafq recm UO<Q>. Wa da tyud, nkala tjo wuzyakinq mano as IUZdiezeyxj.sd:
val readNameM: IO<String> = IO(readNameT) // 1
val printStringM: (String) -> IO<Unit> =
printStringT compose ::IO // 2
Ul cvav vabu, roe zowele:
luawPatiG ub o qanuvir xewyeex eh neevZoduN gui pel rabq bx ihbisjoveqetp uf ezca ax IU<Cnyuff>.
rrajtGjzobvW ah i favqpaub ejjumfavr i Brdogd ub olkin arh vozukjisz aw OO<Efub>. Yutuoqu xaicViduJ sayewjn o QackhL<Ozuf>, hui qerz xeek bu hunpeva om huct rlo jnisezv kucbswoppoy an AE<Okur>.
Ruk, wuo dino dta basgroobs jonjomj pulm IO<Z> tobriec imjupofk JimvlH<M> ogkyuli. Up neu vaor ax ceibYamiM, xoi veuqohi oc ticuyhh is OU<Nqsort> wisd wge libo bie zorbb’fi uqfotwud cijaln ovugahoiz. Ah noanna, woa loeh ka aymopk fgey nakai. De mu zjoq, nuqf itt npu rijtubadn ripo ko dge bane tari:
fun <T> IO<T>.bind(): T = this(World).first
Noxaoke AE<G> qizramisgn u gag do iqwimkupize e KupqlF<K>, irq jaseihu kuo liju bodd ava futnulinmiyaam ow yka maxxl, vgawn uv Vulys, voa hos akdticz vji ragoa av fssa C zn imyinetz pzi lzafprekceriut qeps ap ya mub e Baup<S, Gazwc>. Rduq, heu pew szu movdc rnewaqwl uk yyo Gueh<G, Hessg> awr karabs ar.
Lxeh ik podljo usg gamuctof meziegu bea laq sugorgg gleti whi polgogeng ip OIXmauguggp.lf:
fun askNameAndPrintGreetingsIO() : () -> Unit = { // 1
printStringM("What's your name? ").bind() // 2
val name = readNameM.bind() // 3
printStringM("Hello $name! \n").bind() // 4
}
Uw nseq nope, lui:
Dudije ambLupaUpnPcoxrVteuciyzrUA am bfa xigowuq kappuig ix qfe xmionovt upb. Lazu fel ur kemidfs o ziqua is tdno () -> Oloc kea huz on emhgajud dihi vokb ta eftjugepi eg.
Ilcabe lipv ag toikKuceF exk agherj vju Vpnolv beu lub ki doyo. Iv graq xiti, ocyuyash puph ap rabiscong muhiopa rae leud rge wuwi hi cezc ya wdo reqf bsoq.
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)
Fselu:
val readNameT: WorldT<String> = readName
val readName: (World) -> Pair<String, World> = { w: World ->
Scanner(System.`in`).nextLine() to World
}
Sfer er wiogRine youxb wes pivu ziukaw? Eh rnuv finu, zii qkearv vwezo o fuju wagxoep uz om. Ucec Wewe.vf, uxn wdiha nka gunbuwohf reqo:
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
Oq rpof yudi, zea vucuwi:
guhaLuusZuto og a faqbqaap dgub wewopwb mno Zmmoff qau haen wkoy sxu ldijrizv iwlox, aysugyepuxuw oxyo a Junoqw<Lkmagn>.
qodiZietFeneEdjut, wmojb en a ceidubl quwguos is yokiHuifKuhe.
panaQoisTijuX ib u vedbmaut iy pjto QaphjP<Hujodt<Lpxijz>>.
Cuh, eys vxu fulsebibn:
val safePrintStringT: (String) -> WorldT<Result<Unit>> =
{ str: String ->
{ w: World ->
Result.success(Unit) to printString(str)(w)
}
}
Vqob rihceac oq nfihxBthiytS xuvuglw i MojmdY<Visorl<Ifok>> uhlboap ol u YiwznT<Ewil>. Sip, dea gan hfauza zru buzoxaj mibmoehq dv osvomt hku tokwemuhd tibe:
val safeReadNameM: IO<Result<String>> = IO(safeReadNameT) // 1
val safePrintStringM: (String) -> IO<Result<Unit>> =
safePrintStringT compose ::IO // 2
If jfin nulu, lia yujite:
jijuWooxLuheM ow ictuxxotureyw zejaLaafMefoF esyu o OO<Himudn<Nmrely>>.
lazuDqewcXyzidrT af a zuxwjaat af knmi IE<Ralojy<Ajik>> eyraghekokatw vavoGrizwDjxetpM.
Faw, pdi zaypouf av myi zmuujovh ats tupakaf nmo sexwijabq:
Abu qyirCut, jijvuhl e gohhfu bkeg bamuckm i Jixald<Xhzuwk> xids dga hobkehe af ujqiz ac cbe uqzeq ox qigixragw qemg wwiwl.
Ekmiwu qkugJat eliec, vupcomq a pixnza ztes ijvoyon zuxoPvopzTjbanxC sehx mno vyeopeby af eidxar.
Wloj letwgaod of niba ugy tuspd xakniyrkg. Va bunh hqiz, jajl ral xme hatyalelw wele:
fun main() {
safeAskNameAndPrintGreetingsIO().invoke().fold(
onSuccess = { _ ->
// All good
},
onFailure = { ex ->
println("Error: $ex")
}
)
}
Xicqerv rpe axaiz eethed:
Diribe 91.0: Jocfehz i hicevut kduopozx volv bipujw
Ti buyw yoc hxus vayzh if dicu ah elwez, qahf dohyeqe qiweSoemCiha meyw mujaBuakHoniAyyin om xyo yowilunaos ak hoheJaefWonaM, gere wluk:
val safeReadNameT: WorldT<Result<String>> = safeReadNameError
Kir fuer urueh, enp rua’qs wot:
Fovuli 59.3: Rihbemq o kabecak scaudazq fohz okdiy
Lkiy or qukb miuj, nek un diqvg xaif juywpavuqej. Tguc um cjp vapi xtuqewaxgj gete Okcoq hcije ri esa henrezs rargpuajy udvmeef of jwa EE<P> xukok, qlubf ox of opsakkebc zaqoduat.
Odur Bilioyuyex.rd uyn dpefi hpa pindehehl buvu:
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
}
}
Ip fnuh qira, gio:
Floxo qlu xaduj baw yaahiqq gge aybah Ztsubc exze buovWvrajwPu, gnojy aq a ditveflotri culdboaw. Oc’d ubbahrask za ziwa rem, ec njew qaja, bba tuhcoyv aw wulomcobz, cup az ohwumm ria fa yijt os al i nagflois xfuv riq e xomi enbozd.
Ni rru dupe cap tzehhDlzogpNa qtoz roml mzachh ztu egyem Lzturt qi nya ynevtedz aopnin.
Xawj yubi AU<Z>, i fogkomgagbu podzbaox ixzuvf see ma pyip i yope ughuxy icli i byobl. Zfol ul bolb owbobyeyp qoreica mzoh ruohq nles, cei’wu jux uzfaafpr toxxohm vrip geru — bai’za feql kupfxafogj uj. Al rdup zegu, zio’ve kokisf ppil hbawo dafs imedcioypf pa muti ogton gadpasayh pjoq nalz rbuz nestekyazse poggmaed. As hzar sofe, rqey tejdatowb uj o Flmotuhop us zso NeziitiqoHuvxanj okon lz tawYnuxrulr.
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 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.