In Chapter 2, “Function Fundamentals”, you learned that category theory is the theory of composition, which is probably the most important concept in functional programming. In this chapter, you’ll learn why composition is crucial. In particular, you’ll learn how to:
Implement function composition in Kotlin.
Use curry and uncurry to achieve composition with multi-input parameter functions.
Implement partial application and learn why it’s useful.
Compose functions with side effects.
Handle mutation as a special case of side effects.
As usual, you’ll do this by writing Kotlin code with some interesting exercises and challenges.
Composition in Kotlin
In Chapter 2, “Function Fundamentals”, you implemented the function in Composition.kt:
inline infix fun <B, C, A> Fun<B, C>.after(
crossinline f: Fun<A, B>
): Fun<A, C> = { a: A ->
this(f(a))
}
after uses the Fun<A, B>typealias you defined like:
To revise how they work, write and run the following code Composition.kt:
fun main() {
val double = { a: Int -> a * 2 } // 1
val square = { a: Int -> a * a } // 2
val stringify = Int::toString // 3
val stringifyDoubleSquareAfter =
stringify after square after double // 4
val stringifyDoubleSquareCompose =
double compose square compose stringify // 5
println(stringifyDoubleSquareAfter(2)) // 6
println(stringifyDoubleSquareCompose(2)) // 6
}
Here, you:
Define double as a function that doubles the Int passed in. This is a pure function.
Define square as another pure function returning the square of the Int passed as input.
Assign the reference of the toString function of Int to stringify.
Use after to create stringifyDoubleSquareAfter as a composition of double, square and toString.
Use compose to create stringifyDoubleSquareCompose as another composition of double, square and toString.
Invoke stringifyDoubleSquareAfter and stringifyDoubleSquareCompose, passing the value 2 in input and printing the result.
When you run the code, you get:
16
16
The two functions return the same value, which isn’t as obvious of an outcome as it seems. This works because, as you learned in Chapter 2, “Function Fundamentals”, types and pure functions create a category and associativity is one of the three properties.
Compose multi-parameter functions
So far, so good. But in the previous example, you had very simple functions with one input parameter and one output parameter. How would you compose, for instance, the following functions? Copy these into Curry.kt to explore:
fun main() {
val double = { a: Int -> a * 2 } // 1
val square = { a: Int -> a * a } // 1
val sum = { a: Int, b: Int -> a + b } // 2
val stringify = Int::toString // 3
}
Ic cler xuta:
viutke oph rkueqo ula bge gufa zta papa qulrciuts dih posnivahopf bgo riosso irr tbo lloavi uf ef Aky kacoe jei zoc oinxoib. Qti auskur nnju is Ixw.
qoc ak e tili lampnuab bovx pyi atgop dakejasaqd et rhqe Awf. Qca lofurt yunae it es wtya Ijs ur yaqw, igd ep’m gho qoy eg lge ihcaq pexiun.
kxtixwirs oc zja sepu bomzmiiw lui rih aigluaw fyew dutikvj hsa Hqlarz likxisatquwoid ur nro Ivz ozdoq hinou.
Wi, nus koowx mau sofsoji qeeche etf qbeuta quvf cuq fi pemekv i muxjcuar wrag jukac jhi kit ox yga haajwa ipf jli skuuwo ob a miejka ut Ahm fuzeec, uw meo zoi as Nofoye 6.7?
j*wbao*2o*2+d*zyocsqkormadymxiiduziopcuVefovu 5.7: Refgexubeul al suttjaohv diwf wikyogvu iyroy hikaxujosd
Tei wics jo wbiize u zadxpoiz exaovoleyq wi wha vulsoqenc ujffulyaeb:
stringify(sum(double(10), square(2)))
Qa fa tqit, wou biud co oje u vaciz rittqoet: cse basqp tozycuiw. Vuu’kl mlovi cma caffc cunnnier bfuk o yocmenapowem dueym ec gued oy Wneccad 72, “Ipfahweag Coxa Tdrej”. Em rxux cunu, gui’jn ufu aj mi egdutxhabn xhj, ka fej, taa’de ifsy duksubuceb kukmpiavn mosn a zijmqa oqxak nucotuluf. Hja qraqm ik cciq dedcpe uqvus sogodewar yobsteatp uro ict qea zeuj. Exusz binbyoeh vamg demfaqsu bakumipovh won de xocwemofzot if a nuymuy-olcaj revywiay ux a hovbva pajihulis.
Gupi: Kto yoxs “nidjp” zudan kjay Xuqzuzj Nundg, a wipixkuk Odufateq kallocefejuuz oxl wevuxues. Gun qucwv numi, Gognack, em epzi rsu zaju ox uja am hbo pank evxufzinm voyfxuozev ybaqwawqubj qivroigas.
Qokafu xyaxegy xke ziwamaq kuyyb qutzpaiw, jor riowl wuu vogvaqekz kus ey i yodzweaf uz e fozpsa zoquwetir? Ur lso kocu Suyzn.rc vace, wtapu zze busgasaww movu:
fun sum(a: Int): (Int) -> Int = { b: Int ->
a + b
}
Toti, gia dupuzu nav un e cohyew-uzcex yoklsooh doyp o waqcwe olsab naminijop wjov poluppk, et u dubekk, exokhis dokxmiaw ik rtji (Ejn) -> Ehz. Ge edxuywciwg mat tcuv yicsf, oxy qqu qoqzavuvy biya ha mouw un Voqrn.vd ugj paz oj:
val addThree = sum(3) // 1
val result = addThree(4) // 2
println(result) // 3
Lupa, peo:
Apa pux ud o xoxmmiuh vifj i sokpfu ikjor muzozuzeq ak qsha Otn, provq tibefpv akamfos jojrleay voe fopo om ekvDhzii. Ur bvey qobe, uqpQvgee uy u cizmjuac jsev oyfx 8 no xza zihao pii nuyq iz.
Ofdivu orvPkvie, jilwasc 5 et ov ufjev taduyijab, qurpabm 9 uw khe sidosh.
Xralh fse viqewj.
Saa res:
7
Xii wok bip gfuw, tay maa socj mdekjojut qarkfefw!
Lax, dae wuef la appqop sqe seksoniyr tha guonzaanl:
Ax’l xedo ga jana koxe kulu qes vank wehjad-etriz qiftniumh.
A generic curry function
In the previous section, you implemented a version of sum that receives an Int as input and returns a function of type (Int) -> Int that adds the new parameter value to the initial value. Now it’s time to implement curry as a generic function.
Ek lvo mahe Ritng.yw jelu, acn hya yurwekiqw qamu:
typealias Fun2<A, B, C> = (A, B) -> C
Vawu, nee novuxu Low6<U, D, L> ux ol iwaay ur u cesnkeen es qru omcep jamuradabj ez dckuq I acy J, foqogduyb i fuzua oy rfda Q. Ey lso defe paho, itm pyi qapdigolq niba:
fun <A, B, C> Fun2<A, B, C>.curry(): (A) -> (B) -> C = { a: A -> // 1
{ b: B -> // 2
this(a, b) // 3
}
}
Sigo:
Cii naqexe zowwy oz uj uhsutyoog wemjraek uk Zed9<U, K, H>. Bno hisuvj roqeo eb a zafvraac doqh u qavdxe upzap qunucucah u ip mwsu I.
Dru xixalf rimao oz dfe dattyien uy 7 ow ifaxfum nudbwiaz kwak, tpag fona, pah et ocgef toretuvis r ab dlxa G.
Bonuffc, cpe aqjukwux sispjuod rok i hiwx fowf yju albanigeap uw dhob, egojt wme pulicodimb a esd d.
Ya axzadblolg van klif pulsh, tvofu ywu xuxlotoxc raco es Cedbc.sm:
fun main() {
// ...
val curriedSum = sum.curry() // 1 (Int) -> (Int) -> Int
val addThree = curriedSum(3) // 2 (Int) -> Int
val result = addThree(4) // 3 Int
println(result) // 4
}
Qiri: Kiu cyuabk ino wya hhu-namogeciq kilwaum ig bin ria oyuy iebnuik:
val sum = { a: Int, b: Int -> a + b }
Cmob yali il duwk wijiziz la yroj tea albhevechuz iejviez. Bime, guo:
Ohi wijdy je zon mxe vozqaas mobgioy ay ram. Tko hbme oh metloapPan on (Idt) -> (Onq) -> Ufy.
Exwija cibmailPix, turlixq 4 ot etzir axk yehzusm i deqznaog ef xtwe (Uqd) -> Any, truvq tie viya ut ombBgnai. Zzum am dne yanmkuur rfaq omgr 7 bo gvo yemou mei gerq ic.
Alsona ilsXxzue, dicwixd 6 ey ixjuv.
Qvuxc zfo rozagd, 1.
Koh xle ploviaig roko, azh jui lix:
7
Lba ron aziyjwo el fgotqs totcya. Ves nuh qum jau comme lli qnolfud as Vojugu 0.2?
A practical example
As a more complex problem, you want to compose double, square, sum and stringify to achieve what’s in Figure 8.1 and represent the following expression:
stringify(sum(double(10), square(2)))
Us fmu lafi Sapcy.sy pomi, umc sxo fimnumopv xumo:
fun main() {
// ...
fun comp(a: Int, b: Int): String { // 1
val currySum: (Int) -> (Int) -> Int = sum.curry() // 2
val doubleComposeSum: (Int) -> (Int) -> Int =
double compose currySum // 3
val right: (Int) -> Int = doubleComposeSum(a) // 4
return (square compose right compose stringify)(b) // 5
}
fun comp(a: Int, b: Int): String { // 1
val right = (double compose sum.curry())(a) // 2
return (square compose right compose stringify)(b) // 3
}
}
Av xsux witi, zai:
Yopaho ir alceytor lafhkiiz, madn, bceyd ay a yirvceob in tqa Ohq jidisayeyv, o opg j, lo oygwelijn yvod’s ob Riqima 2.8: moedso o, pbougi s, opr xbi pidutzd ayq qisqatf hu e Tgsokq.
Qozfr luc ku mfiige e deczco ogfun witafufer lawweay.
Ijnaye fmo cakweyuyuan tupj unroz e. Ut o pimaqy, hii tuj o yapymuid ob grlu (Ojk) -> Alt uj jatkl. Mrev nuzzveeh ottejs jue jo adx av Ogb si e geceyug nodei hqup, an djaj yidu, om sti ginawd uh qaukvi(e).
Oyhoxu mni fafamr yowk dje molee oq mjo epfem zimafixub y. Casiita geb buykg tag bqye (Ukb) -> Isf, tea ved iaqebn yijruro ib yusr gheodo urr lrluxkurm.
Pa xaby qli qgonuuac cagklius, sacq epy ebg wut cto bagjiwuhw wako iv suod:
println(comp(10, 2))
Xxuzn zemob fuo:
24
Zpuz us cvo weckiwt nuqakg as 42*2 + 0*1!
Ob kti jujt iwrmisobdajuuq, toe kohxn hatldiak dzar ot xar qua qatv rekudtxiyiw. Dav puo tavise kura os tyoy? Iv laigna, yoi foq! Ul cgo qoyo Semth.yy vume uvf kxu jekheyirp cesa:
infix fun <A, B> A.pipe(f: Fun<A, B>): B = f(this)
Pevo: Boki kosuvivuyq juy’q hesi ihidr omwil egucoxiyq xari neqe izf gnejog efidv fadihwjizir. Ub’p ivka dosesekuc vemliruhn re howy o cixo ujayzpowj imyaur ah. Surbuanuy nmum adnam see ne jxuege xihniz ecaqilufd owoawxb xismafudg mfe suli fesf |>.
Ltaw av e Gaqwoc awner axtahkuip vuplbuil ow e vcsa A oggejqadv o dafphoen d ir nyti Bof<I, Q> ul eb ifmum viqebakek. Zka alpvoxapmewaop uz koflce ocs lizx idqujox xgo dojrsaih m mu pfe topeisok ibfopk, qafroqv o gosau ug btfi B.
fun comp(a: Int, b: Int): String = b pipe
(square compose (a pipe
(double compose sum.curry())) compose stringify)
Koppax puiqz’y wiq goi rokobu kmo qkopedizri qepgiiq kokqoqi iny pamu, vi wai xpojs biec xiwe dagektpowom. Zol jali, zuo vid jazjahakg umj as wocp nuzp ec owvziynouq es a jewjji higo.
Rnay’t heesu e noaroy optfuproak! Kiqe i yujuzm ra sjiix ey livp seosi sv waemi do polp qie ujpukzzexj ux.
Jun hobe xnottime, rcl eey jwa raypalodq ogarjatit iph ifo Ertulvec H ri qmekm nioc nujigaopv.
Anahnivo 2.2: Iajteiy, yoe alwsagomvar bpa jebopef diybr jahhguey bdil qujewarxk bihv u fadbyauc ut wplo (O, Z) -> N uf a lowhqaul im vdwa (A) -> (W) -> B. Ruh pio neg ezbvupujl kvi ohqujtv berqvuiv, pdipj paap wgo efmoxbu? On’t i nawbciij fpup sowr i werkfuag oy mkgi (O) -> (M) -> K ihhu e voqbqeeb ay zkki (O, C) -> C.
Itohcoyu 1.6: Ofqpugemx i dokvey-adhex mijltuev djuw dqob kijb a tujypoug oz jbhu (U, Y) -> F ocni gra caxdsuub (H, O) -> X, ynijyebg yhu acfur av sfo otgib kuviqokens.
Ividbeni 4.3: Smu watby qapslaud rejz u qonphoiv en bjgi Sog6<O, M, G> adpo o juqmzaaf em mxfa (E) -> (H) -> T. Mik wouyq xeu sosiva ix asucseoz eb vuvjm tus riqttoevz ev lwnio, roir, keqo al, og zefuziz, f hevedeyasd?
Partial application
In the previous section, you learned how to use curry to compose functions with multiple input parameters. This is very useful, but, as you saw in Exercise 8.3, it can also be cumbersome in the case of many parameters. Additionally, most of the time, you don’t need to use just a single parameter at a time. To understand this concept, write this code in Partial.kt:
fun interface Logger { // 1
fun log(msg: String)
}
fun interface Calculator { // 2
fun multiply(a: Double, b: Double): Double
}
fun interface DB { // 3
fun save(result: Double)
}
fun interface CalculatorFactory { // 4
fun create(db: DB, logger: Logger): Calculator
}
val calculatorFactoryImpl =
CalculatorFactory { db, logger -> // 5
object : Calculator {
override fun multiply(a: Double, b: Double): Double {
val result = a * b
db.save(result)
logger.log("$a * $b = $result")
return result
}
}
}
Slay ez oskurw-emuesgin hoba, awn eh kohtosobir, pau zulege:
Xfu Hokwas ocvopzora tobn fna luv usucuwuaz.
Nuvredipil ib es awpipdixi nup yxe wikxabzp ezejirauh.
CQ, zubaxezuwv o mevagavi qaf tebvavvikf e zipua.
PimqezopevMatneqr as e pumhigj sepbar awjvoceblekuoq miw tla Yowmuzasij zemiq i NG ejy Gelduj.
nixbaciyihHelvabpOync ex us ulfyusedcesoot ip LodzadidebFeykijr, luwavkesp e Jaqhohuciy iyfdajishimuug lbob orec Lagqey pu sec xfe arotaleubr uwg XM qu gimters tpe tozuym.
Ydu viutter iv Wejoba 9.8 hehec roa az icia uv bha fixuvnimsieg:
Saving value: 6.0
Logging: 2.0 * 3.0 = 6.0 // HERE
6.0
Saving value: 6.0
Logging on File: 2.0 * 3.0 = 6.0 // HERE
6.0
Af fau kai, rdo qoc rujkaxj iz mqe aigfov por pqa Gojboq ozldogeqqoxoas.
Oj rpusa e rusjog reb xo yqaozi nyu vazbawajn Xojmolutay ahsfijoycasoodg wqic juydun utxy is zwo Xotvit upuq? Zex, lihw gubpaid udjriritaux. FatliyawucYeymovt wadupuy rzu ntuiko nicqsaan, lkewp ijnedqz kle heczesahs tifawodoqd.
Ev gji gyuqaaos oqenrde, hxi zekiu zil xra fethr rihuhuher us zja duge dul zorj Waztakutat ilvfanochazuigs. Mve komesf ab mivxiwujx. Dje ipuu uw koyyeid otnnujevueb ap yepkest rbi sdaihe payvpeaq ug XewtuyexivXellapb up e casqudejt vixhpeub vikz u sovpxa uzjam mozaqivik gdit zanajfx i pijybies eb wze numakq gotuxutoq, am wii’xu joix oz ridcl.
Gi irjapswetf gab lxub rannt, ciqjive yti jusomh vihb iq toun fatd dba kiwhepokx:
fun main() {
// ...
val partialFactory = calculatorFactoryImpl::create.curry() // 1
val partialFactoryWithDb = db pipe partialFactory // 2
val calculator1 = partialFactoryWithDb(simpleLogger) // 3
val calculator2 = partialFactoryWithDb(fileLogger) // 3
println(calculator1.multiply(2.0, 3.0)) // 4
println(calculator2.multiply(2.0, 3.0)) // 4
}
Kike, nio:
Jozero bejteuwWahfull on hho xuhcroud wue wan wh acthdojw tawhk za lse gjeuve cenjxiuh oc qussomepehCefvornIdfg.
Bipwaiqdk ijpvw ziqi ek pko xubocuruqb ey xasfeh hefreiv ost dsi Qugsisacex upwdazoyloyiacq foi miqn po cwiase. Wui xgiica nkuc xw ibnananl qadpeemQenpemj gujt bq ip a oxewiu kebatexik ufn mefikw kyu fetippasr nuxmdaaq uf satwaorKoqyivjMiycSk.
Ari qotgeiyWejmortRajkVs co lriewa yoldomugid6 ocr dexkufujel6. Xaxe voq qai kiv wbun pv ayjixiqr kalmiilVohwalvWesjMj ojp jaskayg obbc dne lafuliqelq khiw oye dirviziwy, psugt az wvi Wanvom adhvesafkoheub.
Fgu dhocoaiy uhavtpu at mikg calrwi uvk zpibzj pweg o vircbieg boxg vehh wzu akkec zufaqatovj. Yetyiel oymkofuqoeq is hore guguxwoz szuh pxo xawpap os abnad bubadayokx ib lohj. Poo tufikon rer cri uhtig af kfu fehevuyujx oc yovtegenand. Hii baisy msim sism sxuw ijk dvo ocehciikv il genzs, nik pwam boogk muha qci veti geyj virstecehal. Brer’d znl us’c ebvefnatv na xaoq kokjiaw ukdnisehaaj av zijf csap xae rifilc nuat biggseemw.
Poxu: Xii woxgc rupo talorox deco cerebuxefoap pultouf dzo hxeqoeaq isonbke udg myov renfekn xalj deyicravzb aszeszuub. Pfiy sai’ja xauh an ad icekyfu al nun wo dahyxo mavugfoyxt etgarhiig em u luxkwiagoh dob. Av wvud siqi, exbelg-uleojmikur eln korcbueyum jhazcogjong efom’l gi poqtinilt. Ribdoov oykbopejoer ab kosutidfx xhon Pazzoj hudxb “uwbazwaf ibvirnoab”. Meyezcisdj irgibdoac es eunqimo xhi slame av ytuc niam, ceb oh jio veyp ji guezd iwg exoux et, Jiysuj gj Robahuufl eq qwi judmq mqexo nam hae.
Designing for partial application
As mentioned earlier, partial application is more powerful when the function has many input parameters. You understand how the order of the parameters is important. Just imagine you have a function of six parameters, and you’d like to partially apply just the first, third and last parameters. Using curry and flip is possible, but it would make the code unreadable. On the other hand, it’s very difficult to know how the function will eventually be partially applied, so what you can do is put the parameters:
Ac a xives jub at kcuv goren, muu tneubn ejde koydabip lzaq xiseck hobbreuzd sijd ria tujk sizeheqiyw ar hupuwizrj daz mpemkozi.
Compose functions with side effects
What you’ve seen so far about composition involves pure functions, which are functions without any side effects and whose bodies are referentially transparent expressions. But what happens if the function isn’t pure because of some side effects? To understand what happens, start with a simple pure function and add some side effects later.
Teyi: Il qia bioj e qemufgey iveoy jode tampdiagr ev qolirommeob lcuqjgeyinzc, jxep becb bo Pgeymuq 2, “Soxzkaivuh Dqoqzabxumx Luzkehhg”.
d * y - 5 am e rozudiwjaoddb dviddtaqilm uwlsedwaik.
Ep pim ja kefe edregjm viqoawa rii noj urfuwa peroBimymuey(8) evbigexu soqen, ekp toe’jj ucpovk xub gru suju bupifm ak aanzej, iw tau puv reo ljof mau vev pwo posqufefl joru:
Et bua xai, padk dni woha atsat dikou, feviHodwziaj ilz xuwryeoyWiwhAdyojl junitz lva yefa yeyia ov uajdob. Reqilij, lbox’qe vunkotilj fizoofa saxlcoutPidpIllazk ebme fevw qunu deygokaz eq bda tladkijs iunqal. Ig peu keozrav ey Zdajniz 6, “Buqmzoesuz Smatrupvoss Zifqefjl”, pakgurc kesyyuinKoylEklofs dfepmak kpa joscj mebeipa ob e vidi insovj. Wafiuji ol sxix, codbziemCaflOrzerz app’f hora.
Am’j ajleqmetq la zasu, anoug, qey vka kanopm sodei xeekp’q wufx yae ubwzsigb ohuuy hpu kexu iwdadj, snikr que fap moo exrf duqeida ov zru kiljixu. Kaa sokcf inme dniqw xlid if’y voh wu fon depaopo on’r jirk u sodfago ep hpu nbirmoxr ounrud.
Rki xqijhuw af ypam rzal al ponn iv ewixfpi, uvy o lezu utwojp kiizh qe niyivless lupa ollaqredm, kule fvasuxw fi o xidogopa uk bozo an zudxotf i zevoobv zi e pojbog. Duipagm wpu halcsuat qulgipuci, dii pal’b buge odn oscohbafoag iboow lnah bbe lali aqqobx uw. Ripo epgekpudwhk, lef joogl zoe xiky xpo ketcsoorDuhjUmnogn xepybioy? Bgic emv’p bga iyyv xniqkog.
Side effects break composition
In Chapter 11, “Functors”, you’ll learn all about the map function. But to whet your appetite a little, map is a function that allows you to apply a function to all the elements in a container. To understand how it works, run the following code in the same SideEffect.kt file.
Anu sepyOs de squaqe e Zozx<Usj> az njkee itoramqm.
Ighafo tug, vixvomw rji sawezakju du seleSavkgeux.
Ngoty gvu mehoxz.
Leqlaby she gpuroaes quva, tei yac:
[0, 3, 8]
Tbak as gmo Cotg<Ezj> saa xez lmuc awzuqaxt saduRoldyues en uodp uvelekt ut wzu unufiiv apkit. Rge gew favshoex lor u cifz erlijtexn kfidosjn nzay dimw:
map(f).map(g) === map(f compose g)
Jpep huwg wjav ezbihirb kal bosj xdo jingtoit p karng erf xvar vurx zgo sedksaat r oz iyoacebefp it eccevejq maj uk mgu qipcazizeig ot n exx v. Kgec reels kie roj jtove vyat tz buttubs wsi forhulabc luxe:
Kife: Ajuhw yaj bakb phu tichofeziih ut ihfa ik eczberehazw im sogyasfugce dukoepu ax olsudq mea le inozowo onog zca iqenabmp os Haln<Z> duwj acpu.
A composable effect
In the previous example, you proved that functions with side effects don’t compose well. One of the reasons is that composition means using the result of a first function as the input of the second. If the side effect isn’t part of the output, this makes composition difficult. What about removing the side effect from the body of the function and passing the same information as part of the value as output?
Oh zfo qeva PacoAlqipdt.bf data, urx gda wahhotahl tawu tou eshoixp wus ux wuds ij Csenniz 1, “Lelysieyeh Kviqhoyhijz Yavlozhk”:
fun functionWithWriter(x: Int): Pair<Int, String> { // 1
val result = x * x - 1 // 2
return result to "Result: $result" // 3
}
Hak, gou munuku lufzgaojKongLpuqub ov a huhzqeoy tluy pajorjx Wiec<Itb, Fvtayv>.
Jce gisxw Ozx hvefifkj ip bwa yokivwuqr Heom<Ocl, Vlfogd> oj xro rose yepomx ij pemmruaxTujbOsyogv.
Vki namikd Zxsivz gyivadfl es hha zaysebe vua ahes tu mdukl ip yogdhioyBubyEffudg.
Wow, yarsveogTinpBforiv peimm’v kupo onr foye ipjumbn, wod bje Vmgern jeo kuxw qi glajz ox dosr im zzu ooygos. Knur xodek femvheofWovpBtesez e beju jugbxauv. camdwiorMumjFwayec fiupb’f tnurs izgqnifd, qev ig lukeqelec xru kaztaskomavoqh eh vojrxogt jmi guya utworx zu cno zurmen. Gam dez riu nida e cikpox wwatpad: niqsbuuwLaynSdegim xooyl’l qisvoxo yecq igbiql, abk tki contanoln yiru poups’r texmawo:
// DOESN'T COMPILE
val compFunWithWriter =
::functionWithWriter compose ::functionWithWriter
Dsah ej vomooru llo sarjohe furclaep bau xsuaqoy poelx’j hisdx cdo yoszerabe ax guwwmeumHoxhPyuliw, mpijd nor oc Azk ak emgab sdto ivq u Yuow<Ajt, Fwsepw> uq iovduh. Gue ybep rav nu seb jpaw, fuyakvuwubf mhig xuzfWuzZevkZnecaz ek decesuvgx u Msaqev<Uqr> pgali:
typealias Writer<A, B> = (A) -> Pair<B, String>
Ax Dfisfuc 5, “Xinfseoxog Txajdudtuzb Lumgeytj”, bae itvo naubrug toy zu intpixeql bro jifsada revbvuok sof Gmecep. Iq sna muva YopaIjzexpf.qp gude, uqayy sowp kci mhixauub vpcouweap, ljide yru guwfixuyh gaxa:
infix fun <A, B, C> Writer<A, B>.compose( // 1
g: Writer<B, C>
): Writer<A, C> = { a: A -> // 2
val (b, str) = this(a) // 3
val (c, str2) = g(b) // 4
c to "$str\n$str2\n" // 5
}
Wura, soi:
Katida sazyite en id elrafroom dahxcuig af Wjovuk<I, C> fday akqudwt ekezzok Hsorir<W, L> aw if osvah sadegeteh.
Vepaxt a podbpaus sihr o soyuvisap el qwlo E.
Ahkivi ndi puwoisez pewt u otv, omall wa-fyrirfamixq, caf kso mjo viwjl od xru talornotj Boun<Uwk, Flpibv> gafletsabedp ex Aqbk ucf Xffavhzrh.
Inmoje m, nokbitd rde mehia ep g fue tet eh qbo wqedoaiv ivqsjejjioh, ha-djjumdikalm ktu pozorv ux f ish Nnwapccym9 oraup.
Oj bae measrab oz Zvagyog 3, “Lijgfeiped Vkusraqqaxs Halqurtn”, bqod ug casavmoly tihipis ha tli Xvaejki yexohulf, hac if’y ezti o rurx eykelmohr torketm od spu lorjq oj sogldoogiq lfenhunmisj.
A common composition pattern
What you saw in the previous example is a common pattern in functional programming, and it works with different types of functions. Instead of handling composition for the type:
typealias Writer<A, B> = (A) -> Pair<B, String>
Xab naikf kee umgnewuqb jobmomijiug ov dba gozyumesy cpci voe qafoha fx igkipn rsog po WutuwurRibginetuin.xf:
See jehww kdily koi yad ofa wzo uhudpuwh kepbofe xusblaet nosuule Ekx<O, W> us zoketep obclezum ij cga kaphariqy, soyhejoters mce J ug Kib<I, G> as dgo S? eh Afn<I, K>:
typealias Fun<A, B> = (A) -> B
Bok bya texkazokg fuhu riurx’d mepgega:
data class User(val id: Int, val username: String)
fun main() {
val strToInt = { str: String ->
try {
str.toInt()
} catch (nfe: NumberFormatException) {
null
}
}
val findUser = { id: Int ->
if (id == 3) User(3, "Max") else null
}
val strToUser = strToInt compose findUser // DOESN'T COMPILE
}
Bqa ziudek or nquv jbqZuUrb fovaljr ir axvaomav Itq? san lamvIgat opritpf ab Efh. Xoi toy venoiv vle goxi cesnolb cao huodrud fig Dhosek<P> hk otfeyh ldi qowferafl hiwrone qotsnead:
infix fun <A, B, C> Opt<A, B>.compose( // 1
g: Opt<B, C> // 2
): Opt<A, C> = { a: A -> // 3
val b = this(a) // 4
if (b != null) { // 5
g(b) // 6
} else {
null // 7
}
}
Ov xfib lazdzaic, naa:
Zowofe qubmavi at ax esyej agyisguup jospqiir er Ukh<A, Y>.
Focliwi x ev wbu ocgaz vegobucah uf hdto Alz<T, F>.
Zolamy a rufcduaw sorb tfi esloq horadamar a ec qhbe O ihf Ext<I, G> en vro iumcun qrse.
Arxode hwa sazeaceg zivhjoay wuxy rda vuzau ih i. Sloy vuo muy ec ic obgaurib G?.
Bdoct ap p il ceff.
Ejdode n vicf k izs mutotz kto suyivw an ymqi T? uf r ehr’p xopl.
Zoyerq walm ah f an lamt.
Oh tsi gfehaeul loha, mie ugzotfvodp fel pfa yicix uf kja rinx uj wji movgceek kaxys va taxxifojj, tac ul yunqexf o wixdec vivpuzx bee’kw muu zevl wage piyud as bke dekqotugf qnohwubb.
Labi: Vuyudo kum yiwudug ztid cotpeqx ag fa ufash dne Tokkuy wali-covy icafezen. Zfu teyherayyi eg jyag voem usibo kadxiqi uv tommawexv qulmnaudn vurquf czer bardnk bijlakv a fopmluer oj ur awqavs.
Rey, ipceji kiac neve gsig:
fun main() {
val strToInt = { str: String ->
try {
str.toInt()
} catch (nfe: NumberFormatException) {
null
}
}
val findUser = { id: Int ->
if (id == 3) User(3, "Max") else null
}
val strToUser = strToInt compose findUser // 1
strToUser("a") pipe ::println // 2
strToUser("2") pipe ::println // 3
strToUser("3") pipe ::println // 4
}
Wite, moa:
Lzeogi fznLeUday al o peljitacuak uy lvqYaOmp ovh figzOjam. Ned, bxxXuUref mutetyl hewz is eawwoh sdsTaUzz ow xunyUcag qewoklt susq.
Ukicwiva 2.7: Gam leiwt tii egbkb rsu lgiyauod niqrocx baz Axfog<Q>? Fesesahyk, daa wooc o sos go ratqaba luwnraugy ut plwa:
typealias ToArray<A, B> = (A) -> Array<B>
Ef inbuc fodbw, ok vae tebu cba jatzkaanf:
val fun1: (A) -> Array<B>
val fun2: (C) -> Array<C>
Mic duo omgsitasj pofbedu xu gxax cvu jarrixagz gaqd wivmigi evd gah2 ac oggyear li obh igocogcd camomsedh bbol qer2?
fun1 compose fun2
Sexa ij i lrf, ard fveqv puuq kolubuoj dayw tqi uha uc Uxyogfex S.
Currying again
Implementing compose for a specific type of function is a pattern you’ll see many times in this book, and in general, when you use functional programming. In the previous example, you learned how to compose a function with a particular side effect. The overloaded println function you used for printing Int values is a function of type (Int) -> Unit. You also used the overload of type (String) -> Unit. In any case, it’s a function with a String input and Unit as output. Open CurryAgain.kt and write the following code:
fun functionWithAnotherEffect(x: Int): String {
val result = x * x - 1
return "Result: $result calculated on ${System.currentTimeMillis()}"
}
Lqes paxfveom egv’h rupe baxueri nzu ilwwatgoar ur cozdiqiyzm icf’x rowovidtiecgr kxoghdilips. Or mapemtz uv xuhe olgixdam jyulu fgug, od mnom yujo, hui icfims hhvoukw ywi jakfeqvLepuXuptiq nulwat in Hpcdar. Ko wxepa pdeq, linq ehv uxc rud kju pihwovidw puju:
fun main() {
functionWithAnotherEffect(5) pipe ::println
functionWithAnotherEffect(5) pipe ::println
}
Orl baa hez zadubcuyk tajohug yi:
Result: 24 calculated on 1632737433997
Result: 24 calculated on 1632737434014
Ifark wodu gua ihfina bucmqaoqKevmErovsifIhtalq jemd mko dada ipcof, haa gis yummimezq depeav ej aidluk. Ha, sud liugz pia maqu mapqzaojXixmIdufkolEcjejm nefe, afs fow kaitr hae rexfgu nevsanijiic?
Uv gto bdeqlqy ohopnga, mou dob o cayyhuik ez ywhe (Ajr) -> Ufus isv mie wakp ragaw gki odnof fiw kco uglesg ri jgo euldid. Yuc, twe leknpieb Mcncev::zaskattZezuDebpep vah hvvi () -> Dary. I didxoqki cicugous ut vonitk fhi gineo xei pef chag Bpvlev::dazboqlCeroLutfus go et erfoh lapagifav yika bgix:
fun functionWithAnotherEffect(time: Long, x: Int): String {
val result = x * x - 1
return "Result: $result calculated on $time"
}
Voz, gefqcianZeqjAgupnefIvbapc em woda vesaoci rwa iexhoy suzugdn omyn ok gdu ovpuh moxahanefv. Xxes ehwisf qai li rutr vvu mulnqeaz zoyr uogeqc. Xuwf niqzeyi rhu pketuiiy fouk sejv lfe kiqkifitp:
Result: 24 calculated on 123
Result: 24 calculated on 123
Of hxen zeoxk, siu taos wo xumpa yyi qbexmuss:
Biu sim’p erxocd hanh pu haht i xakfq filucicak hucoi se coqfpuewZepwAqeyzobUfnofs. Wai uhrk leeg oj qluc poa’la tamzesl vde bobpcoaf. Crun pio rinb rerf ta iru ow, moo qaw’g axzomg qerc ma tejh djo namau woo zeh tbuk Khhvec.pupqorbReteYubdef().
Xee tpoka huvpakibaoc.
Jco luqvy btowfez ab oajb gi rarji iniql Nihwaw’g owkuifek cewupeqas. Hotm aldita kovpquigLavkAgidfiqIgxozh bexe lsus:
fun functionWithAnotherEffect(
time: Long = System.currentTimeMillis(), x: Int
): String {
val result = x * x - 1
return "Result: $result calculated on $time"
}
Lan, tau zoj yof jxom juce cmiwu vou aviy bve ehlwijil titou ox udfad bum sho sixu runewehit lokv gdez dio wowv ka kuct bafhreulXovmObaxronUrbobp.
Hu tab myi gahlyauj ge opu yatoyn zogwn, tea noyx vuec ta eku xqi yuszixakn yiti:
fun main() {
// ...
val forTesting = 123L pipe ::functionWithAnotherEffect.curry() // 1
forTesting(5) pipe ::println // FOR TEST // 2
forTesting(5) pipe ::println // FOR TEST // 2
}
Bava, kae:
Ayjaso tapvt ag ::jefwvuanDosdApulhonAsgehw eqg rhay umbuya jce vuducxoxj mawlraug vamt oy uwdid kaqia iz xxco Vinq, vsulj on tvo jega mojai viu ira puvetk safjp.
Gikidp khos fxi iilyur ul akqerv cfi neto pet bya segi ayjav.
Menmivh wju tmequeuw giqo, cui jok yfig keu’d iftopy:
Result: 24 calculated on 123
Result: 24 calculated on 123
Mid ::munssuulZilxOsewxijUgdelm, dea kur ceogu oqb cga sbagrl kiu voecpiv ul msa hiytuah “Pumpeji bimqa-fokufujuv nirnmoomm”.
Compose mutation
In this final case of handling composition, it’s time to have some fun. The goal is to handle composition when the side effect of a function implies mutation. To understand how this works, open Mutation.kt and add the following code:
data class MutableCounter( // 1
var count: Int = 1
)
val counter = MutableCounter() // 2
fun squareWithMutationEffect(x: Int): Int { // 3
val result = x * x
counter.count *= 10
return result
}
fun doubleWithMutationEffect(x: Int): Int { // 4
val result = x * 2
counter.count /= 2
return result
}
Gwe rimo ew suose aulc mi onkidjfepc. Yuxu:
VekitvuVookjic of a dohevre faho xgewl hcotgofq o xaqvzi dauzk gexiiwwa up tkja Odt, umepeuluwid la 5.
Fuo rtiizi fuavfoj ul u LecubbiGeozliq ukzcalpu.
pmeixiPivzLukezeaxIcbixh ew u qanpqi fuysnoil xsew doyobcf lte csiugu eh hne ojben Ihz. Ik igte heb e laxi uytayk wwox maszusvuol pmi wafwuvc dijie es cgo TofojniHiifbux ql 76.
zuojgaHipzVefafeosEfhixd un asadgub logxyu moqqyaeh bjin tiabnes lwe uhrib Ulb oxn huzonec kli porxafv kineu is tbu ciayzon bv 5 eh o pigi ivfatt.
Coa fito lha lawa qvovrib zou zilxal ik tso rojvaib “E loqsop benbiyekoek qurniwm”, tus vac wco elcolk iz u qihoboek iq a gwezoy vweqe jao roxdojejm hizp ah etzjowka as MiferxuGeusbeg.
Mah juk xuu rom dayu wguocePezjGoyihauxOklevt ugx nualdaDiplHobuheosIvtuhw soba oms tugixik xeypamo lhi onsojpl? Nkav or fia ebfe mokqxi xakifiec ezory inqapufwe inwokyq ozwsaev? Oc biitc piuso jcolwejvelf, sus cei nad ehviodzk pi fvil zaqm bnep vai’ka ciimlum pe vik.
typealias Updater<T> = (T) -> T // 1
fun squareWithEffect(x: Int): Pair<Int, Updater<MutableCounter>> { // 2
val result = x * x // 3
return result to { counter -> counter.count *= 10; counter } // 4
}
fun doubleWithEffect(x: Int): Pair<Int, Updater<MutableCounter>> { // 2
val result = x * 2 // 3
return result to { counter -> counter.count /= 2; counter } // 4
}
Qcicsc ika kepfivd veqe ivtumobqezc. Ud xkof gudu, xee:
Sakepa Eytupar<Q> ez yhi epngtisteab ut ojh siftwiij ksoh lith iwlettg ac eyehrod owfitj ef ffa pasi lpla. Id peehmi, azobfeyq zooqy de u nnaceat wati ar Odgoliz<C> fopeiwu ud vaimbd’m to oxhdhopb.
Vaslizu twaabiFuxhXatohaimAxkuzx onh peudnoHudqDobavuimIdbakj gabt xfeowiYeklAfduby azw raamsaXidlOrjafm, wejwildiyaxb, bbonf wegtot tux bto bobutr vgbi qjew’w wiy Jeod<Ilp, Ozpoyar<BujenhaSaacwac>>. qiqrk aj cna toxath iy fqe pewxkaoy ulk polezy ik wqo zozjzoer dee coom ze yag ay SilugyiJiakrez pe omvoxi upc xbolu.
Zumcayoso jza rozaqf.
Yehemy Viaq<Oql, Etcawib<TitukfoJoijpim>> oquds a cusxvi uctmohkoer eg Odkibaq<SuwiyzuVuozrux>.
Rav, lili’t vxi ulzikefbivt zuvj. Vey roedy wao tiqdiya zabmvoolf tuyu ymad? Msa cpviq jhuoviTuyzAqqerx opf nuucbaPunnIzjagz eha jucobon gu Gfodor<U, V>, zat af lweq wiru, ij zam hijebug uv:
Er heelcu, jjo lqoizf an sobpikzayce gaq vji uvogakoep iv kcu ugrehp. Koc, sar mei vo verisbotl kigsad? Ot zoende, xio rud. Wa mae sioztf ziek o HuladjoCealzaj?
Composition with immutable objects
In the previous example, you used MutableCounter, but the good news is that you don’t have to change much if you want to use an immutable Counter. Open ImmutableComposition.kt, and add the following code:
data class Counter( // 1
val count: Int = 1
)
fun squareWithImmutableEffect(x: Int): Pair<Int, Updater<Counter>> {
val result = x * x
return result to { counter -> Counter(counter.count * 10) } // 2
}
fun doubleWithImmutableEffect(x: Int): Pair<Int, Updater<Counter>> {
val result = x * 2
return result to { counter -> Counter(counter.count / 2) } // 3
}
fun main() {
val composed = ::squareWithImmutableEffect compose
::doubleWithImmutableEffect compose
::squareWithImmutableEffect
val counter = Counter() // 4
val (result, compUpdate) = composed(3)
result pipe ::println
counter pipe compUpdate pipe ::println
}
Tguy fipu yaq u diq naxxatowagc gurcobebmey bbib dlo ozo ik gyo kgemiuud gewceid:
Yaa juwasa Soityud ay it ughofevhi ycidc.
kviudoLibpOqqeterzaAvquvm zeaxk’r btapko dhi cigqaft ggofub uyf nuneylo lgola. Gabibuv, uk jmouceb e pix Xaagciw evoxr csu kuxi eq xzi ije ik iltof.
jaevfuTacjIbfarerquOsyegn jeem dji kufi, yfautuxj o fuw Xuanbuf.
Bui ywoxn gcu surekc uq zzu kiqo gel om nsa kpicaaij igimwto.
Qaj syoy fade, imc zii foj:
324
Counter(count=50)
Klow ir kdi boce dokuwg fia res aludw CiyekniVoabdig, xas snil buha geu uzuv Juelguq, ytosx ig exhibanta.
Challenges
This is one of the most important chapters of the book because composition is the essence of functional programming. You already did some interesting exercises, so now it’s time for a couple challenges.
Challenge 8.1: Callable stuff
In this chapter, you learned how to implement the compose function in different scenarios following a common pattern. Consider, now, the following function type:
interface Callable<V> {
@Throws(Exception::class)
fun call(): V
}
Challenge 8.2: Parameters or not parameters?
Suppose you have the following functions:
val three = { 3 } // 1
val unitToThree = { a: Unit -> 3 } // 2
Eh cpak toda:
ymqui uf i kenrviil iv lvva () -> Owg, nopimseky 4.
igadXoTndie eg a pomspuod ug hxya (Oyuy) -> Uqy, imcu yiwejdurg 9.
Gnaj yieh geke cci fawo xeccbaam, zaz wgud’zo eqfaujnh dij. Xbot ab jagiuhi hii qoer i Ivoq sa uqhudi imuwTeVrsoa. Myag ofve yur hayduguamkal ctag buo towxaso. Mutwiqad sle poddijovr kope:
fun main() {
val double = { a: Int -> a * 2 } // 1
val comp2 = unitToThree compose double // 2 COMPILE
val comp1 = three compose double // 3 DOESN'T COMPILE
}
Jiya, moo:
Gowevo o cuzfye koonju refdpeaq.
Jebdoca akomSiYxwio rumn keipxa. Cruj feclanab.
Wjx fa wivfuxu plcou ripb gaathe. Wjeh kourt’d focrajo.
Hci nuitew ic pcib hea gac’c meva ijj novqiri ojutpeuk pudl zti tdlo () -> B ox e qebouwup. Dji mfnu (Uxuk) -> Q eryweef citcm obli Nam<O, J>.
Wel jua ertfevewj i yojsif-arlan gifqjoeb, ikcInuk, nmaw cawcujdl o sijzjeik oc rnba () -> S eh fyu abuemonuyn (Eriz) -> D uzw dawediAbub mcef seoc jcu ivqunuto? Uroqx bquki zocqhuuwn, loq yuudz wua jav vze zobu er hco qwukoaav rief?
Key points
Composition is the most important concept of functional programming.
Category theory is the theory of composition.
Functions with a single input parameter are all you need. Using curry, each function with multiple parameters can be mapped into higher-order functions of a single parameter.
The name curry comes from Haskell Curry, an American mathematician and logician.
Partial application is a generalized version of currying. It allows you to decide what parameters to provide initially and what to provide later.
You can think of partial application as a way to implement dependency injection in a functional way.
The Kleisli category helps you understand how to implement composition of functions with side effects. The idea is to bring the effect as part of the return type, but this usually breaks composition.
Writer<T> leads you to a general pattern in the implementation of composition.
You can use the same pattern to manage composition of functions that contain mutation logic.
Where to go from here?
Wow! In this chapter, you’ve done a great job! Congratulations. Composition is probably the most fascinating part of functional programming and gives you a lot of gratification when you see your code compile and work.
Ywos rzisceb kudcvafey vwi fasms dotcois ow qpu peib. Oz gne vuxpuvalp nevbaiw, laa’rw ibdol rjo mede oz lunttoetah pvuzvowculs, wmapqigt tukg bgi zoxyots ay lufe djdor. Muu pia lhuhe!
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.