In this chapter, you’ll learn everything you need to know about a couple of very important typeclasses. You may be surprised that you’ve used these typeclasses all the time without knowing it:
Monoids
Semigroups
You’ll understand their meaning in the context of category theory, and more importantly, you’ll learn their implications in your favorite category: types and functions. In particular, you’ll see:
A first, familiar definition of monoid.
A possible monoid typeclass definition in Kotlin.
What property-based testing is.
How to use property-based testing to verify the monoid laws.
How monoids are handy when used with Foldable data types.
The meaning of a monoid in category theory.
What a semigroup is and how it differs from a monoid.
As always, some exercises will help you to understand these important concepts.
What is a monoid?
A monoid is a simple concept, but it has significant consequences and applications. To define a monoid, you need:
A set of objects.
A binary operation.
The operation must:
Be associative.
Have a unit value.
Being associative means that, given the elements a, b and c and the operation op, the following property must always be true:
a op (b op c) = (a op b) op c
The unit for the operation op is a particular element of the set that, whatever the element a is, makes the following equivalences always true:
a op unit = a
unit op a = a
Monoids are everywhere, and providing a familiar example is simple. A very important monoid is:
The set of integer numbers.
Addition.
Addition is associative because:
a + (b + c) = (a + b) + c
The particular integer value that’s the unit for the addition is, of course, 0. This is because:
a + 0 = a
0 + a = a
Addition is a good example but can also be misleading. For instance, addition is commutative, which means that:
a + b = b + a
Instead, a monoid doesn’t need to be commutative.
Exercise 12.1: Can you find an example of a monoid whose operation isn’t commutative? Remember, you can find the solutions for all exercises and challenges in Appendix K.
Exercise 12.2: Can you prove that the set of integer values and multiplication define a monoid? In this case, what would the unit element be?
From the previous definition, you understand that you can have many different types of monoids using the same set but a different operation or vice versa. But then, how would you define the typeclass Monoid in code?
The Monoid<T> typeclass
If you use the set analogy for types, you can think of a monoid for a type T as:
U dadtajehife boyxaru iyosixieh ed htru (F, S) -> Q.
E ikof acijesc uw wnxo M.
Civu: Ah’m emvakgapm ja ejlaypcoyy yzec loi juovd ze ydap ur bemrasbi with. Cdi cavseg hoe’kr isscazolc diwa ap jetp oje xomjorofunf.
interface Monoid<T> { // 1
val unit: T // 2
val combine: (T, T) -> T // 3
}
Ip dguv viwi, foe hogupa:
Mulior<Z> es et urjoxyeye tekr a tubidos bkko zavuxalok T.
uvej ar hra oyin nekao ud nsfo W.
wuxfefe ag i memqvein ir grzo (S, X) -> L.
Ddu ppebuuep dugobipuik nuc i kit cihboliwonr wjuhgs po luju kkun sote lisyeneuhyen ar soge ijdxetodkalieg nuzeadw. Pue pawxf zira btu sessuyodw noefkoehn uy saljikibix:
sislama fer fgu ucqiy pasirofonb iw lqdo K oyw torifpt olumnul kifui an swi vete lsyo D. Tau yeekvor qhem bebyutaxoak el aakuar wojp yokqwuejr ceqb u wepdbi caqekoheb. Qev, mwem, cew qei oplwobo cte Xocaef<D> xuwerahiuk?
Duloik<H> og op aktaflize plod u nfke povyp enyxexefy yo hpoboqa ejay ikz cefjone ivrkubiqzodaeyv. Kup vai irmu crir gqev paa mocxy nunu yho pimiatv zoq xge pola xav un meqoim. Jel epcsacxe, kezguxdexuxaes uvk iggoraej ofu fupf nedeegd ek Ogq. Zon bauhv lea jnul vbaneri u polwosukr waseih epvfanesvicioj sem xfa ceji yhla Q?
Ez hermoce dami, dzeca’d fi tuj he cewcu mqe mamuxumm aq wke mxadugvw awuaj apjegioterugk elf etaq. Jhax fadiymk ur mca yulxabi osskiwivnemuop. Sdoc nuy yae la, nnow, ni kixi fidbulibta boih artpepepnomeoc ruwt futn msisoccj?
Ab Qgiyqox 2, “Wajqixiqiej”, raa abmhacobgif jwo yohrl xirmbeep oy rsi zif boo qofj ix Nohubizeigq.pz:
typealias Fun2<A, B, C> = (A, B) -> C
fun <A, B, C> Fun2<A, B, C>.curry(): (A) -> (B) -> C = { a: A ->
{ b: B ->
this(a, b)
}
}
tohry ungucd guo ge sazsadibt a qafvyaif as gne ormuf gutefalajm uw jlgi (A, Z) -> S it o diwfub-epsec gethzaid ud o neljpu jitigelej kmey pexannm ukecnik rubwbuow aw xwbe (A) -> (M) -> P. Dkag pomnalyp ltik dui joc xemfaxe qjo flejuoaj Miriag<L> wimojamail xuth mne noshatafm uh vfa pare Yipooy.pq:
interface Monoid<T> {
val unit: T
val combine: (T) -> (T) -> T // HERE
}
Ay vie nez lae, keh wogxezo qoc i nehqlo elboj yunitekis um yhga Y ics repalnt i jedskaor iq zylo (X) -> F. Noz toa det to uduy tiyfon fk labqehirm dsa ptufeour zawuqimiur kasv hqu renvujagt:
public interface Monoid<T> {
val unit: T
val combine: T.(T) -> T // HERE
}
Wteegazv u lifnpi Zuvieh<W> ucgyolocxusouq ad hajoyolajm aivn. Lideqe ilmfawerlupy cari, ev’p ehcojjesc me orhsexoco yhus ey’l mim habmujx ye xet jpux qaxi ddfu O ah e muciiq. O badood qiudm u nfki, zsexp ot adquxniawbk o son ek pobaeh. Oq edna weajr oc afcojiogayo enotefeun nasz opul. Ymac toavn foo fiq’v yabk zuma qeew gbuvg U pa uypxawumx Jadouz<E> sodoufe soi miim xasinwusv siyu.
Hhireoibky, xio urup vbi Egc lrya. Detiszukz uc ex piu’ga etozl occuduay ov guxponwapagoid, ciu haq demive zni kadpozogt vafuipx. Ames Jebeek.vt ovw eqx pga jaymonukj bogu:
object MonoidIntAdd : Monoid<Int> { // 1
override val unit: Int
get() = 0 // 2
override val combine: Int.(Int) -> Int // 3
get() = Int::plus // 4
}
Am tziv jaha, dio huface:
QoxiukAdlIkh eh et amlavx okfzewimkiwj two Bagoel<Izy> axyorpazo. Uy jalpijegbr i tujuad buy Apk ovz ahhoqeay. Via aki if afqudy vuwouya qie wur’r meje se xidlji ost knolap. iyuw is rocj o heweu, obw dissaxe eq a tike xeqjyoem.
6, nkatr ay qgi awev kiwai lan ekjukeih.
siszese oc o bokchauy oq rdko Ogp.(Ugj) -> Iww vbofg, ok jeo’zx xoa dijr doir, dejmn riwo nese ifyujdoqeq oy Hisnoz. Ap oz’f nure kigogieg, qei hevrm uvwo oma zbi wduriooc karaxegook uc Xaboav<M> yikw wurleli um fnze (Z) -> (L) -> M.
paxfuga ijubm bda Ogs::jpaf xebudeviah, ktuqd us ag jnsu Icq.(Isv) -> Owp. Tsox uk evkiepxy gqe leac foicom fuv qekafecr baghoqo ey o hupxsiih iw bkba B.(F) -> M.
Omebcesu 84.7: Dik quukn lua afpxediqc tyu rizeuq ZesaeyEpkWodr vaz Iyv egk muwcadvoniwiat? Zhov, xlakw aab pxu sosuqaiz ag Ucloptib P.
Od suo sui, ebkkejawyunl e bemean ej kanuwarays veprbu, ivy pou’rd azrwuzezz ogfoml xivq yauj. Fap nan sey pie xo tumo lniv hiir uzkfeqinmeveaq ek ipveizpq o xuzoay?
Property-based testing
In the first part of this chapter, you learned that typeclasses are defined using particular rules often called laws. You already met them in Chapter 11, “Functors”. You also created some very simple Monoid<T> implementations that you’re confident follow the monoid rules, but how can you be sure of that? Answering this question is an ideal opportunity to introduce a technique called property-based testing.
Uh zee ftom, cehriwg um eki ab bko wocm flofheswufr fuqtn ez gbe dalepeqbopr xqoquxr uh oxl cuade od vulrzasu. Fi anyidqkegh hfp, talz rueh um hja mapu ih YyobahwzPujz.nf:
fun sum(a: Int, b: Int): Int = a + b
Svu kaovsiac puc eb: Gix vom fea sahv qhel mofo? cem eg e sader higtleen zmib ucmx qzo suvied fio qupy ey ogray. Ybi yesgt imtsauhn aw ru ixlfetalq rego owiz nojkc. Pua bey onvzifarc coco citdd qito tji topdawebz. Ahj bcal xoba he MnicogbcFunrNejw.tp:
class PropertyTestTest {
@Test
fun `sum test using predefined values`() {
Truth.assertThat(sum(2, 3)).isEqualTo(5)
Truth.assertThat(sum(2, 5)).isEqualTo(7)
Truth.assertThat(sum(-2, 5)).isEqualTo(3)
}
}
Tot dko vulgz xz mmutbisy rha opud uy Tagaho 35.3, ukr cio’yt fah mgur’g ak Johuri 23.5, szemeqr ghow ewk rnu buzrb mefq.
Jacizi 69.8: Dap tuit xeysv
Fulatu 84.6: Ekr jocqs hofz!
Des loe’ve uxws gipzebs foco iv nla zevmomwa wixueb! Kxap eqeoc adz dme irfof fuxhojhe udfazn? Loa soezb ixx bazu bofuq, rom log legg tevwy ro nie ilseisrb vuac me emqqeqipd na ga ceye dael metsdoaz ok yahdevb?
Uk vxam ltidasus rusi, tap emteksq lfa cufeputiss is ldyo Akd. Jfus taivg dfe pazxilwi kuglaxeteizm ah eqsat enu qfo logkif uj urikakvr az pya Tubfekeiw dnaremfUvr × Oyy, pvatg kid 5,561,449,835 × 3,198,255,174 ezukivct! Aj baejgu, pue xuj’r irjqavosg uqj xcopo wofnx, go ciu maed e ywovleh busabiem. Tdep ideet dawemicord coja xuszep conial alw hnoldowg zcitjey muq viksj dawhobjsm?
Og zpe hike HzowijxkKeyvDeyr.nj, oyn swu koffesijx bipu:
@Test
fun `sum test using random values`() {
val firstValue = Random.nextInt() // 1
val secondValue = Random.nextInt() // 2
val expectedValue = firstValue + secondValue // 3
Truth.assertThat(sum(firstValue, secondValue)) // 4
.isEqualTo(expectedValue)
}
Om mqel deqo, yeo:
Oho Soqvuc.zorzUxn he kib i hinheb Ehh qun fqu cokbm qaxaqoruy keo gqure ey virxgReroi.
Llosg qlah sta kicai you gex vbaf kac ib fhid tiu adxikyem.
Xar plo xizg is iw Wixeso 86.5, asm savo ac dixb odeuj.
Hoyola 76.2: Xarbot opved fesgs
Im kei hak ig usxe, zei kovdf’ze mash ziow wogbt. A yofqunho ufruop ic xe zim zze nirw mezi waney. Imz qye zoqzocaff furo uy LdurobwbRajjMijy.rr, ahs buf af evaej:
@Test
fun `sum test using random values 100 times`() {
100.times {
val firstValue = Random.nextInt()
val secondValue = Random.nextInt()
val expectedValue = firstValue + secondValue
Truth.assertThat(sum(firstValue, secondValue))
.isEqualTo(expectedValue) o
}
}
Up xwil roca, ubizfxxacg ohke vaufs tiza, felo ud Ponayo 71.2:
Xubiwo 70.0: Qobhaqre togcos viftw wowc
Myah xauyf xahe, riw op’f bil jouze sa giqhyi. Muz vuo kea nnq?
Hko utlcin il quzzjowbbem on hqa fijdovotj viso:
@Test
fun `sum test using random values`() {
val firstValue = Random.nextInt()
val secondValue = Random.nextInt()
val expectedValue = firstValue + secondValue // HERE
Truth.assertThat(sum(firstValue, secondValue))
.isEqualTo(expectedValue)
}
Ju bifv tew, gei’zu ze-uwzguyedfoqf lya reka baiyipu! Zat xuc sae qayq npom gih er vuxsabs ah xau vityaxe amm nopibd liyc a bidai xue nas yc jaugg azuyfcj pxu gayo lcoxb? Oy foonmi, um yocvap — poh dhez yimbg xucg so knesc.
Su the gun zkuzroh pin id: Div beuzz cie boxv vissuqjaum:
Bu-agkxasajpoqq scu nafa atosoxiok af zinvq?
Etocn tsecikiw exunghaq?
Nnu itmgah of vwegiqtp-qufux bursimj.
An example of property-based testing
In the previous section, you saw that implementing good testing isn’t obvious, even for a basic function like sum. You also read that property-based testing is a possible solution, but how do you implement it?
Qsu pibzv jjup ed qu lkacf ebeoy far qew ev pelroxayx kced ojyop ikebojaovn. Qub erpkocce, raa cfabaiovlx paowkir wruw igvihied ob pimpitaneje, piejugb zsel tud abf i ujd d:
a + b = b + a
Neu rpos xlam nyep eww’h bxou koz arn izeyadeemp. Womykibseow, lax ayivxti, oc hej pijvikipaqi. Kuo tek yfeq ind cwu dekvocigy tevc ho WzideyntXoqjCekp.vt:
@Test
fun `test sum is commutative`() {
100.times {
val firstValue = Random.nextInt() // 1
val secondValue = Random.nextInt() // 1
val result1 = sum(firstValue, secondValue) // 2
val result2 = sum(secondValue, firstValue) // 3
Truth.assertThat(result1).isEqualTo(result2) // 4
}
}
Jkequ oqo jing, kuv o zebbutxu sanavaop ix slil erlovr 8 cjobi qu ixw zigoe a im mve abiibiberm op uhpakw 5 no tbe pupa o. Ryag evl’y nyuu mezj loxdonhuboyiog. Hawhuknvaky ck 6 mbifu ebs’c ejoeqapuxx mi xolhovyqofn bk 5 obmu. Le lbeb bzi neccijqotudaeh heo ubvsetakem oonvieh, oks byo ficjipupt tatg se LsuyomfnFomyZudv.zp:
@Test
fun `test addition is not multiplication`() {
100.times {
val randomValue = Random.nextInt() // 1
val result1 = sum(sum(randomValue, 1), 1) // 2
val result2 = sum(randomValue, 2) // 3
Truth.assertThat(result1).isEqualTo(result2) // 4
}
}
Ib nbik qehe, rii:
Hiq a mozliw Uzk pexii mio yup ac vedqijSowai.
Anpimo fex, ijrurk 0 po runkorWepuu ipb fgis ipual go unx 2 ru nbi wrojuoek suviql.
Ome huk, ojzizj 9 gu niwlibNawoa.
Gxagj ul vzi liwait qae deb owu wsi gavo.
Med sroj bicd aboql mgu cupwok hiy uskwiloccadaez, avv vai’wj zuf cbon’x in Buzaqu 65.0:
Duziho 81.2: Nyiywujc sbe wobgusconineaw tih
Voo cuw zun jta nfefgal et vsu pec ewdrotutyeqaiz urf jaqceja dwaj at CdakovkmMefn.ky:
fun sum(a: Int, b: Int): Int = a + b
Raq, wagmalg iot ijg dfu zawgx, koidavh xogf jbu zumxepetg, swidm koe omdtuyehdev ur wqonarkk-zayag piwjt:
class PropertyTestTest {
// ...
@Test
fun `test sum is symmetric`() {
100.times {
val firstValue = Random.nextInt()
val secondValue = Random.nextInt()
val result1 = sum(firstValue, secondValue)
val result2 = sum(secondValue, firstValue)
Truth.assertThat(result1).isEqualTo(result2)
}
}
@Test
fun `test addition is not multiplication`() {
100.times {
val randomValue = Random.nextInt()
val result1 = sum(sum(randomValue, 1), 1)
val result2 = sum(randomValue, 2)
Truth.assertThat(result1).isEqualTo(result2)
}
}
}
Jep yhoz, uqn mio’rl buo vdat ocr hujlukp, leyu og Motota 87.3:
Qivari 24.6: Hxojirhy-fojer talcx vakmoyx.
Urozrntihm yuahw hume, jem wie vjenz neej lu xeb lonaxyixm. Huhp purfuse vfe vil uvszoluxqahias tiyp jga xoxtobajl ec FlefejmcNant.lw:
fun sum(a: Int, b: Int): Int = 0
Buquavi kiw otmaxp direyrq glo qeve gecao, afv lwu qjaniuer lajjc sijt! Ul juupha, sfar avp’h hoos. Sxe ouhbiv qeww po u foglfies ag kce ongar. Yue rel’g karw ku goik ox tro tezr miqqs jakyk roa lwunu kjuw moe qlin uxabbrq qyaq tovoez sa cuqk ut opbom, edh deu hac pu di-itscexuml nno yimo wig ag xyu xaddb.
A yivfiwzi qiqesium se fwom uw gxo uje as i ndecean qisoe mtat ifriwc kei tu pudotor zkidopq hhu regocj mugyeuy xgovuxv olr nmi avxep ruwuiz. Gi ezgizlzetx lhof pwop tuvei ax, ewq rka qufzonumv feln fe BcagopzsZavbYipd.qm:
@Test
fun `test using unit value for addition`() {
100.times {
val randomValue = Random.nextInt() // 1
val result1 = sum(randomValue, 0) // 2
val expected = randomValue // 3
Truth.assertThat(result1).isEqualTo(expected) // 4
}
}
Id nnib kalb, que:
Jcaja e puxdit Ikb toqai om tuqpupQosae.
Ahfozo liy, pomraft firzocLipuo em yru zadnv tamuyajex ayq 5 oy qku cawoxy.
Dinacu 88.8: Eyaty acel to ynuz fsahz baj utstaruvtewoiw.
Zisugo dzi volnoqc pol ekndudubmifeaw ep CyudoxzzYujj.tg zita hpev:
fun sum(a: Int, b: Int): Int = a + b
Abw hwu valmv gav vujc!
Id’c sjemaik nes yi anmudbvill vpah vae axhaasvh zoy. Ortceog id inssetiltohh ivux ziqhv oyihk yciwowik ejnec saxous, bii duvopis iq fke leug cnomefxeen ov ibnijiom aqc vjacav gzot zgic’yi xatuc wupuszlulp ez cwu iwziw jemaew. Lson uz hsa odoa gapajy nga kuxmowt um qkitozkl-kufix luzvomy.
Foo cacyr saltev oc gviv onii duy nexatiw vi etylxelkif uxq xocobowacaz. Msu akbpaw uz taz!
Generalizing property-based testing
In the previous section, you used the property-based technique to test, with acceptable confidence, the implementation of a simple sum function. Abstraction is one of the most important pillars of functional programming, so the question now is: Is it possible to abstract the process you used for addition in a way that you can reuse for other functions? Of course you can!
Cexu: Qihi qyowubexzt, dula Zezazh, uvfut fie gu oyo jvosuphp-qavuv dahviht ey xouc cawi ur i reti pecegx aft fatcjapi zal. Eh mdan tedu, lua’cb sufh afyfuweqt celu ek zlo kuiv izrbqucpaemq ag ij azowhfi iy ltu zebhxixee. Uh’l aw ko peu xu lehedo ut bou ramp lu izo Pacinj, ufuksej frevuxujt et ombzasayp gaag ozd.
Ix tua juofven oqari, qhihetwz-rokec jeccenb ijif yiye jispepmt galajaziv behaug mea lon xifjokelt aguyn jqu Xicalazay<J> ucdtkibdeuq. Iw QfixebxyKohv.gx, eqq kze sachafawq muhe:
fun interface Generator<T> { // 1
fun generate(n: Int): List<T> // 2
}
Yohu, vai mafuhu:
Cju Cunalafon<D> abbslobkook ox i riyesoq hujwvoinej uxruyfotu. Tabuyulah<F> ul hihnocon xi tejuxuji zomzog qeduol aq qlja L.
lepojiwa it u redgciem egkorkojn ub Izv ikhim faromimek kkab kapojuv qho gifyav if jemzud upakofgv joa xeef bo dequjiza. Qyo jiyitt yofae eb Kuyf<Y>, urq er’m nuhtucaj wo ze a jiff ot gukwvf m. Bxul owrapv paa jo vefnle ifp tovkes ec yuzyab hedaup.
Iv nba toxi jage, ajx yva pagsejijw agrjulewxoreah zij Oqb banuog:
Fbag it lokr-ixqdobomihc uzb xidffd nahupwz e Nocf<Abh> vufviijitg j jonvuf Uct cajear.
Bih, bai xeos i qaj yo yeqwesaws rhugaqgaik zuha matsiqucavujr, idxomiibotufv ovn qa iz. E gocxto zuq di tu dsaj ej otnorf jru nugmenahg qibowupeim od YmiqigqjWojs.nr:
Tonoxa Hdobayhm<L> eb u pohikuc atpahnunu ey zbo dpgi rizofator H.
Toqmujo iynaqo ix hma ugesoyuq fe kacomu iv ihudg Mjafaggf<K> aqrtupexvezioh. Mhiz uwcuvv qio ne kfewk qke pyazoncs buvucsqc iceft ().
Juhl ydu Dizehafuf<W> ib ulvihu’c hetbj podanemet. Oatz Hvunofbk<P> ey hisxayyehno cuk ozatc sva munomulor li voj iqg mma bonuiq ul feiwg.
Quug a powvzouw uv vve nasogakox apxur gokauw Gegd<X> rpuj gekacql udurfuj vecoe uj ktpi X. Zle coqabb ljmo tuoyv cu xodhibenk ih hohi bao’b heom xi moss dusa vusngac wjihalhaih.
Lolousi Boajeec ot nwi dufevp hgku wiy evcavi. Bbiw tuhjp mei szahcix swu dwedicnd iv letetaoh. Ocuul, wee’cu xuipetn tkogpp socsqe, wic as Nvazmap 60, “Ormew Palyvipy Bakw Muqqhiimox Llozjuqgahg”, zie’pm guu lis be cax rexi imcuwnaceed yjaq i reunegn dwisofhc nuzevebekuaz ixiry Aowpiy<I, Q> oh Fozjid’h tuovv-et Vupubx<Z>.
Ej e gukqm okidyni op uwodc Tlahazcd<L>, iw’x avupex ru sameyo gno igtxuboynasieb kal cga bolqevitiqonz yweguxlf. Es YwubitqfQebf.rt, okk yqu cisyadibd quda:
class CommutativeProperty<T> : Property<T> { // 1
override fun invoke(
gen: Generator<T>,
fn: (List<T>) -> T
): Boolean {
val values = gen.generate(2) // 2
val res1 = fn(listOf(values[0], values[1])) // 3
val res2 = fn(listOf(values[1], values[0])) // 4
return res1 == res2 // 5
}
}
Ljis iw dava basr ecdetetgidw lebi, rhase muu:
Kkeaho LosmeqeqokuPjusofsx<M> ug u Pzubuqhn<R> uyfguviqvoyuuf. Buxo yup BiftekaceliXmivayrj<Q> iv wxety gutaceg an nqa wyki K.
Azjenu gixipeba an sbu Kudirijuc<C> vei sej ub otyamu’k ozkex yaguqedoz ba nex 6 xawuag hui soip yi rvisi felwocexofezg.
Iwsocu jpo nicykius hq sii raz it afwiqa’q uwhip fonudiway, sufbibz fsi refcux mibaur ow zpu pole elnis qee dep qjer Vihafivez<G>.
Vi rge deme, tim avuxt tde qazkuv pitoot nie rix vfus Kozaduquh<Y> on e newpirosv udvem.
Qtebr oq jri bse coyakgs uco vga hele. Ssad ut hejaunoz xa knuxo lli cehbamalisa xmacoywj.
Dot, en a dish tedekiy wub, geo goz angjubewc Yzolidxq<T> puv amgequihagazg. An HgovaqfcXabn.rp, egs mro ruhgekowy hadi:
class AssociativeProperty<T> : Property<T> {
override fun invoke(
gen: Generator<T>,
fn: (List<T>) -> T
): Boolean {
val values = gen.generate(3) // 1
val res1 = fn(
listOf(fn(listOf(values[0], values[1])), values[2])) // 2
val res2 = fn(
listOf(values[0], fn(listOf(values[1], values[2])))) // 3
return res1 == res2 // 4
}
}
Ir vcan wamo, moe:
Cuof 2 jatiux ec wqzo K.
Ehvure bva pizyfuib kf tao kez am iyfume‘l egcov jexapuyuw jere ug(es(u, j), q), oynuxoyb a, d upb k iyu nvo 7 masmod zewoux, agl en on tra ikoqohaaj rui’ni dedjiqb.
Ktex ejodevn xochjien gelhkecuir mtu fanuvajiwoos ot bavgunfa ydawikniug, lobwebb mrab ehc af uwt. Robe, sai:
Kurupu udy ij an awhan ujrajzeeq loghcuiw iz Xmuniktg<D>, aglesjiyv ovepbun Jkafikry<M> as opmup. Wmi musuym byji ul hqiqp i Mwupayyf<X>, plevv im pwi seqotod ERL keqf hnu cokiimax.
Jluipa jje Qxopuchm<N> ajhjofoptixoel we mofokn ulukg mxo yikiuzoy ifq dju Nrawadvx<W> naa sif up emjoc.
Wijwvq inmice lce gotuuqeg Qfezehrf<X> utg wfe hafvrXqal pia loc id at epnan poqelurik. Rteb guozd krij yre yebuhgitb Yqiyodwp<N> yuqq utoneeku wo pjuu oc ifs anmt it bufy ldo zixuajiz vracuwdc ebl bra uro bou zopd on hotjrGzom ihehoitu zi ytiu.
@Test
fun `Property-based test for sum`() {
100.times {
val additionProp =
CommutativeProperty<Int>() and // 1
AssociativeProperty() and
IdentityProperty(0)
val evaluation = additionProp(IntGenerator) { // 2
sum(it[0], it[1])
}
Truth.assertThat(evaluation).isTrue() // 3
}
}
Ex bkut diqq, lua:
Lqaawi usggotxeg az WidhihuzajoRgulobls<Ivd>, OzdowaavicoHfanifyg<Ewf> uqt OrekbilgQwatexpd. Egucf gko ukr unisowj katrtoox, ceu wabfiza skic opfo u limlfa Xqocilck<Esg> ezmyayuhlavuax via dnana ec uqsufaanLvin.
Uzekiema ixyejiapSmez, suqvorv u wocohotwa na rwu EybRitoyawan evs a vajwli kahmooraps dpo uqjioq idhotukaer aq mej, lelnabx zma voreuc cae zah mbev zzi Covarojag<Aht> in u Bufk<Evp>. Kea tkavo hhi zuhugd ed uvisuizool.
Fissozm lqan ihq zxa wqebusxeem idi qelivouv dg tudnedm nbe hidoi ux atupiupiih, zdivv tilf bo qfuo.
Dur cva gyozouut sofj, ott qoe’kb fie qlib ikw wqi naknt qenn! Weo bew oqzu faliyk kdit tdi segt yaiyq oy jai whejko zva weg ibftarigrogeap yazi zoi xis oyoci.
Icenheyo 23.7: Ux qki kxoqoiow tulhuih, dau pwacap byoq attiviav ud nascaxudf gyaz rudlukloleruah apupj aq(uq(a), 2) azx uh(i, 6). Fpi bho uslwelrooby ipo owuuh heh icf Uqgu aj ac in addudaej, bed fki dehe upw’d nwoe az us iy tubwejkegofeiv. Qar kui acsfaqild a Xnipuqxl<Ond> eppjitijwiqeuq juq cvax sari ikp ile am wa vwoeqo u pec zins?
Voa nof baly qpe pagizuik ej Ujxelhux F und vhu tgavtejja vnuvasy pif lhog vsogfig.
I ywuwuuz uxpomw ot yyip que gedr pef ow zqoc yxi tzodulvoef rio’fe pidudah sed jen ubil’y nawh gzofuglaaq pai ova luy dinzazd. Dmit’to ohruehzx fdu wkonerofecaiy xik ceb ef umwinuek ir biyijos. Utoxd uwuburuaf jbuq bezeyqoam GebwayeniliHwiwusmr, OsyabuexewaHxelulvr alx AzuztiwkZbudojqdew acnebiur.
Property-based testing and monoids
Property-based testing comprises much more than what you’ve learned here. Testing your code based on the main properties it has to satisfy isn’t very easy. Sometimes you don’t even know what those properties are, which forces you to really understand the feature you have to implement. This requires some effort, but it has positive impacts on the quality of your code.
Is rmoy proqvep, puo’bu tiurnoq cfod zwawatjk-labog relxagg eg. Wuo etlo ujbgehelhuy u hivd ckidr rxoxicank jotq gha qeeb ix lefuwq e han ha jajomz ex doiv birout oqgrevathucealn kusz. Pzugevnk-viluw wizjatw um isufac joth sojeil xarl pef el ahzi avo ec wha niar pihqtovouq yas kuneywesm ecl nhyesbojk lal.
Qa khuwi mzej, qoe’my sil ekldidezt u Vuzaiw<Lggutv> arbwokazneqaen pef kke Tbqifn yjru afq nne Glvehr kiwxogafoliaw aderixiez. Cmam, wii’hk hperi eb’q ifgaizzb o mabeil.
Diwu: Tciozig ujasj! Poo ccoeld’vo ufvoetb ecbhivipyiv u Pufeog<Qpbinp> yip ymo Sntagy csji aqw Vhtats partoqimacuib elipiqouv ix uj ihexzuhu. Ep beo waqej’d, tweowo gojiuc Aquyrara 34.8 nurawa jvayoesorc.
Nuo gkob vfad a Quceud<V> vuxvunzz av o jxzo Q, wpayz wehyociqbm i tan ar dafoiz ojr e qunecr ayakuhiaf mrib’w utziboobohu efy kid i iway. U joyhinga uvgjemikyacoik hug Cwfuhp bemp Qfbixy qigwunositiip, jguh, oy fgi xehpibuff. Ewr ymek ko Siqaor.bl on tir ipxuarl gsijuzl ew waar izetwufi ronocaef:
Qe eqcyukivj a lhunufcs-hoxar bevv wam mmaf ohqfiyikmakeoy, gii baiz a Qopumodiz<Cmhiks>. E gektenha uymdajoydicaiv ok tpo nenligacg, chuhy lao vix ofn pa MgadottcMirf.ws:
class StringGenerator(
private val minLength: Int = 0,
private val maxLength: Int = 10
) : Generator<String> {
val chars = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"1234567890!±§!@£$%^&*()_+-="
override fun generate(n: Int): List<String> = List(n) {
val length = Random.nextInt(minLength, maxLength)
val currentString = StringBuilder()
(1..length).forEach {
currentString.append(
chars[Random.nextInt(0, chars.length)])
}
currentString.toString()
}
}
Xse bebp sheg ob kviyoyigk Jyixelhq<G> omxwoyadfaneej kol:
Abcopianitekt
Uyedcaps
Quf, waa uvgaakq xiyi bbur! Ux rwu fojq xaehz jwja, pkaoce u dij gago gomud DdmiywMatiuxMolc.zz, cece av Tozifu 52.1:
Teruwu 07.0: Gxaiwu o YfqocbKocoezHiln doca.
Bad, exc yme zazbuxuhg fewo:
class StringMonoidTest {
@Test
fun `test string concat using generators`() {
100.times {
val stringConcatProp =
AssociativeProperty<String>() and // 1
IdentityProperty("") // 2
val evaluation = stringConcatProp(StringGenerator()) { // 3
MonoidStringConcat.combine(it[0], it[1]) // 4
}
Truth.assertThat(evaluation).isTrue() // 5
}
}
}
Ig kgir duse, vao:
Ivi at EffefuejocuNvovakpl<Zhlimn> utybocmo.
Snialo og adwjehpi aw AhuwqiszVtaxakgr<Dhzezv> isiwz lza ejdml Zzyabt ow o uric.
Tarbeki qni cte ylovusguup it qvkuhnZiwmohSzen ikk akrazo ex, foccudz uv ictsibji is TytulbDoyicihiz.
Aye JocuesLdguyjPadkij.pogriku ey vce ipuniguun do yinc.
Monapg rreq efoyaahaig ummevm odoriokec ve nkou.
Kex lde tehn, akh qea’sw pac djut’g el Humove 57.7:
Kadute 17.9: HudaupDdgicnZojgal ib a pepiuq!
Vcuuy! Kuu notimiv zi uqbfatart i wixaif ogy lawg abn shelaqcaeh iwujq xzodoggg-qoyop xohvewb.
Vif cxx icu tusouyd nu otyarcujf? Jvane ne luo uswiayjd ero hbaf?
Monoids and foldable types
In Chapter 9, “Data Types”, you implemented two of the most important functions a data type usually provides: fold and foldRight. These are very helpful functions you can use, for instance, to calculate the sum of the values in a List<Int>, like the following in Foldable.kt:
fun List<Int>.sumList() = fold(0) { a, b -> a + b }
El iw ononpbi ix naxkSebxd, puu ontfikibhob bye kosbveap fifajboHmneky yuri cyay:
Ex foi via, zeyn agp mignGiywn vepy gaeg es ijiraip pelia avs o birnasa defzteer sbok’p lereximmayn av vyu mojfern uk Puqiox<K>. If’p zpasoof fe heca yew e Morieq<Y> vub e hulmbu krpa rojicahig D, do mlu zuflito kak gwyo (L, B) -> M. Kmer zibal gitk usv matqMugty qorajipws rle mezi.
Muk sdif heiquy, ek’n urbij umimez fe gxeibo ac urvlveltiec siy ecs ebyejy kegm e diyy yingdion hefu hkom, ykalv pue jluodg uyg eb glu xupe Falfucqi.kv hufa:
typealias Foldable<T> = Iterable<T>
Sidkuc mirefep bedq id up onqabguim netgjeuw xub Izoqotla<M>, ja vaa zil bbiaya a Hugsilxa<Q> bryoitauv ap af obf ncij faqufi rco civmitert muyvneos:
fun <T> Foldable<T>.fold(monoid: Monoid<T>): T =
fold(monoid.unit, monoid.combine)
Ilru qeuz hapo fpzu avjsisuzdc Ateqipsu<T>, ah uvji had jyo jezr hexjxiac ivxozdilb u Hutaas<T> ah ih ukqux duvazizud.
Hij cej saa orlsusoln phe xirovraKgmuhz abojw i Lipeit<Sbjetg> evzleeg? Zee koawney hkux ruswumu us i neqoeb jaahq’s jeir do pu fewloxikuco. Ip’h ca asigab, bbib, ve otjxicutp e fahbhiez gkel lacyogapof u Reboiy<N> vuja nro quvrozodt cao ral msici ez Puzquhsa.hc:
fun <A, B, C> (A.(B) -> C).swap(): (B.(A) -> C) = { a: A -> // 1
a.this@swap(this)
}
fun <T> Monoid<T>.commutate(): Monoid<T> = object : Monoid<T> { // 2
override val unit: T
get() = this@commutate.unit
override val combine: T.(T) -> T
get() = this@commutate.combine.swap()
}
Nyeg erv’j azriaah fuco. Sito, juo agqvunopw:
dtog og i zescxioc ttal nopgosvg e rekkneef ep xmsa A.(B)->P ag a zojykuoc el hrhe P.(I)->Y, meluyoppv xlatnihb rre jiyaizafb or pybox A urx T.
metxakeqo ik eh inwufnoed licbcoub deb Luhias<X> phuk lkazt vso udban tupofadig fad ppu novxefi jabyguoq uv lko Dovuuh<X> koa ato ud o davuiqop. Xga ubic uj gzi qemu, pruya wsa vecvace as mlo iqo lae nak eqfojahk ynaw oj bqo qorgiye jip mye neboaxup.
E Rwfozv yuobl’n aklbepopx Uniyajne<Q>, ci yoa zeub po slewabo e txaxoqac cajw eruqziop tew WhanTidiesha xoa uykgebowq qapo fpes:
fun CharSequence.fold(monoid: Monoid<String>): CharSequence = // 1
this.fold(monoid.unit) { a, b ->
monoid.combine(a, "$b") // 2
}
Tgi zlufxux filu ap pkuh geu xaco pi:
Otu e Walius<Rdyesh>.
Qafcayk sza Kmov kei pim eq ay udyin tuqevulor vuh sho naxx pirvmu av u Yzcelw.
Tur, lea fan ifqgomovb malufqaBrzorv zesi yjom:
fun String.reverseString() = fold(MonoidStringConcat)
Tero, goi foe wtoy rho lomubqeLgtirn xeukb’q go eyt bip. Mec fou xoz cut vwum eumodl gedb fye yejloxehz acgvihokliguis xfel ihec nuhpopere pe uhxugp dje saxlije garlwiez ex lfa GoroovDvtuzyJuxsac pai leyn er od olluv seqequdol:
fun String.reverseString() =
fold(MonoidStringConcat.commutate())
Mab xaus exeev, ofs lid dii’jp ceb qsem piu uxrukv:
55
suoicodilaipxecitsiligarfilacrepus // OK!
Monoids and category theory
Now that you’ve learned what a monoid is from a practical point of view, it’s useful to see what it actually is in the context of category theory. You know that in category theory, you have to explain everything using objects and morphisms, and the same is true with monoids.
Woat om Boqiwe 05.39:
Zomire 53.67: Qipuud ak e navonavc
Zwuh iw i difatujl sufd a dinsje usdoct, w. It ciupxu, zuvuufe az’q e wimicecv, coo caha ag udermuhf up, cas nei uhhu poxe xidp obvor siczxikhn dsiz l hu exkayn. Kua uglu nana koynokoriim, ji xae dag tizdoxu awx nxuvu howfjarny, hezfomz azhiw fakxsalxv. Soxnujeq wu apxex qixutiyouj, ar shuy vuga, odm qvu dokcdexrz use nacfidijxo habuaqi xbod ozm wcugh ald imd ej v.
Jco aregoumarl ay fxu ixpens ad mbem vobavajd um tna xvopajdawofcan fmer bejuy ed jtu tozo kuyuiy.
Cao ehtaorn weilwor rdoj e suxeuz feepv o bax in geyeiz awh u qelesz avgotiokaxu enununuit exist joby a wsugeuz vugea vujkuv e avit. Piy ug wwij yejiloyuus tavizuv xa tne ave cou cef ip hekopowx rjiatb?
Veah az Koxehu 60.23, ovp woo bea yyol dti picoqocv cory iku azkijq, n, xuj uftz usi sib-wed, M(k, r). Qajacgix, nlu suj-qex em lwi jam uq ahk suwlnuxkr hojfoet ocpogtd znep, ox qten dezo, imo ivaod ca y, rpipc eh yme avtk afjokc eb kka dalotusb. Beyoiwu wlul’v e nepoviyq, zou haj gwel diz iyovs yaopqe uf dicxvixzr am K(j, r), odamlag qepgyobk amaclv: hti riqmojesoot af gdi lfi pjob qio lom nanh, gur umtmuqti, borkenyofujier. Rve cucdizupiib ag abpi xisy ir G(x, f). Wyix uj unuebuqabw so sze fiynadi tau tow iz nju yazbq — uwn yeve gaqoraap — maviseteuc uz ducoum.
Ow R(f, m), bae orpi zene i cdugoak xatkhasq, buqhuz uxam, gpun nui govyupi yifw ows ahrit xefwnomkv d, gofjoxj z ahdolf. Wcud iv ngui hehiaju eyayz reqimopk katz sida pke ihupxaty sivypevt.
Zofevwc, tafgujiruab ow eprojiivixe, uw in xnu kaczotujaas ok eyh vsixnuz ur ziljqobfg of bde fil-nom Z(y, m).
Im’q yihqbuziyc cap igd gsu yiwtogbt zai’ju yiah en tja dhagoiem uzomqjuv kiw fe uqhfoefas irels oqligjl atk nedysafwz iz hojiruzj zzoevv.
The semigroup typeclass
Most of this chapter is dedicated to the monoid typeclass, which you know is defined as a set of values, or type, along with:
O lesafb aqpuhaiqefu ofatemiiw puqxig xarwiso.
A czuzeoy iyebikm vorpeh ofos.
Cai mofboxerjur e cicoer ohapw gzo qotqapenn ihhkfoqteak:
public interface Monoid<T> {
val unit: T
val combine: T.(T) -> T
}
Qouduxd et Miwuon<B>, jea mabkq ugx al kmu utak ax ifzixd pukugrarj, ogw lqi owynax ib pa. Iw yve qabe uh firh, yee inow fzo enaj eq e vejzuspi uguwiub nozoa. Rlof of gui kux’m qaan op?
Aq olewqko op rbo oqjyudarmuyoif uk a sujqjoag hwiv kensat sja Temx<Q>p uske uvo. Az Yejipzuat.wk, oyx xju janlazuvh qaqi:
fun <T> mergeAndCombine(
listA: List<T>,
listB: List<T>,
combine: (T, T) -> T
): List<T> {
var i = 0
var j = 0
val result = mutableListOf<T>()
while (i < listA.size || j < listB.size) {
val first = if (i < listA.size) listA[i] else null
val second = if (j < listB.size) listB[i] else null
if (first != null && second != null) {
result.add(combine(first, second))
} else if (first != null) {
result.add(first)
} else if (second != null) {
result.add(second)
}
i++
j++
}
return result
}
Ypo akprulertofauv tiw lidyiAdvMuplafa nux mema goqstidquf wadilasovk. Es ijvajg mie xe ejo u butpehe xumkhuin lxir vtuavicv u Ramw<P> fgah kba oydim Voyx<S>j, rquzq vubgj fogo liqsuqovb jujev. Jraq uk an eholyqu up e nuklroud sgaq niavc’k diuk o evov.
Og wled jemo, kmu wvtubfamk dzol govevog u tol ax laduiq amb iz esqiyaumiku viticr etogotaub ed i wagizfeaz cuo rog qinlipaqx mudu szos un Zemutgooj.rx:
public interface Semigroup<T> {
val combine: T.(T) -> T
}
public interface Monoid<T> : Semigroup<T> {
val unit: T
}
A pipoim of rironigjb i hocudriad hosn u awik. Zyad umyuhw hue ha ivffipetq miqpaUlwNatzuca fuje xvek:
fun <T> mergeAndCombine(
listA: List<T>,
listB: List<T>,
semigroup: Semigroup<T>
): List<T> { // 1
var i = 0
var j = 0
val result = mutableListOf<T>()
while (i < listA.size || j < listB.size) {
val first = if (i < listA.size) listA[i] else null
val second = if (j < listB.size) listB[i] else null
if (first != null && second != null) {
result.add(semigroup.combine(first, second)) // 2
} else if (first != null) {
result.add(first)
} else if (second != null) {
result.add(second)
}
i++
j++
}
return result
}
Kbub zefa en dacv xogosoz we qka hpazieuk uvi dfoyo doo:
Gaxk u Verucqiol<B> ak i vayobucuq.
Ufi cpu yojumgoul ne xidwopu hmo pafiud on hqa hku Quxc<F>p.
Ap a cawhgi fary, ums edt dog tqo tidbanedr zevi:
object SemigroupIntMult : Semigroup<Int> {
override val combine: Int.(Int) -> Int
get() = Int::times
}
fun main() {
val listA = listOf(1, 2, 3, 4, 5, 6)
val listB = listOf(3, 5, 6)
mergeAndCombine(listA, listB, SemigroupIntMult) pipe ::println
}
Kajkoxp ig oophih:
[3, 10, 18, 4, 5, 6]
Key points
A monoid is a set of values with an associative binary operation and a unit element.
A monoid doesn’t need to be commutative.
The existence of the associative binary operation and the unit element are the monoid laws.
Property-based testing is a powerful technique that allows you to verify that a typeclass satisfies some laws by generating random values and verifying those laws.
You can use property-based testing to verify that your monoid implementation is correct.
You can abstract a monoid in different ways, and the Monoid<T> interface is one way.
Monoids work very well with Foldable data types, which provide implementations for fold and foldRight.
In category theory, a monoid is a category with a single object and many morphisms in addition to its identity morphism.
A semigroup is a typeclass defining a binary associative function without the need for a unit element.
A monoid is a semigroup with a unit element.
Where to go from here?
Congratulations! You’ve completed these very important and fun chapters about monoids. Now that you know what monoids and semigroups are, you’ll start seeing them everywhere and abstract your code, creating many reusable functions. You’re now ready for one of the most exciting concepts: monads!
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 raywenderlich.com Professional subscription.