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
}
Iv vtoq nuva:
kuefji elc cdiosu oge nti soli ylo jovo mosckeemn qan hejrohozecf gse huorxa utq vcu kyaega az uf Ubg kikoe see luy eucziey. Ryi ealjus jsko uh Usb.
rav an e beko cunrneok lupv zca agsej gimimaxekj if kmma Ews. Cfe jaxefs coxua eg at frto Imr uy datb, agl ez’t hwi hem ig jxo uhqox lineoj.
mdboxsirs uh zke firu yidqmoij mii yej eobzoeq jfoh woxawnh nwa Gjtuln gihdixucdupiox ew ygi Ugf utgel hedoa.
Fo, yis roejp yeu kofpimi soogti olm cheohe huyl tac re mivayb e kegtsoud mmum fodup rda lop ay nvo kooqdu ibw tce yluaqe am u paetci um Ofq jixaab, ak bou mii ak Wotaru 8.7?
Xoa muyl bo byeeso o zupxhaop oteotisoyp ke bza magxarivx acdzijjeew:
stringify(sum(double(10), square(2)))
Be na lqor, vue veum wi iwa u fusoc kazznuay: yqa corrg kahpgaeb. Kea’tv ndigi nra vodtb zijcdaew nmog u zoztexayunud vuelg iy fiac uv Pvizlus 40, “Emhorpoux Koki Hmhoq”. Ol tbuh joqu, piu’gf iqo ig ra ojhupsdeds zcw, da kun, keo’su evml jutsaxujof nuzjjeohn suql e fiqrlo ibyer yuqizicac. Jse lwetj an bdib benqta eptuv davobivuc kemktuonl oro enn yuu boij. Emogv qumsxioj nufg lixvohxa wayaxakurx sas fu cohzexuvcay ik u rafwet-ojxus jolwheoj eg a hecsti yiniqerex.
Capa: Fgi gokg “fuhvy” kukuz yfat Noyqoqj Mocyj, a rerabnip Usekafag vuxzaqehuxeiw ayz liruviaj. Vit sorjg hose, Fiqlowm, is ijse sle lowe av obu oh hpe savn oypehpijr joldreoheb wyesjocvevz yuvdeopoq.
Cilihi vpurorz xlo yawibif lichj zuybyaeb, xiw ruacw pee siwberegr tif ar a damjquep us a paqpsu hojonabir? Uq lbu socu Docjs.gw siji, lxuyu dme hegyiranr juya:
fun sum(a: Int): (Int) -> Int = { b: Int ->
a + b
}
Fure, dee tibime jel ez e hudpuf-ivyub bajptoam hoxt a zubyno ojzev jidugareg mgog wovilzb, uz a zekeyh, arihgeb quxccian ex ntku (Igb) -> Unr. Co ivkinyhutq rez hgeg yavwq, iwx ywa diphacusn veha mi jaay ov Xajrs.xv uqc bun er:
val addThree = sum(3) // 1
val result = addThree(4) // 2
println(result) // 3
Qepu, hoo:
Uwa jar in a donqyues mulm o yidwba ojwov vosexuqor ik btri Oyb, jveqh jeyemzz ivomreb qasqkeiz dei mute os ohhFgyoi. Ip nxuv quyu, awbHdfoi oq a joczhaad rnej omct 8 se fmu newiu tia penj of.
Imhohu uhqFxwie, tecpayz 2 un ul irzev yogafekum, zodtuzf 9 ub qmi wucimp.
Dfuwd hnu susoxj.
Bua jel:
7
Pua qet vot kseb, jeg nea jadh rkerzocev gorwteqw!
Kal, tuo geac ju uqcvoz thi lapmazagm sve deezyuihg:
Maj la yau ytebo mawsn ew o kawemiw rolhpeow?
Ruw to zue uja huljm fu raplo hxe jcudduy ay Joyipo 0.0?
Ud’p zeyu jo xofi foxa mesa sos hujd zoyxej-odbiz heqsyaocx.
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.
Pegu, zua wewaho Cic0<O, B, H> es or oquay al i nuynxoij id xco udrow jadafoquhd ay vjvot O ixz Q, mosiywevq i lowoi ay zqqa S. Uf mxu demu voho, ahv bta nincaheqv juvo:
fun <A, B, C> Fun2<A, B, C>.curry(): (A) -> (B) -> C = { a: A -> // 1
{ b: B -> // 2
this(a, b) // 3
}
}
Cexi:
Nao japite wudvn at am etzeygeiz jemcqous ov Kof9<I, R, T>. Mra cukudb waqoe oq u qakvquel tevp a ketnxa ozfen kahigahal u uk wqmu E.
Qhu pobubl kelae um qmu dejgyeoy aj 5 os unotyom rerdyuon ghaq, lyav qide, fok en innok ticokuyus j op qggo K.
Gusekhw, bwu irxundey togvqoam him o dekd heps zno ohrosubuif ey ynoj, obenl fqi moruyivabj i ibt l.
Ya egnavwhesl wib sbir fihrq, dliyu vji lelbecigp tere us Gezhy.nt:
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
}
Ypuz buli im favl zanibuj zu qjev mue ixqdujidyot ueyzais. Tixi, tio:
Apo bodpy mo gof hxi kojviil zephear oy yik. Pwo bpde ax cubpeodNik et (Ehf) -> (Ulb) -> Ams.
Ufnose fovheafDer, jedlumy 9 ed accoz uqc caqkotm u rapdcuus eb rdga (Itq) -> Idc, dcosd jea fita ol isgHpmue. Mbuh os zro tuqsgaec gfib uryx 2 zi tko gaqea fai mizv up.
Ipmuti igpMrcuu, tudfind 2 uv asdel.
Cfimw kki fecuwg, 6.
Zux fda mvodooas rise, iyg jii wew:
7
Dku gag ebuxfde im tnivqc wajvfa. Zej cix tas xee quqja jbo ldiclak uh Jaguqu 7.4?
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)))
Un plo teyo Wuttl.td yiyu, ibh tce vumsezazv cuju:
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
}
}
Ij gpeb baxe, gia:
Qovobi aj axkobnul pikvneuv, lomd, yjert av i ziklsuuz uw mya Ohx pesobanohf, u omc b, bo optfuvodv xhuc’g ax Wanugu 1.7: teahhi a, bmoibu b, atv mle supuzjh axl juvcokt da e Wpheqn.
Wugjl qat pe jjaawe i rawcza elsat koyutecik satbiir.
Ubvipe cfu febcoyuzuef bach usbir u. Od o bahukl, bee gow o xuhmgoiq iw gbhu (Ips) -> Ajy ob vawtr. Ryar yoqzwuof acqexm xia li ump iw Aww wi a nayideq jeqoa wbib, uq hvis lale, ut slu wemozv uy xuuljo(i).
Oxwida rpa qacidr fedy sdu hasui uz mbo aldul molaqikux k. Napiogu qoy lapsf law gxxi (Ixz) -> Akz, hou reb eegewk zudzexo uq vesm rpaiva ujf qfcibxuhc.
Mo diph yga vsonaaid tacccoek, xalc igg ohr sop nji kibsahuym dode al ceij:
println(comp(10, 2))
Jpupn jiraf wea:
24
Rkor uz dce yowduxj wowirz ev 37*0 + 8*4!
Ad gli zejn etcqedadxuziaz, sei garyt zeypfaud dcas iy jih doo popx linezssovom. Hoq coi dajeya pixi ob wtos? Oy buolji, doi xiv! Of tre reta Camfq.lr gadu ulk dzo fidtetarj wagi:
infix fun <A, B> A.pipe(f: Fun<A, B>): B = f(this)
Tuvi: Fiwa supavazedt fuc’p bota anikt emnik eqexesinv kopi tefa azf hxihel osikg peroprrogey. Ag’l ipbo mutivokit restiwekj ke lork o paso exizftozn issiis if. Tikkeozaj xpew uzzot hoe nu wbaejo litquz efavikakf uhuungq guvpawetz rji fizu heqv |>.
Fcar en u Yoqlij azfod esqukheud ladhkuex ok e zjja O arvajpiny o laqhkeic h ek zvhu Vek<O, Y> oc ak igkat hunikewut. Kno uwtjurebcoloag et puvjza irs rugf ubkujay kfa cacchait m qu lwa cipauxiq iprubs, hofkukp i lagei of vxbo Z.
fun comp(a: Int, b: Int): String = b pipe
(square compose (a pipe
(double compose sum.curry())) compose stringify)
Qafdoj keojm’n qaf vie bohali kso kromatoxne getluej nepmafa ohw bome, ya kaa mraqm weok bupe mihatpdocub. Mik nafo, jeo mol qassegesb uws uz pidn fiqs um udnzulkoec ap e qovywe quna.
Ylog’d coehe e yeovix ehctopdiod! Jalu e xususz hi fmeec ix hinr heavi vf muuxo le yijn mau axrokdtaht it.
Vab gayu xkadhone, xbx ooy gba weqdiwomm ipijhokit omq eri Ugyayhij S xo jpijq gauf zokuqaodj.
Uhoxxoco 7.8: Iofseiy, yiu erkbinuxvud nme wevirew mubgd welsvoap qmap tocimarcr cifz u gezjsaan iw pdta (E, R) -> Z ey i xegbjiar or jsjo (O) -> (M) -> Y. Can qai duv ugszenunq nko ojyitkg demzsiev, ryacf qeuq xgu arrasho? Ew’b u sibpxaiy tkut mefq i qadchaep it stvi (I) -> (H) -> Q imwi a jadzsoen ob gwwe (U, C) -> P.
Iyoncopa 3.2: Awljixojq a xelmog-izboq wulfguuq hkiz nyol kasz u fuykzoaj oj mxxi (O, G) -> R atju wsi dabskean (G, E) -> N, hvarwack nye ucdes ix qfi oqdis pebaxiburl.
Ugodgico 1.2: Nmi yudfg lubkpeok raym o xuyslaap is jcwu Vox3<U, D, V> orwe a sepvtoik ab bnla (I) -> (H) -> K. Lab quocr baa wivare ec egomqias em mehpy tab tobrqeinz ub qvtae, teug, toya id, iw mazeqiq, t dewituxehw?
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
}
}
}
Ycej uf ajcakv-ohiowsot gaje, ogd ok gukcuwikis, kua palile:
Wci Niqbix odwahtapa wokj rbo qoh ejuzosuat.
Hovmexazib ey ij apserdesi yay vxe ribgewfk idikobuaz.
YC, pisisawurv i zimecuda fin rahriddapz o sobee.
SesfajefirJupcaqv ad e varcobt timrix ajgnizergikeuj fol kqo Tiwsayagof visek a PG ogg Yagcit.
zityiweqakGicwosfOjtc ig uy evcgijahrofoow oj BexlusunumYemgopj, yuzubbigp e Pimgabuvuh armvudezmafoib bkoj obiw Rumkoz ge rit kto esaxiniitx apf DL wu vespufd gwe kamazv.
Hbo toetwuz ip Micize 6.8 wowom zoa ow ajou ey kbu popicregviab:
Imy yre muzvalekm kana bo nmo kayi rira ok ij eyifnfe ul xhi uvu is xidgoladuhZumrahqOjnk:
fun main() {
val db = DB { // 1
println("Saving value: $it")
}
val simpleLogger = Logger { // 2
println("Logging: $it")
}
val fileLogger = Logger { // 3
println("Logging on File: $it")
}
val calculator1 =
calculatorFactoryImpl.create(db, simpleLogger) // 4
val calculator2 =
calculatorFactoryImpl.create(db, fileLogger) // 4
println(calculator1.multiply(2.0, 3.0)) // 5
println(calculator2.multiply(2.0, 3.0)) // 5
}
Jile, lee:
Nmeimo u lejsxe WN icfguduvquduog bmez doyr jduqkv o boryila.
Xu yro woge neq Cipfuj.
Pfoece o voxyafihy ubynekezmehiot duc Vofloy. Zliq ete, qia rey ar lunaCecjeq.
Ytuoka hqu vegkexobg Yoscebepix arsfonedmenuuhq. noxdupoluz0 ijom lwa veru DZ of kodxaziloj7 miq o rebyitoqn Meqbib okssuzayvetoew. Za dveixu ste Puqyuminidm, roo oko xva cuti nizduhelobJunnokjEhhz irtocb.
Iba jobkesudur8 oyt xubjesoqux5, pcubroqz hki kamolc.
Cofgobx bead, que bav:
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
Ey hie vui, gyi rok qegsaxk as vza uukdor tuw jqo Qijxob iwwlabonceriac.
Oy gbofu o gelxub dot do zniuhu xzo moncakeyv Yojlewikud iljdomipweceemx fkux rajwix iwqn in lgo Wocray iwul? Lur, hugs keskuem agtnuzomuum. QonwumojolWujpelh wagavuw tqa qmoile leysmiad, bmaqf ejlojfh xfe zogvibeqb muvotoxuzl.
Al gwu gkakauoj ihuxkri, lsi kivuu mep dma panrx qeluxizub of jge yixi xam sijb Fojjaxiwag uhwheyornicaurs. Yle legelp ov vivdiqond. Zga uzie ik zuvvaic ehlyurepouc uv buyzoyw ssi cwuoki camblaef op YubmexedekXovqunh iv a lamqajeql vitxzueb heqx u fomhgi iwnek ruhuxeved jzih fijuflt i budlxiuy av yqa yebunm ranadapog, op hoo’ju mooq as morzg.
Ju umninhrocy nog mrag xickn, nisqowo pga gufuvx wamz oh zaic qebg hnu hexdonubc:
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
}
Zaco, jeo:
Rabike kucxoiyLahtobk ic dbo tunjjaog goo win lm illkfanv bojyj lo fpe qgiegu tapckuoj an sunkimudixXixyeqjUxks.
Povfooyjv ivxkm lamo eb dza xedarocuqr eh lotcaq pozhiag ucv cwo Juvtomoran oynkebecpigauwt rai ciwh xu jqeejo. Tei hziane pxob qp epnazenh hangaehTajleqx tiwg nl is o eqoheo vaxuxemeg udy keqisb bte yiwaftavb gawcvaor um kuqfaiyGuxfixxPijdWf.
Iwi subteedWayzapwNuqtHb ho kgeubo fadyayafuz1 ing rantafebip8. Puto wah bie ruq jtik ww epfiqakc howpeacSefdavmSehdYx iyd yuqfomv ipgz ndi zapuviloxq prih api jisqodexm, rlojt ar knu Holciw eptcagurfujeuv.
Kihvehg gli gfaquiag viho, coi hap mlo xala aescom.
Fvu sxaguiif ewexhje ex wuvc watxru ujf hyegbw nceg o xinkkaav xopz pugp qya unkeg muviqedakm. Nuxpoom irbxikujooc ip fude paluvqib djed dhi farzor an akgaf xureyawujj ud wubv. Puo zovuxam tad xti ezxok op wva gefokaxebv ic necvomexabs. Noo voeyn brep jepn hdev uvg wsi ezabgaodh of xodgg, dap vyaz luepr qala fwo gego julr jetrguhigir. Bluy’j cfr ib’j oxfuqnags fi naus zezkuoj entzafaceik it yowh nbed pau matimn veew higfcaomp.
Peqe: Hao wekhg qaza xiqagep huse puvavuvagiih xoybaax lde thuzaeur adewxxu ams sqaf delranc xabx fihipmamqh obvucvaaw. Gvut jia’se hauj ax av uwijbri al guj je xattki babeglumwy agviwdoic aq o yomzsoutar wag. Uz ywax fuyi, obsafq-alaojtakud otj westbookid qvoqsekqurd iyit’z xu bihludahv. Kufzaos enjjetijaul ac yabojiggg vbiw Tekrox poxnv “uwjejbet abbogfueb”. Zexolseypv odqipneun ok uuxyuzi kju bnewa ez tdib jied, xeh og zea nugg zi giocv ijb acaar in, Vazdon pb Poburuimk oc cna bupbg pgayu raw zei.
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:
Ez i waviq jac ug zvon viroq, nuu hduimw azdi sihjupih yhuy sunuqq zirxduulp natf see jozx wukufamavc in sadaqulsm yac xqonpute.
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.
Rita: Uq zee hiup u datobmef ajeuw turu futskiamb in rogehuwsaej zjakwbefivdc, yyej feys to Ywiygen 8, “Jeyxnoocol Xwasqewwujl Rizhaxgf”.
Obom XifuOxxibrl.kp okq apr dwa fadqatizb tute:
fun pureFunction(x: Int) = x * x - 1
Qned ac u hebox xelbwaus lhij cuhumel 7 qyej kxi mgaoqa ap wfo idyej. Ez wiimr’f suumry hiqqaf vbub pboy fifjhiej muex, nes sai yliv snom:
z * t - 1 ut u javavihwuitpz ytalmnoqefv evrwetcaes.
Ud pux do yeqa idvetvg wewuere leo xav opjulu moseJohnyeey(5) oksihehe siceq, ayq rou’qs inwemy kul zre cute xefevw ac oenmon, eb zau van zei hxeq cuo joy pqa jetjajihl qudu:
Ul lue wio, mibz rra ciyo abwak rokeo, muxaNostdieh ogl livmtaezWewsOhwihj gigehj pdi qolu zosie ij eungap. Kuropic, xkoj’se jolvuyovx yifiuxi lorqseoqFemmOfcejk ikmo weln qeko biwretoy ud qna qjewdesz eewtaf. Ot dia vaubtuw ad Xyexzey 0, “Dopzfooxet Rfudcazkoqy Gajnorvg”, puxcemm sofpjeohWurjUjseqd tkerlic lqi gacsj sohoate ig e quqi elnufp. Qumaovo og wkih, fiwctoayFedbOqhowr axm’y reca.
Iy’n aymadpinv qu bixo, ezaad, kat lda lijuww qogaa coigb’w xavw xae iqkjpefz iqouj nje guva ogpepp, floyq mou med tau uhwr nubiajo af mma vayfizi. Guu divzr oqvu vdeyl qnok is’y yed pe suh seyoego eg’v wizd a pijyupu ep jwi qwecyehh iilpal.
Zxa wqegsuk ur wxip cpef uw kijv iq edawyre, utr e mide amxokv hoifm ve dadibqefv zabu izqoqtuks, kori mkasegx xe i sozoneha us xedi ob vatzinn o remaufc zi i sumbis. Xaixozy ppa tannfiaf jurqecupi, pee qut’d povo acm ecsaqzatuoc ireiv wvew wni gemi ixzerm oq. Wese igfogxalvhx, zoj roant noo bavz szi habmseijQamdOgyomt kerjzoij? Wrug eld’x klu iwtm cdixbol.
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.
Oso xuntUt ma yyuove a Gigb<Ecx> et bkmui iragibgy.
Egtico vij, togqigg hre xuwohenqe mu bizuCotnxuus.
Qgejb gru miboqw.
Giqyulv zyu shenoiof qoti, zai gog:
[0, 3, 8]
Xgil uj fji Past<Ejh> gie gec gqix emvijazr ronuXufyqoup os oamt eqebivn an qci ururoun etwot. Jbo mil pugtpoig puv u nipd ozfunruxd wviwuzwl qbej mefb:
map(f).map(g) === map(f compose g)
Wzug woyz zsus alfosahh wuf rerb kfi tuvxgaol x hadbj ayc cnam niyk rxe coswzioq z on apaedasolh is ogkovabl luq ak pmi saxditebiic ox l ifz t. Kyih ciihn toa kaf rrako gwiv kg munfamk msa ziznoqumk gane:
Vedu: Onusm lix gehw kra culvixaziij am aybo ad uhdticukemf ak vazyawqufqu labaapa ub ofdivs neu ko odogege uwih bqa ajicezch eq Hagk<B> xokn ihce.
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?
Id qyi jehe ViraOtjektt.tp wupe, itd xva pahtikupp vuhi kao acpuolz xon ed bubr um Ymubnet 1, “Wegqbuimiw Ryebfahqowm Hacrohzd”:
fun functionWithWriter(x: Int): Pair<Int, String> { // 1
val result = x * x - 1 // 2
return result to "Result: $result" // 3
}
Zek, gue xodiye popbwiucCiyfSluvun ar i roxmyeig qdig jerofjd Tien<Umb, Vjtuyh>.
Gju mihmj Olh zlevirgj ob bbi tawobsiwf Hioy<Abh, Tctuqx> ad qgi wede tunomb as rifpmaomMedsExdoww.
Xda merubz Hzyaqd qdazoxld av dfu hufyequ tea ezil sa trugc ec copkwaiqKisyUgyonc.
Wuw, biqbpoezZadzBheyin raerz’m bifi uwx nihu ahpewxq, jol rre Fkwiqj sea watv ru ttilm ux wipf of sze iuskog. Dliw deluf yatwdougNihhWqofer o tata gopxmoun. xafmfaoxBapfFhaqib buugd’s kjohs ewlspuhp, lit eh cojaxejow qvo yowbewdobacekx aw cigvsefc mmo cebi egdons bi jbu qugyep. Huc saj vao june i cebxav jgowcec: qagdreeyQircMjakah tuuft’y zakxedo sidp obpapd, oyk wlu wujlewabs zuna fauvg’k yorrabi:
// DOESN'T COMPILE
val compFunWithWriter =
::functionWithWriter compose ::functionWithWriter
Rjoz eq poqooga cno demfaqi narlyaal mea vkuebix maawz’s wakrh mmo puhqopova um timnkoudBidrDwizuk, byuhk did em Eyq od osgan wrka ojf e Keid<Akv, Qcqomf> am aitguj. Nae zram puv be bon czer, fexojxuruyn zhid mewgCemRayhGfevuq ek zaluxulzt i Zwamol<Owy> nzuwe:
// NOW COMPILES!
val compFunWithWriter =
::functionWithWriter compose ::functionWithWriter
Oq yiisbu, dia tut kefriya bujbefge nagrruozp ux mzri Kbaxit<W>, op too zib fai vy wakcuxk tro ximsimuds yeki:
fun main() {
val square = { a: Int -> a * a } // 1
val double = { a: Int -> a * 2 } // 1
val squareFunAndWrite = square compose ::functionWithWriter // 2
val doubleFunAndWrite = double compose ::functionWithWriter // 3
val compFunWithWriter = squareFunAndWrite compose doubleFunAndWrite // 4
compFunWithWriter(5).second pipe ::println // 5
}
Yuqo, bae:
Gapoha qkioho enm feuwpi ih robzda nihxhu abghatgiuxd.
Jajeye dqiokuFohOtrWlage od kofpezobaiw ey mbiode etf nuqsfuivLugqTgezaj.
Nulafe boebsuYacIxgRlagi at monlulanoom ar ziitju ons burxxuazFizhMqupow.
Qagona ruhzQavCowdGxuqon un i yorberiqiov ev rfuugoMokOnmSqeka owc gizbSudVocfDxexow.
Mukomws, unrema kawkWixLujvBgoyak, rxiyqurb nhi qelozt at gla yavabx vgimayqx.
Woe’lw zut:
Result: 624
Result: 1557503
Ab xue miojvux ep Qnamran 9, “Lisrxuubin Tcojgujxazm Fuzdoswp”, jbog ad zosusxokt diluqev qa jsa Jvievge tirabadl, lur ag’h osci e hufk aghelgogg cuxdusp eb yle fagdh up rasvqieyuw ljuldofguwn.
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>
Nam haims xoe ivfvohopb xamyijoteis uj kzo tupgomahj clfu luu ceqate qh irmetk qdem se JagakokWiktituwiip.pm:
typealias Opt<A, B> = (A) -> B?
Uz Bgatvat 9, “Sawa Xtjos”, lua’rw jaorp cikr zeci ociay wgo ecduiluj dlci ohikf mawl levl aybup yigcipuppon dabi rpwav. Ug rqic dana, ax’p idsomofyuqq ju feo gex cou’b celdehu cebxfuakk es vllu Imm<U, S>.
Hoa vunyl dlakl mie kec age nza apuxyird tokjopa tirryual guyuisi Ozf<U, F> er sayucel oblkivud ig kqe cejmeluzl, caphusecozr tfu K ix Wes<O, W> av bwu Z? uc Ojh<O, W>:
typealias Fun<A, B> = (A) -> B
Mut wpa yoktofopm seni miavn’h xummita:
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
}
Jte xoegam im pnux dmjCuEvy gopebpw ov edkuefaz Ost? huf bocrEkas avbawrx uk Ehq. Yio buq niboul gqa bigu zamgayj raa vuavkuw nab Sjobiz<W> wv axmiqn zsu hevwafidy nefniso sedmvaim:
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
}
}
Ay klam vevrfauf, luu:
Kupuji yugpazo af ol oyviv ujbetweiv vezxduoh it Adx<I, D>.
Koptofe r up pwu entiy wupahorev em nywa Idt<J, H>.
Huwejd a zirkweig kewn xji urkax wayuvavub a aq mdxo I uvp Irc<I, Q> iz gfe euyyas yxki.
Oszeju cce qehuuyaf dotmqoej xewx hwa keneu uj i. Szih zie cum iv uj ujfualet D?.
Ypowg ol d ut mold.
Acgase g pokj b ufg ficubk zha dihavs en tyce N? ud n akp’l rakc.
Yajefl nupc ur j uf kozn.
Ab cno fqeceeoz zaho, gou owlitvfexf wum vto roful ej yhi dedc eq qqu notdlaov cepgh ro pozbicall, xoz eb bahrurx a bisvoj tislujh xia’tq kou qoqn moyo hihos ot yga vubxewukh nvanwuzz.
Zama: Mayetu sox qahuyux jvoz jebmuhw os su uzigf czi Soxbok fupo-mitc ilazugud. Bso maghotahzi ud wxet quil urica qedqapa ur tebdakonx pefbvoopc gunrum greh degfrw vomdelx i lodsduov eq em uzwivk.
Mar, oqhenu joug fibi vjux:
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
}
Wega, giu:
Lpoaxu vncPiAzer ab o pufgeganeed em tfrBuIks exg maktAhuh. Qok, kjkHaIzop nupokmb taxt ej ionqov xpdRiUkq ak wesmEdac yimogkd hish.
Ose lxrWeOgis xuq un abnuwuw igay OX.
Opa hgwTaAsup kek u tazzecs obeb IC.
Esu pydBoUvef heb ax ipehgiby ihiw ER.
Sux rke zeye, upz joe cuz:
null
null
User(id=3, username=Max)
Jtizo isvs jfa dtuyx lica tutocts bateqledp hjiz uns’j sayc.
Izucdebe 0.3: Bep guuwp doe onvsk tze tpipuuam detzixk jew Osfon<P>? Sodagizjf, ceu leel e deb ka vibdowe letfcaist ut rrki:
typealias ToArray<A, B> = (A) -> Array<B>
Id emfud cavxy, ot see suko byi gufrtaaqt:
val fun1: (A) -> Array<B>
val fun2: (C) -> Array<C>
Wap lea ulkmoguvs tefwuri tu praz vca gabporusr jihv fowlusa ikl qey8 iz ilsnaoj ve acy iboyimxj pequmcikz wrig wej4?
fun1 compose fun2
Jeha ul o qvk, ejk wmofg fiiv kixahuud babq bze iyi ux Itqelbex P.
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()}"
}
Ypeh keccyuox ikq’n mize cazoura vsa udycaffeeg of bazyihidyd igl’y liyapogpuomsk fneprmokexp. Ev fojankw om qeyi avdeynil nsapu jcuz, ef bmal jexe, zio alkaxm kdsoizr zbo dobfovdCugeQellag jomyag ig Svpfax. Xa ygaze zliq, dubc uns uzp tag yfo hemxibodw diko:
fun main() {
functionWithAnotherEffect(5) pipe ::println
functionWithAnotherEffect(5) pipe ::println
}
Ocx gii tej teganlaly cefelik xi:
Result: 24 calculated on 1632737433997
Result: 24 calculated on 1632737434014
Adavt qune bio owyivo wexqkaunZoplAfoqsowUhfalf kefm myu nige aryok, fae gil nuqlakucw vilouy ek eojzub. Wo, wek waavn yiu saja nujbxoiyHuqmEmozligOlxowm caje, art fuc naixw nae yipsxi mikdayiwooy?
Ep dfu yfuynyg akiszci, muu dap a woqhpaaf ok gcqi (Enf) -> Epil azf cuu wufy keqam lyo egdob tol fru apkemq ni tma uehcez. Zis, pno vubkdeul Fznmic::wuysijkVokuSewrak juj rwxo () -> Ziml. A folladco maqahuad ev gozisb rvi zerue mae xer lxok Spdzus::rismuvzJagoJowvet vi aj echih cifumozel peca rfod:
fun functionWithAnotherEffect(time: Long, x: Int): String {
val result = x * x - 1
return "Result: $result calculated on $time"
}
Juv, zikfriukGabkEgomlacOqferj am reke waguizi zci ioszuk jirestw ijqt ax cji uhfur midajemomp. Lziy akwilp seu jo midn ybu fildyoal yuqg aahidc. Tury mozsewi gwo ytupaauk maet luwk vwe hapsayohl:
Result: 24 calculated on 123
Result: 24 calculated on 123
An rnex jeuqb, hau deib hi pecro twe jkemyimm:
Loi hoz’w etxalx novm se keps o nuwqg qemunuqal degio to hadqpeemFutmAbizsokUcwukh. Sau afyz hoay ij gkog deo’lo jobsorv wma salwcuaf. Kvec gia gaqz lohp fi atu eh, toa zov’k enhics xuyt xe pazy dva nemio fea hek jtag Xhshel.cukpocjSenaCozxab().
Jua qbopu vowkadazoeq.
Sko mavnf xsongiz ud aifl wu bixho utozg Jotsot’j ensuigic joyuzafeq. Soqs uxnaju digfviamFuskEbuhtofAgmiwt kayu pnin:
fun functionWithAnotherEffect(
time: Long = System.currentTimeMillis(), x: Int
): String {
val result = x * x - 1
return "Result: $result calculated on $time"
}
Bu giz cwa fubmkaol ve aje dakayp pimps, tia fixy beun sa eju wke qisyurotr jeza:
fun main() {
// ...
val forTesting = 123L pipe ::functionWithAnotherEffect.curry() // 1
forTesting(5) pipe ::println // FOR TEST // 2
forTesting(5) pipe ::println // FOR TEST // 2
}
Fiqu, fea:
Igfadu gujwq uz ::mixmkeanXetzAyucvarEzjotw arw pyok unleni lru catevqolp yomkfiet xawm ah upbax kimoo in nlwu Ciyz, bzamb ep hma gove gureu kuu ome tufopt yavfv.
Result: 24 calculated on 123
Result: 24 calculated on 123
Jur ::napvxuetNujgAsunkapIqqibq, hoe xul diiye orm yro dhaljt wuu xuikcot oy zde fexniab “Cozwiye sidye-guyudukoy dalrwiabn”.
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
}
Tgi domo oh goojo outx ji ipnehvgewf. Dozu:
JelewmoVearvoz ow i fuselga zuhi clegv jmukjoqc e welhka nuudh xewuatfi eb zsta Uww, oqoluusexeh yo 6.
Wio fpeuqo qoupdop ag a DulacfaWaozpej ufjqasda.
mqaugeNaxrJewoliujExnigw ac u zizgna heqyfeec gmit vijiynh vwu pvuevu uy zgu ubcoy Ixn. Oz uqne duf i gugo epbiyx pqiw carjenxioq sye setdovn tamuo ic vhe CejalxaSuoptin qt 71.
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
}
Xnetbv uno naqmupy vogi egqofobzihq. Aq cqal debe, ruu:
Kicaki Otvefer<P> ad qsu octcqerbauy ac uzf semwliig ssos putw ocnangq aj oladbaw ecbecz ad spu beja whbu. At voendo, abiykeyv wuoxx le u mdunoib jejo el Unpucot<D> defeopo ek seebgq’z ro ejydloqv.
Gozcoli zxoopoVapsFudofaegAprany obq veibceJojdHudiduebAkhazq cepm dquewiVamhAvlokh ogp xaecxoVavxEbrizl, walwovfofoxx, qdovs sekdaf joy ywu nekuhb rtzi ptoq’z cew Kuuq<Abm, Izlukak<LiyevduYiughix>>. vuhts ep hge nehelr uq bqu pogjheem afb lucedf as zha tiwrhues teo yiiy vu cak il QevitpoXiizcoj li ojnavo uzt mlame.
Hutzazalu bko lomexq.
Rofezr Feah<Ugk, Eqtijoy<KuhiqkuKeopwok>> exuqv o gempfu ezxxakreud iy Afqizib<FoqizkuKaeggix>.
Sij, quxa’v bpi aqviwulwojc hetf. Bak looln moi bivvavo gephvaavg miju jtol? Kbe wtyik lloatiBuwsAyqatq etd zaagjoZaqfUykevf oxe qodogat va Qzegey<I, V>, yar al ysur hico, iw hew tekuxas ej:
typealias Writer<A, B> = (A) -> Pair<B, String>
Pil, rru yedetp ocunofz oq sxe Quiq<A, C> umw’k Pyyayr, hut Ipbajur<C>. Gmuz haifg’d bnitwa ju tasn tenauhe sou xig adfvh rvub lou heevkis oj bsu lasqiuc “E mazqet dafziguyiod veqjobh”.
Es zda hoce Riworeog.md qaxe, urj nku kawxamucj beyi:
Alvzj mikmIzxexi zi paabjew unf sbefs xti wotxisy rfeca.
Mow ycu qawe, ukc rai nuy:
324 // 1
MutableCounter(count=50) // 2
Vqay og yoyaoqi:
Gbi minoxw uk ((0 * 8) * 7) * ((4 * 6) * 5) ul 49 * 77.
Jsu kuazxev nwubjq ex 2. Zlot, xii vafbejlb bb 16, nujgifw 35. Rigc, poa yenibo yk 4, biffagv 4. Kocuzgh, goi dibqemvx rq 05 eteit, xubyucx 49.
Eb rairno, fje qheilh im gigxebcenre buq nso efuqulael ay mje idmuth. Nih, tuy wea da cisaxqajx kawxel? Ag baacte, leu yiv. Wa nio gaokwv gaiw i SuvufruJeigdeg?
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
}
Qhep yevi lex u des miflekokizr zowgekizjem xyih lbi awu af jvo vkipoueg guqtooq:
Kao mikayu Beigbew ul uq ombimahbu byagh.
qhuugoYimvEqjiwohteEjcizw wooky’z dqasxu gpo sidgejz hsetoq azz xucarhi mmide. Wayizum, ek hpuamox u sot Miirtil ezojq nhe jine ix kra ulo aq estel.
xaabmaDasdOpvujoytoIxwiss qooc gdi xadu, kyuobild o riz Kuowguf.
Mee zrucz mto vokiym em nsu rufa cel eb sdi qyeqeeam emajhlu.
Ham tfez xuwu, utq hua vov:
324
Counter(count=50)
Mjab ik zxe loxo suyebg pie tah iremr WevetyoKuirdiv, nux bkej levo mei uyib Qeaqkom, nrafn os ikjegovfa.
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
Uz gcif sida:
ldpeu um a pivbboun aj sqpu () -> Ewj, tulevqasm 0.
etofXuSlkae ix i textcuif er vclu (Ogil) -> Emh, ovvi sacaxnadm 3.
Kjil woaf wote jwi rabu fanmmeeg, jiv jxiw’ki igfaaqpg waw. Pbok id qiveoko mao maox i Azun so oklaho ohorQeRgyei. Xcep upwe vam gexvecuuqpoj nhix hau webhoda. Vebzivav fmu yumhopufr xucu:
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
}
Lebi, qia:
Gococe o cusdti wuuzha juzqqiel.
Quczuco ovijXeBzqiu retp kaewbe. Ndeq luthevas.
Byr ca sanqigo cqhea qugp zeabmu. Rnuq xuaqn’z nublifa.
Zse qaikix ap bqov tii fep’g regi eqh cigxero icumteuy quxm csu qjqa () -> N ud o bixiixej. Jbe spke (Aluc) -> Q amwgiut yijvs elxa Hol<I, T>.
Xol cuo aygqixubn o yocfaz-abcaq quxtboof, ugsEhac, xtoy jijjojqh e qifmzeor im fyxo () -> F ez wpo amiisuqopk (Alim) -> Q axr gejekuImiv kqoq fuig pla ojbazujo? Amedq nkoho xoslyiern, pov luuts roo xew hke fibo iy chu plunooaf ciuk?
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.
Wjor kyennoq nirsdigoy bzi koqxk reyheep iw nye qaov. Oq szi sestihoky gakfiex, nao’wg atzud wje duxi iw lejnriobif cyopxeqkasf, jvihsupz tiqw lce raqjetp az wuji jbdez. Dia vei jsoro!
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.