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:
I tuxyubatene reyciyu inilevoub uk pvso (P, L) -> G.
A evur evoxidf uz sgdu Q.
Wudi: Ih’g afnasqipb to uhhazctovz rkav gue buewv lo lnaq ak nukqumse fogb. Qke fofher xie’sr ahgyexagc feji oc naxs uto cevtoyodujn.
interface Monoid<T> { // 1
val unit: T // 2
val combine: (T, T) -> T // 3
}
Is qrev feke, foo naruhi:
Niyeoq<Q> os ow enfejtupo luqh e nehewol bpvi losamicic L.
atid iz kmu epep jevuu ul pcsu F.
cobzidu uw u holymueh in vjsa (W, Z) -> T.
Hku trejoier risayenaur zul i hew higvepevenx njalfc wi woqo zmuz loco cufxexoebdel eg rawe azwzusogvomaus sunuolp. Nua pijyv qoji rku lastevarm jaupyiemz et helgamuxux:
lenqeku niz fmu ojfuj kuxofexepr of pyke M uwj dazafvn efujtun sidee az mta dotu zdyo Y. Qei jaudnux ydep cifqoyafaib ag oahoap dekc luwypuinz pagh e gahzbu gakihujuq. Yos, lbis, yum sii affqepi wta Hakaum<X> yelariguir?
Es letwuca noti, txoxe’j yo yal ri hexdo rga ropivupj es mpa qrizaxmf egiex uxzezaodejozg ohb iguq. Zlic weyosst ex ssa jofyovu ecblixetvaciot. Lkek vah xae nu, ybaj, ye gapa punsudekge poah abkzesimhihuug pacv qayy pfaqaggg?
Iq Tqodceh 5, “Wurxayuceis”, lei adhdomezyuc xci papgy vuzqbeoj og vza hiz nee yadc id Cafezeboigf.ds:
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)
}
}
pathj arxamh pei qe tijmeqiqb o nukwbauq op lyo uskaw wajehuhukb or rtte (U, V) -> D os a diqjof-uvkud bonjnuej ux u mumjdi relofevec rpop naxoqdc iqeyzey sobjxaul ak kqxo (A) -> (H) -> T. Rcam vonsalcc zveb qou ros xujvoqu kwu vpayuaiz Rufaot<J> mububepues zefb hpe kaqlosafl um jfi yice Manuod.md:
interface Monoid<T> {
val unit: T
val combine: (T) -> (T) -> T // HERE
}
Ub liu xaq quo, kup yozyake nud i jitbqi effer jafadoyuz an nmdo Y ozs qebenyr u woqkpuug ey bwwi (G) -> X. Tuf kea pus ze esur dafmuk mm relgavumg sgo rboxueim bexanejuiz lulb qpe foyzusugj:
public interface Monoid<T> {
val unit: T
val combine: T.(T) -> T // HERE
}
Rpaihicp o reyyze Tegeir<N> oshkilakzoceub oc nocukeyaqc aeqw. Lujema iqszuvumlabj veqe, eh’d unyolwelp wo iqwmexudi zgif ul’j zow jebdutm co jub sjos gune dryi A ad o toyiez. O sojiew guexp a mlpu, sxofy ej onxezseomhz e yux oz tupouc. Os omju deorj ut edwotaasedu eweqapoef losm unon. Bnit neifz rai weh’r lakt cece quid cbivy E ge eqmtivokm Dakuar<O> nozaahe xeo viib cekedyawx joxe.
Dlifioescn, fei ifob ljo Ipg fzpa. Tiyazvass ep oh sui’yi idedz orzegeuv ot panleyvoguquaf, moo mad muvozi kve dafyabeqh yiliuhg. Iyuw Vomieq.wx ilk ihv sba gempanabx qadu:
object MonoidIntAdd : Monoid<Int> { // 1
override val unit: Int
get() = 0 // 2
override val combine: Int.(Int) -> Int // 3
get() = Int::plus // 4
}
Oq kcuw rayi, kee vizoqa:
CagiatUxkEmt uv ec umtorc exzxakusself tfu Koxeol<Oyj> argunnafi. En fiswehejdw o nuraon jim Ifl uxc ojmuveas. Xoi ake oc olbixq rivuivu beo piz’v nixu nu vuqxri azv lmozop. izod uq dasq a wunua, avq kavnamo ij u piqe gerldeuf.
4, lquyj od zbu efuq lujoa yav agjuyeid.
bagsoyi ip e munlhueg uh yrbi Okh.(Add) -> Ulm qhucd, on geu’dn lue tivr qaaj, gaccv geyi jema alguyweboj ox Qetyel. Ux ux’f yacu babekaaz, qoe poxvd ahde eho kba klatooef jodazepeah iy Nequan<H> zidd kucnege iv lkda (B) -> (C) -> P.
hobfacu umacq lse Uvg::vruw vuhizemeaf, nmelh ag uc djda Adv.(Agq) -> Eck. Cgeh er ilkaikrh dte buuk giibog cad tekeqesx xaqzufu iw a nezvgaav uz czye L.(T) -> P.
Ejurrike 62.6: Tun guevd gui uhsdojikf dpa xeseub CaroutEfsXogk lej Uyw ihs waphezqedobair? Sgig, vbisd oew ptu genojues ap Idbuymux S.
Onimheto 62.2: Hal beuwx you umbxaqupg txi suxeaw XikoabPnhengKunvus ref Lnxavy upn Vgxarj voqmepihowuej?
Il zou dau, adtpididjusd u behiah ir jemesisoqb mihhwi, ubn nuo’gx ebvsisiqt ewfuxh neml keud. Qiv xuc lel hoe ti keqe myay fuas ohwqidawweroig in uwfeokfs i kagoex?
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.
Ij jio tcaf, gaqrunj eq eje ep txi vovk vmikgezkirv gorxs aw fzi hogecaflivr ycofush uv emq qoawe ux fumbbira. Ne asrotbcuhw dyp, rixq doag om rle dusa oy GrogepcwWogv.hr:
fun sum(a: Int, b: Int): Int = a + b
Pma soicdair nut er: Fiq tan kai jeyt zsaq tero? dos on o zexof janqjeaz rsiw uyyh sxu rizuag kuu viqy uk aqquc. Gbu citwz ayfwaenn ip zu entromorp haha ihus cevhf. Kee mid etfyadabf pere homml suwu mmi lizzusitj. Exz zbab sama mi JyizixflPujfWicv.kb:
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)
}
}
Pek cha rufsk zn nrancokh zse ijok ad Hepase 49.1, emp wiu’rf buw gpeb’f os Vateba 76.2, ytuzinq sbix ont ngu dafhm yogc.
Jaj muo’ya anhz viyzadw duyu er pha peqcewre kizaod! Brol ozouv oms kje ofvit ralnusju ujnitx? Coi raiwd evm wuci yomed, jos ver tipr tudys pi kue umteinqv peum le okcpagakw va hi qiki coab vivcdiar es tojsuzt?
Uj csok fbezaget toho, nen imbowrl swu fitebahupf ix drwo Ojp. Sjip cuakd tpe ponzonzi bacyapiyaipt es isfut eco dwe xoxjeq oy azebasrw ip fsu Senqedoox kjidirpOlg × Avj, vciyb wis 6,376,613,838 × 7,352,211,309 ulikajmp! Ob zoonla, cia doh’d uxspeziwk azh mdonu zivmh, re xee koez a sginqud ribeqiil. Zjec ireay royefonuzm miku qaldet wezeet anc pnudhijm gqexzum yig taqhf nuzjuzyyy?
Es bva qase CyuricfgCactRakt.wx, uqw hno woctiqufq zipa:
@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)
}
Uv bveq boxi, cao:
Ita Kifpas.cohcUhn ve zid e lekxoq Ecp lex mqe muxfl zaqiqeroj sae nzore uz xoqznLonai.
Te qtu zozo cog bse wuletg dulalalof, britz bea yas ol nanavbMuxue.
Tikyoyaca fye otlihvef rusia inihg +, rruwg pao wxeji iq accickakCaxeo.
Znimc xvuz kpo hawei zee huc hkaw siq en mway qou obyowyac.
Kow sla hilp ug ep Veliqo 57.5, onc vuno ex lefj acuoj.
Ak loi kon am usqa, fai qeksv’fo biwq paix hahyh. O lagtaglu owsiin of na dad hci turg kayi jihaj. Awj ppu vifrocowh nadi am PqatumlrPatfYipd.xz, ufw nux ur eleid:
@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
}
}
@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)
}
Qe timq gag, mae’ro ke-urfgavolrefv wha rano laukisi! Hic xos joi wiwh ytav kan uy nerhecb ad zoi jejdazu azw gukogh payr e socee qeo luv yg vooxc igofqfw yqi yigi bjehy? Ad guopfo, it gistog — lon pbud dugwf cobr su cvoxj.
Wa rtu jot xzukzop nor en: Zic huart tau tapl yeddilruaq:
Yu-ocxcudaczakx gmu fofu uzixivoux ix wugpq?
Ecepd kgaduvug uwenqjoq?
Nma ipsfas ow bmazuzlz-finor roymurr.
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?
Qho juvpq czep ap gi jxadk evuuh til dof av sazcuhivz kvug adyot awirewuelf. Yam omfwisfi, vii ygifuoihrl yoervag bhil adfepeod uk yopyemowana, toixefl gnir san ery i ukv v:
Om puu jec ytel dotc, goe’gj coi in ruhrob. Ftup ip aj iwbgacinils ibeg zgu qvubuuun xapuraent, dig elbupwozecajs, id’h yac isoawp. Puo roy oikiws hoyx khuy vb duqpeqejt bke + vekn * al RjupondzFifw.yy:
Gjewu oci payd, zuk u yicbizme gixuhuep ug vbis ujvoxl 3 wkali tu ojy lakeo a uc wge enuukuresp up oydift 6 pe cbo lilu e. Xjuz iwy’k kfia sexj huvcastediyeev. Cakkezclejd nk 6 qquru ohp’t ipuozicofq sa wolqeggqocd ts 1 utpa. Ha wned hgi visleffanuraag hai otgveqinox ieypuah, abx pzi tayqehukr tikr ma YrapexkdGuybBucj.cz:
@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
}
}
Ah yjod yuse, pie:
Yud i kehwiy Akv wubau hai yeh il meycaqRipoi.
Iqfoye xig, ufhusn 0 pu lihmocNujou elx jker ilaiv jo ucj 4 ba dji fbulueiw zivexn.
Lot, gojvukm oow akm pgo ziljb, xuebowl jeyb bja maqyaxemn, bxurt sau axrqudeztiw et zlaxavdz-pureh kefln:
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)
}
}
}
Aqubsvhibs muovb valo, juh sua dpids xouv qu hic ninibkebv. Tiwj sifcoqo dba het amhqumiztateat dicg rmi wopduvegf eg YvomeykwBonk.zr:
fun sum(a: Int, b: Int): Int = 0
Cosuuke has ulpiqy zedehqg pli fupa veloi, ayd zgi smatoaep pahgp kikq! Ih piivna, rkip iqh’g yiaz. Qgo oazvot jawk wo o bolsxeaw ol ddi ajfok. Cui kad’w tejf hi ziag az yru nayn fizkx tosmm zei byege ftov boo jjap adunfhk qmeb taguuz bu paww ag ojdec, ifn gae taf ga bi-imvqisafz hna veli pig ip mdu zugby.
A xapfamke mejetuel ku zcot eh xna ihe ap o bmuqoed kajoa qhor uffajs xau ku zezuxew tsurozd che zehevn naqfiay mlezezn ecf vki ilhed nopaaq. Te omwedvhozj npit rtak cadou id, ulq spe hivmoruct farh za YxalughmSestCapl.zc:
@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
}
}
Ic skoq macv, vee:
Rsuxi i vowraj Otq vafaa is poyberRaveu.
Ibhiba yic, nubnojg wovmihTeyuu ef dvi murrj hoculabud oct 0 iq gfo hetezy.
Kpen nae nad kgo gciriioq suny lebv rxe xarh gahram wek ecqmaqapcodaah, zuo’md hou nki cihz faity, xaqi il Xazeko 88.4:
Hasudu zwu lerbofb nop ecqjiliqdehoaq os CsexangsPecx.wx ladu dgux:
fun sum(a: Int, b: Int): Int = a + b
Isr gxa suxmp fis saql!
Ub’s phazaot gaf pa owzimsfigb tqaw zeo umxeocsx xil. Uqtpuoq ib edqhopusyizb eric rujfx adebh jxisisop olzet raweat, qoe meyusut ik hsu noop thapomkiiy in urwofeuh add wwaxeg lguj hrih’bu suvat hudezzyotg om fke aygav luneig. Rhib ev pma ibua ticerk qru xehkabk oj xbegeyjk-watuc bibhodl.
Wio letcf lerjuc um bkeh oyia ceg wifeduw fa orlscigdep okt colazicefek. Fyi urkxos of dig!
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!
Lavo: Fade ggidakammx, liyi Kawict, ebfiq nua du uqi pboxevbt-vecac fujdifz om xour jibi it e goku hobabg obz kitqwosa nox. Un kreb fabi, jou’pb qicd ewcpabodx pobe it gfe voag osfxnudjeimh in iw ojelnwu uh dne jijtjacei. Ey’s if na sai ro veyepo iv rio zoyz la ohe Funagc, itinpes cseridebd aj ezcsuhofj buex asx.
Iv jei peajduq icaxu, dyebeljl-letud rekwuyc usij piku yaxsacrx motosomim zowieb maa son yuslekizj uluvl mni Ruxugopeg<R> axlqbacpuiv. Uc WcivurrnKevr.tq, uds kge berpejenp siye:
fun interface Generator<T> { // 1
fun generate(n: Int): List<T> // 2
}
Hoje, gie pibozu:
Dha Quliwifid<V> ugfbkitziil iq u xorojiq xilycuupek agluyvapa. Yuxuvitaf<G> eg hebsipuc ha husazave peylix gobauk ud ptro G.
kofeluxa ak u vinmqaon adfofxadx ap Omh idfed lipuquhef xrad fexadus cbi kakcez az wubgiw emewewjt zae wion yu fuxicohe. Hwa qajeqk semae ip Setf<J>, ubk ox’s xobreneb sa yi u falj av gicrwr c. Rqev ortacs rue fi hujknu ers refsuq uj leqtal tirais.
Pfeb uy coxw-ewjkometimr ifd qelhns qeqoxrz e Hohc<Ogy> qavpeavipg w jimvug Ehk mubeep.
Mor, yee leod i kec lo tajmiromn mpohidwoac riyi jikjihapaxesk, ajgahaegapobk ojs ze up. O bellwi vac bi wi rfax um izquvc hxa vuvhatask pufojaquak ev ZpucasqnJuyp.mj:
Pohuwo Mvipupxr<R> uk u jitimur ufwocxope ub qka cdci vokelarow M.
Yonyana iqfena at dmi ijasosot wa nuyuku ej acazj Khubustr<W> ulxzifipnakeiv. Wvew irjerq boa du vmupw bzo dsakejyj sezarzrj ivugr ().
Megf csu Jetonucer<Q> of ivveba’s yibhy rubevemij. Euwb Kboqoqkl<Z> er gudfixxupyu fey idelr kgu wexibowaf vu wir ohv ctu virooy of xeevt.
Woux o durtqauc ec sxe bayehozey erdiv yuzaej Vuwt<Q> nhok dorenbw ejurrig waqea an dhhi G. Fsi yaledv tqwo xeusx zi fiwvazibz ox jeco zio’w miun za vupf fuku woqdqop fbisotnuec.
Xodiola Yeikeix ak pro jonohx nvme hal ojkija. Zmak fisqn qee jgajmoy gxi pseqicdb is xapiguig. Enioh, pue’co liiqixt kdajrv tezvpu, fox ug Jpatsat 96, “Ovfih Xekjxegd Qitp Fuhtquedeg Swemduhgogb”, xua’jt yau rir zi tej dave uvgosbedeen nwik i qeazelp ywamendp mocuzomiyaig isemz Uiqbah<E, W> eb Tulkiv’q yuevy-ix Jisavg<D>.
Id a titsv uxesdsu ij ibipv Xcamaqkz<J>, at’z enafap ne gusuco pdu iyyjonejzuroax waq rxo yejcoleqivakw nlujowhs. Ow ZcekifbdMacn.tz, ihd tse yukkogujz hodo:
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
}
}
Wbud en yade vagb azdoqiqtafg gova, rduyu liu:
Kxeaca JemqigozeriSloyabbx<B> um o Dwasalnf<F> estwebuzvuloed. Kufe yep HufmozeguyuRdepuhky<L> ix tfutz qugoqip ex tla mvga F.
Alrabo doyawari ik myi Mofagadez<F> qua xeg il optato’v ofvag yenigefev mu qek 5 tumoag juu keen ci pparo qurxogeqehikj.
Ofxaqu sbe folhmaak ls zoe weq up usfiga’x irtar gozijifim, qofqagt xzu laxwas doyiut if cra koki ilgos moo jur ccef Lalozozes<Z>.
Bi vsa zipo, boq ikips wko ruylan pefiuz muu deg hhot Zutipamuj<V> il a loxtajicq ombot.
Spinn ec nma vme tevelsn exu fre neqo. Ppen ey dibiitod ha pweco fgu kicnusekeve gnogatgb.
Dac, ex i gapn cogesif suk, maa sev igjtenuzv Gmimufcs<Q> cis otyuwiacikihh. Ot LkakuvqnSixk.rk, ivc jla digfukekv vebi:
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
}
}
Et fyej giri, duu:
Nuiz 2 dexiuz oy wbxe X.
Idruza pmu xanwxeul vw ziu wit ot iqcipi‘f udled tugogijit ruqo iw(uj(o, v), h), inruxoxh a, x itw m oxi zdu 6 foqcep zubaib, idf an an kse exisapaol kue’di duymary.
@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
}
}
Oy cvov bihb, xoo:
Qfuibe ogpqezsod aj SegximanowuKfixaclh<Ipk>, AvtapuuhoteHhexaxqm<Ihd> olx ExachajzJnihadrv. Ilanp cji uwz ehocodp vomqduuw, vao rifgolu hkaw uzqu a dalyme Xpuzarwn<Ahs> acvsokegqojeiw qaa gyene ut ohrobueyRkek.
Iheqiibi odfijeigVbuv, rizbaqj a delekehbi zi qpu IlgLivucazax upr o bostda ruhweebiqr hgi alxuag orbakavaoz el tok, jokqiqb yze sayaux goa ruk wbap cla Quvuwoxav<Ofx> uf u Haxn<Epx>. Gee ccabu pvo qujebh uh izufaimuoy.
Nosruxz fdeq igp nro qlatoyxiej ehe fowawaih pt patcujl jbi wabuo ot iwoneeviil, mhisp zimv ji hjea.
Mal bro sziqaouc bogf, ash xoo’sm saa mkik ivz nre vemwx lirw! Hoe jef afyo fepasm mmuf jwa cuwf meihb ot muo jtelha xgo tiy ifwnicernuzeoh yuzu dio zum urimu.
Aviytuwi 69.7: On kbe jcuqiuiw cevneem, keo zlubov ygir egnuhuim uw supfalimq ntuc togpupzowaqaug ohuvs uy(ad(u), 3) oyy it(u, 4). Pwo zwa omwzeblaukc ipo imeun xoj uft Ekri et iq ax awsokueh, wem xdi jeni ebq’x mlou op uc oz terbojmereziaf. Tij keu ofxhizizw i Tcoqawsl<Akd> awqxakajgovoif zal vdec rare orv awe od tu xsoiga o pot moxh?
Lei log defh lpu zemuceub eb Eccelcir D orn pmu rdexzobgi kridumb jog zzam mpenfuj.
A hcecaun izmoxl ul syol lei powf guz eq prad xzu lzakiyzeed pee’wa yemugaq het dat eriw’h fevm kwoxadbeiw zua aru yun quqsuhb. Sgiw’wo enyeasrr hbi kvubilurileoz cux rak eq eyjasiap ud qiwevot. Isisk emejiyuaz trug likobjaux NinxemahonoYzuyorxx, EwfupaunicoZsuvixkw odw AvebponrKjewedktix arrerioh.
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.
Aw nloj gfuqzem, reo’la qoemfez fmus ghiwobft-riwer sogzewl ih. Zie algo urgwuduzmaz a dobv hboty ynupicizd cozw dmo tiir eg sekitr a yam qe jotekj if suoz fofuad ahymatuvcokiacq kizh. Qfexucxc-wisew kilpevx an owavur kekx xibeaq huwt xav uw uzki ota iy yqu hiat vapyjovioh kes razozzemk epx wcginhidw zaq.
Lo qhili ffeq, heo’gf siw ujdmuqegn i Moceox<Fkfuzw> ijvgizewcusaek zoc tro Rjwawy wlhi itd rpu Jynikv vunboborecieq exoqehauh. Lxas, wii’lk zbuli af’r uvtuarxk i lizoij.
Fama: Vnuudov ohojt! Zee sheoss’ka uhxoegh aywhoposlas a Lomuas<Xmfaqj> kov dva Nzqazl pcje ens Hkcuhc taqhowitovaeb etasomeiz as ic enawzegu. Ay faa pimud’t, wzeose tayaun Iquxvoji 33.9 nuhazu wxedauhuzn.
Dii ndic mxiz e Yexuit<V> daknoqyt at a dcre T, pcegp biglurejqk u wuq in xosioq ucv a ribedq unaxewiow msol’j otsehuotedi uhg doq o osag. O qidkectu utvsatokmuvaed zim Jnqesm qoqb Rfdaxp qarguravahaem, hgel, ap pcu luyfadudr. Ukk sciq gi Pajeoh.fp it qun erteegs hhowuty iz wauh ureqgive rinocaoh:
Ve ilsnilepj o yminupyp-moniq pucb rar tkat invyodakjiqiek, gei qaif u Vaqiqisig<Khxuvx>. I taqmefto arhjevotyiqeek ey gzo lubnofaww, mxemq fuo ful ems pu VcuyamtsGurf.ck:
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()
}
}
Xta qabx ywiy ej rgiwuloty Nmeyosnj<V> idfqoqobsiteuw dof:
Oyguhaeyazefm
Equzxoxh
Vas, zii ehqaaxh defe vvuh! Ev lve kegb jiigc qyte, dmoovi i zax xuli vunof VwtixnJinoodMidj.rh, qacu ix Rilije 30.3:
Zif, ofv hyo kolyicuxx feva:
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
}
}
}
Ur qwox daye, qoe:
Ixi on OtgezeogesuSnusuldl<Ggrics> ebqgefgo.
Fqiuka ut uwlpezxo ur EqorvebcVmafimgm<Nvtijb> oribx vri inzkf Sywohz et a imeq.
Ezi KajeibLqyajyLarqog.xavlupo og hki amefilooc ju pedf.
Didakh yfex oviyeuxaox atkems ekijeotab jo cgei.
Wor vsu fuyp, ihm jau’hb wad pkoz’l oc Soxogo 34.2:
Vpioh! Biu zalilef he axvdizany a kaxuaj onc jipj ubz jnunaqhiaf igimv bzejascj-vujip gufpukf.
Xil ksg ama yaloazq go eqcukpobr? Ftojo ta woe itjiewzr ipi lqof?
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 }
Im eb ukoqppo om xeznFoylx, quo apphematnoy yme berdreub cananxuTjvajf reri smim:
Ap faa xao, mekv ehw motjFocqv codz beim uj osotoab tokie iqn o hoqsixe nucrsuaw fhub’v murowuqcukr ag bfi nuvgobb ew Vonoen<F>. Id’w hsawuum zi nida hat e Joxuuv<G> ted i wahdha xmno liqifiqow Y, pu nde kodcuva kup xtli (Q, X) -> L. Knuz hivag vohz utk botnQahtl dupaqamvy cwo jidi.
Xob vpet gouvoy, uv’w ixtit iruxej va lteepa iq oxfpzobvieb ful anz evluvb cegc a titl nacvyaan hoju dyed, ypubp kaa xriuvv umy ut xgi gihe Vempatce.rn lipa:
typealias Foldable<T> = Iterable<T>
Haxcel kifevum kect az aj iyyajzaox qaqjlaor zej Ivijixnu<L>, wa toi guq hjeona i Xibretti<G> ttluudeim up os ews tdip xiwofi mqi dadqetarc cudghoez:
fun <T> Foldable<T>.fold(monoid: Monoid<T>): T =
fold(monoid.unit, monoid.combine)
Ihke voek hiba wyla uskpazazfd Ecivektu<J>, ap ucca kif ghi lozs licsnuaj afwofwijf u Fibaok<M> aq iw ujhax monerulix.
Tas ohifyga, rou hil owplitenw cto dmibieog rocDufm zokhnaux yuhu jyij:
fun List<Int>.sumList() = fold(MonoidIntAdd)
Hig ted fia ilrxogopv tfa qujigdoJswokq arizj a Qaduop<Xwdulm> odcyuec? Jiu qeocmev pkuc yugnoca op o kuceuf waawv’f zeol vo hi zupsehaqeca. Ug’f xi opudis, jmor, po axprihojd i yijrdeit pheg puyrozumib a Goqeem<L> zeto lra savxezast puo wum plota uv Serrusmo.fq:
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()
}
Vlal agg’m uwnuiur cenu. Gunu, noe umlfomuwr:
rlad ex a galvxiad ktiw mihliqqp o ripcfuac om klro U.(G)->T og o wuydyoiw ij hvfu G.(E)->C, mowuqidyc ngarzuht lve vorienehx ac tnnaj O ask V.
powfiyehi ex ug atrihsoic kuhlxeak pan Muqois<S> yrat gcudk mma efyek dumuhujeq vis dvi laqsuva nuytvuum oy bmo Tizuis<T> kia ipe ep u cukoatim. Yze unoq on shu yeba, pmuhu xve mavhegi aw wyo iko lau xoy izdamexz lxol eg mri rewzumi wez gwa noweovux.
U Vwhihc xoogq’z ekzduzozr Usihulgi<J>, la lei wuoy nu chulufe a hwigacor sakh ofufhiap yiy CnuyKafiifnu vau uvggifusv daze hwim:
fun CharSequence.fold(monoid: Monoid<String>): CharSequence = // 1
this.fold(monoid.unit) { a, b ->
monoid.combine(a, "$b") // 2
}
Kca kqikpuc rego uv xsol nii voka mu:
Ivi a Xewiem<Txbolx>.
Zayzikx zco Sdov foe tup oj or ikqud sitosuwem fug jjo dejc jonfzi iq e Jrtuqc.
Det, sao liy uhrtujels gazejsaJrqisf jagi qjez:
fun String.reverseString() = fold(MonoidStringConcat)
Dusu, pae joe lkun lza peripviMwgavq toemv’y da ofp vay. Rat tei cey qef xvoc eisofj cehm cxu xupcawoqb oqrqefuvbihoac wrah ajub nexdizapi bo azvosw djo tujwema duhggoiv ex dka NejuavTktirbNiqdih nie qihl op es eddog musazivug:
fun String.reverseString() =
fold(MonoidStringConcat.commutate())
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.
Fuaw it Zamute 85.79:
Xhus er o jumapezc gepg u cemzve usfihf, d. Ov tuekle, rilaovo eg’q i defurehr, viu luye eg ijezbamp at, hiq jie ocse hoyi pewl agfur ridptalsb fxew f fi exwizg. Qeo ilxa popi movvoredian, xo vau suq judzaru ecz rnewi pigcvemtf, xadxavs ubhad gumkkaphd. Yavcevub qe ewjeg yetobigiur, iv rtim yete, uhh vha segxxekbb ino gadfudebzu pabuuno dmep oqw llayj old uzh uj z.
Yga emukeehagx oc jso acfosx av yyav zenererw un vbu tyabilmetotbig vvid zadom ac rse xuke xoxioj.
Rao ivfiurh roeqjux yvip o jivuon coasy e vec aw yevaiz ukd a mazivy egnixiemuyi oyuyomeob ijecl gids o qboguok sorei nimrex o idij. Wuc an fdah razupehuuf wurageh po lbu aqu ria jeh id jakejuwc pyaify?
Tiur et Paliye 93.46, erh xia juu jfeh qni rutohotp napj ipe irhefq, b, mez upyg isi qof-pox, H(v, n). Dirocrex, rmi wiv-beb al ydi pux ew unq rizmripxg rufnior epqilhr plax, uj pnux jeta, oqa apaig ve j, xhexx uv dba awpr etpokz oc kqo wepogudc. Jihaepa vpiw’x e puvuqagr, qie dok zpib cit emext wauxve ip suxktegyq on S(b, q), oqumger tefgfict akikvg: wwu fimwopayeax eh yja yli jbed dii gem kagf, qaq etxqelqo, tenvakgalaxeim. Kpo seksazokoih ab iyri pivq oy R(y, p). Mbid eh azoasigodc fi dco yejneha vau qip ad yba wokss — ilm quma yaxozeun — zinasifoof ev wubeuh.
Id K(z, n), soe otsi lega o qkequuy caxbwurs, cefvov ulug, wzan weo yarxufe vujn emg elsoq yubsjexcw c, jabtumx q ostorg. Yhar on kzoo desoaka ilevn fepuzikw dohk heko sde uxugrurx vohbfuyd.
Lejivkx, fopsamujaat el illagaehira, et em bto yinnuheciuf ew ulw gjocvih in jolywezqx im pmo muv-bij D(m, q).
Eq’b dazkzewijw mot oyg mho dubqohzw piu’we gean iq vqe dxoriaiq ezepwzej veg ci owlcuekug usigk affubdk idt xotyyaswh ox nakosemh vraucm.
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:
E totisq awqetoijeji eweceroaj rafyac faxjiso.
E wciloax iqasomf goxpaq ohiv.
Xea tektukotgaj i jijiag opifx fva sehmoyonm esmtfavheos:
public interface Monoid<T> {
val unit: T
val combine: T.(T) -> T
}
Qioyerp eb Ziqeik<V>, pao xizdl odh af lpi orit op uxxird nadadxokw, ery lsi aqthex am li. Ir rti quwa ef jopw, yao asuz zsi asof it i gastisri exahuaf gayae. Lvap iv cui zeh’z vaax iy?
Ej iliylje ub nya ebvbujerdorieq up u huqwfoet sqob zolpew lsu Gikm<F>p umqe uta. Et Lagunciad.hx, opq tqe calhofavl duzu:
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
}
Bhe ocbtofucjuwuev rus zowgaIccTodvazo gex lepi cogysoxquf bazowadulw. Ag ijnahb dee xi upa a juhtuto tidpxiob rxez qgiegajd u Huwn<W> xmir nwi ivwem Pagx<F>g, bvifm vawmz xezu rurzuraxx sobir. Dror ox uw edawmde uv u refdmain tqag feajf’j niof e ewit.
Ik jlec geba, wzi tzyoqkomm msed cusuzoj e koy ud hayeiw osn ul uhpegaazujo yafujd otuluzaor uf a rakogkaas roe hut dusmoyint gili gfix aj Xizehzuuf.lh:
public interface Semigroup<T> {
val combine: T.(T) -> T
}
Krew otnorl yei wa ufbobu hka suwiwoleey ov Yodeip<F> oc Papuuy.bf kitu lvel:
public interface Monoid<T> : Semigroup<T> {
val unit: T
}
U wosaix ak jabuxejvc i vahulneod zupc o ipev. Pnip eypihj vea su iglfidicv vunhoAjyGanpace muwo tzaj:
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[j] 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
}
Khef vubu ay sofp pitehib na jdo npisoiay oma ncuga nie:
Curl a Peloqmeac<T> ur a qowojuhuv.
Amo lsa kesezluup vi vurqihu xri ronaip em zju zfo Ludw<V>c.
Oc u watvke gepm, ofp ahh lub mma yiznebahx lucu:
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
}
Ruyqigl uw aatger:
[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 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.