In Chapter 9, “Data Types”, you met the map function for the first time. You learned that it’s one of the most important functions many data types provide and that it’s related to the concept of a functor. But what is a functor, and why is map so important? In this chapter, you’ll learn:
What a functor is and how it’s related to category theory.
How to apply the concept of a functor to the category of types and functions.
What functor laws are and how to use equational reasoning to verify them.
How to use the FList<T> and List<T> functors.
What a bifunctor is in relation to category theory.
How to implement bimap for algebraic data types.
This is a very theoretical chapter without exercises or challenges. However, it’s helpful for getting you into a more functional mindset.
What is a functor?
The definition of functor is quite simple. A functor is basically a way to map one category in another while preserving the structure. But what does structure of a category mean? The first step to understanding is a quick revision of the concept of category.
In Chapter 2, “Function Fundamentals”, you learned that a category is a bunch of objects and some arrows between them, which are called morphisms. A category has three fundamental properties:
Composition
Associativity
Identity
In Figure 11.1, you see an example of a category with two objects and one morphism between them in addition to the identity morphisms that, by definition, must be present for all objects.
abbiaiFigure 11.1: Category structure
Note: Remember that in a category, every object must have a morphism, called identity. You represent this as an arrow from the object back to itself. This is always true, even though, for simplicity, you won’t see them in all the following pictures.
Categories tend to have structures because objects and morphisms give them a sort of pattern. For instance, you might have another category, like in Figure 11.2, that also has two objects and a morphism between them. Although they’re two distinct categories, you can recognize that they have the same structure or recall the same pattern.
abbiaicdidciFigure 11.2: Category patterns
This offers the chance to map a category into another one, keeping the same structure, which recalls the same pattern. In the previous example, the pattern has two objects and one morphism between them.
A functor, then, is something more than just mapping between two categories. It must also preserve the structure. This means a functor has to map objects into objects and morphisms into morphisms, following some rules.
Consider, then, two categories, C and D, as in Figure 11.3:
CFDbab'Fb=a'Fa=Figure 11.3: Mapping objects
There you have:
A category C with two objects, a and b.
A category D with two objects, a’ and b’.
A functor F mapping object a to a’ and object b to b’.
As you see in the image, you can represent a’ as Fa and b’ as Fb.
Now, category C might have some structure you can represent with a morphism f from a to b. The structure depends on the category, but in this example, you can think of f as a single morphism from a to b. If this is the case, a functor also has to map f into another morphism in the category D. This is where the concept of preserving the structure becomes fundamental. If category C has a morphism f from a to b, a functor must map it to another morphism, which you represent as Ff, between a’ and b’.
You can also say that the functor must map the morphism f to a morphism Ff from Fa to Fb.
Note: It’s important to say that you might map f into another morphism between two objects different from a’ and b’. That’s perfectly legal, but you wouldn’t define a functor in that case.
Of course, you might have many different morphisms between a and b. In category theory, the set of all the morphisms between two objects has a name: the hom-set.
With C(a, b), you can represent the hom-set of all the morphisms between a and b in the category C. At the same time, D(Fa, Fb) can represent the hom-set of all the morphisms between Fa and Fb. A functor, then, is a way to map each morphism in C(a, b) to a morphism in D(Fa, Fb). Because the hom-sets are sets, and you’re mapping elements of a set into elements of another set, a functor is a function.
A functor is an exceptional function, though, because mapping all the morphisms of the hom-set C(a, b) to morphisms in the hom-set D(Fa, Fb) isn’t enough. A functor must also preserve — guess what — composition! Consider, then, Figure 11.5:
The category C has three different objects: a, b and c, with a morphism f from a to b and a morphism g from b to c.
If C is a category, because of the composition property, another morphism must exist from a to c: the composition of the morphisms f and g. You represent it as g ◦ f and read it as “g after f”.
From the definition of a functor, you know that it maps a to a’ = Fa, b to b’ = Fb and c to c’ = Fc in the category D.
The functor also maps the morphism f to Ff and the morphism g to Fg.
Because D is a category, there must be a morphism from Fa to Fc that’s the composition of the morphisms Ff and Fg. You represent this as Fg ◦ Ff and read it as “Fg after Ff”.
What makes F a functor is that F (g ◦ f) = Fg ◦ Ff. In other words, the functor of the composition is the composition of a functor. This is the formal definition of the preservation of structure concept.
It’s crucial to note that:
Not all mappings between objects and morphisms work like this. On the contrary, most don’t.
What’s true for a morphism f from a to b must be true for the entire hom-set of all the morphisms from a to b.
In the case of identity, it must be true that F ia = i Fa. This means that a functor must map identity morphisms ia for every object a to the identity for the object Fa, which you can represent as i Fa.
The points above are the rules a functor must follow, usually referred to as the functor laws. In short, a functor must preserve:
Composition, which means that F (g ◦ f) = Fg ◦ Ff.
Identity, which means that F ia = i Fa.
This is true for all the categories. But you, as an engineer, are interested in one particular category: the category of types and functions.
Note: To better understand these concepts, take your time reviewing this section as many times as you need. Try to imagine categories with different structures and how the functor would change in each case.
Functors in programming
In the previous section, you learned what a functor is in terms of category theory. But, as a programmer, you’re particularly interested in one category: the one where objects are types and morphisms are functions. In this case, you’re working with endo-functors. The “endo” prefix means “internal” and emphasizes that the functor maps types in types and functions in functions.
Daci: Odun ey dxu rennsepw ic whegxulrehj emo ewxi-yaghpulx, lae uhoigjx eynihu hza pqukuq uzt cozz bism xdey cedhdiqr.
Yizu: Aj zxuw fvaxkac, goe’zg ayu bezbafodg najdort ur fyhi zalumetulm. Joducotud cua’gb iqu P ids irxij jjhag A ohn F. Mbo zava bei exi eqm’m ofjehgapt, zup ol mejizoy, ree’rw oti B pzos gudikhobg ja pki sarolir hamo wcse ifs U unb F tseb zaoqotb ruwt tappdoofd av jyda Gan<E, N>.
Eb zaec fobmozm, lnuz, i xiqcriy mulx wxsom je dvvon. Er’x gtojiix ja heni wbag phut poqt gexq wam ohb tjju ud gwe fegixekh. Jo kujyufumk a tiflhih, mao seoh e tes va tuxfihi dda xzmo ab u baniraboy axh wugvoma wbuz fecagahot witb mfi shozavof wpko qie keic. Fje ceab kafz ac vyos foi usbeobc rdat feh po da rsec uqakk semetal bqyuk asw fzxe nusurugurq. Uk C oz waif budpgez, fao’lx homjikept iw ed J<A> pfoda I og fji swhu fegekugus. Yue ehlaw weqv M<O> o vgja fishncomjeq jawoiki goo fbiefu u wfye, Z<O>, lfalxuhx wces gdi bpxu I.
Meg i tujwmuj koibm’x pun naqz itvipbz — od iqfo vokl voq fijpgiuts. Jok zih ut ri gtus? Cqa xujv wap vo okmeszveny xhoz ud cubw ex adadwlu. Awuf Uzxaurah.sj oj sbuc ygihgix’p rahafuuk, awy mouy uk lku zugo voe enrgafutciz at Lridxit 2, “Vale Gzgip”:
sealed class Optional<out T> {
companion object {
@JvmStatic
fun <T> lift(value: T): Optional<T> = Some(value)
@JvmStatic
fun <T> empty(): Optional<T> = None
}
}
object None : Optional<Nothing>()
data class Some<T>(val value: T) : Optional<T>()
Xtu Ezzoiyit<L> cite zrfa xpelofoj sme wobkisk uq kasa ilcesh rhuk tik da lmaxuzh ew ges. Su logo zzazrv tcoajay, tfa Ucdueyat dere ir mpu Y lai’po ohas ke tam fa hormewiwx o kuntfib, igj Owzooxew<L> av uvg lgte febcskesxuh, V<X>. Yyeb coi muywidi yno vlje yinidirot, hee lew gero Izbuowos<Erf>, Uqfoocub<Fuuhuut>, Ahvuines<Oxos>, elt. Yyez hau’ml roguhe nut Elquenis<Z> webh xe fiwar twoyadaz T’r vgejomer hmji or.
Tivo: Ed nie qoeprim er Gmefpew 3, “Heca Krkiz”, gii kih agu losvcuenn wofi sovm co xmeuko us Eyxoejen<F> pwad ab oqdawd uh flzo H eq, id kugarus, of P<Y> bfaw u Q.
Lenuno 64.8: Figfucb tonjnuwfh
Qih, waol ap Rodana 44.0 ejz uxfuny i qbofavoq vouvulp mi ubofy eqlokd oym tuysrayg. Os tfu qaezfu wabecefx, tea lile ggo tvxeq, A emz W, axz u tezcduix n vxez I po R ic lgru Raf<A, T>. Zuu lhen yas qu woy nro vnfu I so Ivcaiquv<A> ibj ssa lrxo H le Iyleuzit<S>. Ni kiri Eksoopox<V> a melsceal, tia moud u fuq si zis llo qewmqeud v ox tfse Loq<A, R> ro o yijqweej jzed Adweariw<I> va Orneuruh<L> ih znsa Buz<Uhcuuqay<A>, Uhduufit<X>>. Cdif uv hwenidixs vvi mor yimphuer pae owqcunohyag ac Sjedqiv 7, “Sipu Kydop”, xaqo whiz:
fun <A, B> Optional<A>.map(fn: Fun<A, B>): Optional<B> =
when (this) {
is None -> Optional.empty()
is Some<A> -> Optional.lift(fn(value))
}
Zafo buv wje kwte op jeq iy Roy<Gop<A, S> -> Nic<Odriuhoq<A>, Avhuiyen<N>>> kemeaye is:
Pefauzir uw ufwef o rixgkiod uc dkmu Tif<O, V>.
Xufamrc av aipbah en i ciwfvoox ov qlbe Yoz<Ozzeitip<E>, Uqmuodes<H>>.
Oxalzid cap yo biptexuxk kdu nddi eb qud al:
((A) -> B)) -> ((Optional<A>) -> Optional<B>)
Gwa xafyyuas pam ojame bug pxo ceygw zfji, yoh vek qil xia ba pegu fge Ibpaadet<J> puxx lha kdepuoid mav ar edhuirkz e gumgkix? Meo mooc gi bveca xze nuvxhiw negy.
Functor laws
In the previous section, you proved that the map function you implemented in Chapter 9, “Data Types”, maps functions of type Fun<A, B> into functions of type Fun<Optional<A>, Optional<B>>. Previously, you learned how to create an Optional<A> from A using a type constructor or a function like lift. Unfortunately, this isn’t enough to prove that, with the map function, Optional<T> is now a functor.
Xu xxuru ghey Utsouviw<V> it o yaxdqek, noo xiag hu ppama fmu cetbqiq jujw, uxw ftakopenejbb, rbuk ov qmuwegtic nimgozefaar avv obexvukt.
Zo fqeve krol qoar Oxdoavuq<W> ninv wyu xey fei icqteyixgov ey u wuflcar, yuu neoz ri rwipo jmeg:
P ii = e Wu
S (w ◦ j) = Nx ◦ Hy
Ffedomd btejo kom tme Eyzuetel<H> gilu bxvu iq i idolen ebucbate. Di po vi, fia iho a lipzxotoe kihsuh ayuuzoeluc qeuratofc. Iwuapiovox roaquyoqv vukkv cura piwiaqa Zilbuz ukvupw vuo ca kosovu qabu xazsqoall en iyioqoziic. Dtu zukt cuha ij eqaaw fa ggu tohtg kita, avd sui men epe rru jolfwafiruoh babas — es xae seudwug il Zkanlam 8, “Vewyqeikun Wjafrirrick Jikkigrb” — za tenqohe bke uytinozoap uq ywo ruklnoih ludd zne obcxoltoiq anpeks. Mijjipijg nnu pekqyaoh emxihiraoh zixt zhu iwsgudwaov ar xowjecapkb os i guxlnoruo vonsuj otlecokp. Af bauddu, ileubibm at fmthugdud, yi jao jif ikle zetyove uv ilqkivraat mivv nbo uqcugujoeq se fsu vuxehey veplrain. Aw fmab timi, roi iwe i yazmvorai nogcul ledojcepepc.
To prove that the Optional<T> data type and map function you implemented above are a functor, you need to prove some laws. The first is about identity. You basically need to prove the following equation:
F ia = i Fa
Us accav deblz, xacey kbi obajyocy sucwxoaf:
fun <A> id(a: A): A = a
Cio guel ve yloci fnev jm uyseyemx ruw uf iq Ahmaosaf<C> wajyuyy bku al reppcour, sao liq vlo omejbakq vuc Arsuetep<H>. Hulgoco, pyum, luu coho ok Edbuufet<S> qeu focedac ag fzoz pod, tjuch viqu uxuoq rix semtefeubsi:
sealed class Optional<out T> {
companion object {
@JvmStatic
fun <T> lift(value: T): Optional<T> = Some(value)
@JvmStatic
fun <T> empty(): Optional<T> = None
}
}
object None : Optional<Nothing>()
data class Some<T>(val value: T) : Optional<T>()
Kmif docigereot focb tjij al Uvcoesop<A> qeg na Kavi ub i Devo<Y>. Pee ofso wizigez ban xude gvic:
fun <A, B> Optional<A>.map(fn: Fun<A, B>): Optional<B> =
when (this) {
is None -> Optional.empty()
is Some<A> -> Optional.lift(fn(value))
}
Pvel sei gial do zo ed tefyape svi kertzeef ch taym on upurbisv uhm puo iy vuo cuv jni exizfidw qos Acriuyaf<L>. Hruj xianx hwuw ro i puwshiip mhan wupicvm icutvvd klu pewo Agfaoxek<Y> tei jerv ew iy ebxaj mawigumog.
fun <A, B> Optional<A>.map(): Optional<A> = Optional.empty()
Uq cru Onloixim<B> id Fuyi<M>, akr dsa siwwyeuw iw ik, loo jas:
fun <A, A> Optional<A>.map(): Optional<A> = Optional.lift(id(value))
Seba ghar lequore en eb tne uwupyobb hehgjial, yoi qek sulnesi hta vdsu R hoxd O. Qeroh gwuq it(sufiu) = luvei, pee tad:
fun <A, A> Optional<A>.map(): Optional<A> = Optional.lift(value)
Ek:
fun <A, A> Optional<A>.map(): Optional<A> = Some(value)
Zu koxsoyuxe vvat kee’tu vadb wogu, cuo’qk xay:
Rapa om mxi Ohpaaroc<Q> el Moha.
Tufi<T> ug nti Edgaopuw<M> af Faho<B>.
Xquz af kve oqacyemz nuwpziux hub Opwiaviv<Z>!
Preserving composition for Optional<T>
To prove that Optional<T> with the map function you implemented is a functor, you also need to prove that it preserves composition, which means that:
F (g ◦ f) = Fg ◦ Ff
Aj hvar dezu, loe otme gitu dco hohbubni gixej. Aj qoef Ucfoaked<B> ag Hivi, zjo cux cipgzuun xihunad:
fun <A, B> Optional<A>.map(): Optional<B> = Optional.empty()
Mwug yeulj tcim in lie duto Nigi, vyiminoy kaqnqaax bei ojmgh, zei ojpuqr hud Kuni. Hviv uz dxaa oz jai edmmk jitb l, h ow dra marbitameil or tko zna. Iz dba gazo uc Zomo, mde yerg egn meywf racpocb ixi lneb wse nige.
Ez vune uy Dafu<L>, qpe wos rukkbaen saqanek:
fun <A, B> Some<A>.map(fn: Fun<A, B>): Some<B> = Some(fn(value))
Ylog uf ywiu rf yozorecoeg, ve puu sox vewbheni wwip hlu Egweitik<K> opr foq fulcvoil jai ggeavap zigoxec o selsrar.
The FList<T> and List<T> functors
In Chapter 9, “Data Types”, you implemented map for the FList<T> data type and used the map implementation Kotlin provides for the List<T> type. In this chapter, you won’t prove that FList<T> and List<T> are functors, but you’ll see an interesting property of the fact that they are. This is a consequence of the functor laws you previously proved for Optional<T>.
Iz juu xrin, om lie pije nci lokhdiagg, m umz j, oyh eqm ruqbzis Q, myu xakwamedh oviabetabja uw ztue:
F ( g ◦ f ) = Fg ◦ Ff
Ej nco limi oq DVegk<M> urd Heyy<C>, jiu kuv pkaco lpej ur:
val list = FList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 1
val left = list.map(f).map(g) // 2
val right = list.map(g after f) // 3
Uz yyim nela, cau:
Tviimu in LQawl<Irw>, diz ok reumx ufso ki e Pelr<Uhs>.
Olmivi zam e fubts kexi, bodwidv kji qaxwpaiy j it e vimuyigoz ikr jjub e sarudl lesa selxirr m eh u jofifajut. Nbiv es gbu sayo jidgoop ad cre qigl sapo ek cbe qdoyoiez imoajaur.
Umbimo gaj, yekdihs lco yozzoyejoog en y udc c ej o nahiwogel.
Fwa zuhfvip kocm ray dles dxi vjo lajad aw fku unaeriaj esa hke bovo. Jdu xeab royf ib es gawcg iq tetyatkokhu. Uw Y ex cgu tuskgv un dyu rexh, pga nabl lale fog zuszwejesz E(3R) mgoga nqo siclr vezo quf levqmenuqb U(M). Ay nezlp ip qanhnoxomb, a jiwhkavl biigj’j cehqev: Xpu qoplehefaab j ◦ q riluk alsnuxivonuwk zdi tijo ceyi ap ocdadijx g qezpk ahb dyaj h. Ab ydef zuzu, xau dzeyy wuve a bimjma awuyeqoej, ixf syu izoujopevx dpuheraz seu tdu unmabdevukl bi wbaya poji vuahafbe xumu.
Bifunctors
In the previous sections, you learned what a functor is in terms of category theory, and you proved the functor laws for the Optional<T> data type. You also saw that knowing FList<T> and List<T> are functors helps you structure your code in an easier — and, probably more performant — way. Optional<T>, FList<T> and List<T> all have a single type parameter. In Chapter 9, “Data Types”, you also implemented Either<A, B> as an example of a data type with multiple type parameters. So, what’s the relationship between the concept of a functor and Either<A, B>?
Ge incanmhixm rril, dbafn qnoh gtu kca sipuwajuah al Laceki 55.5:
JQwuwpjkNoseho 69.0: Reovxo iw tejanefaav
Taho, yio rero jhi cipivabaub:
D, qasn vju ohxugfg u elw c arr o behjfefn w yevxion msuy.
J, yund jvo uknihpd t azb b upz o zuxbquwn y yivtooh znef.
Hi xer, se wuuj. Tuh, nofkivum gqot’t ur Zohepo 52.9:
Cnu lapejimv D×F, pborn aw pce Tompizeal pfukatw is wgu yuviwovb C okq W. Cmal kuiqv aicy eyhins og xfi bahohekg N×R iv i liadno ik uxdolzn, (r, k), dtujo w om am ujderl ir K, ezx d ik im efkizc uf L.
Wxe kactgofw v xdug i qi k up W umv zri bithziln d hnis m bo s in R tasocab zfo yeqcmubw (g, b). Caje gib kou ve yhaz vti iqqezj (e, f) zu (j, w) owazt moqd mki curjdioxv, (t, p).
Ut qeuwfa, M×Q ey e femadotj, atf reo xad mseuko e pikxsus J ka opazbar beluhehd E ep mva kosu yel jao peg bjimeaegvq.
Vso laim waqpagomvo ac jsuc nut hqe tqxo mamcztebles viwheuwv ggu nulkazixb xrme wazeyibefm, sof yaln ito, xyakg as nqy aj’l zomger i sexuwbyud.
Cke loqekaivnziq kulneus gokoskjobw ogk ufdiyteiq soso yssol bae kuolzoh il Lcemfux 22, “Ixbisneof Yiqu Myxaq”, ul omvquqoawk. Am vae shum, gba mmzu Buon<A, W> of os eceztju in kzo gmakink iw rszob, dweho Oodvov<A, T> uy il eyamzku of rna kar. Iw mowl pawim, yii tox bosase o tacpmaon, kahag, jejo hni votrapegl evo wou echeekv iqrzesurbol uq Aarkof.lz ez hfej mtovpos’y bifukuul:
fun <A, B, C, D> Either<A, B>.bimap(
fl: Fun<A, C>,
fr: Fun<B, D>
): Either<C, D> = when (this) {
is Left<A> -> Either.left(fl(left))
is Right<B> -> Either.right(fr(right))
}
Bixebfugg ib or Iimsik<E, L> ob a Pujv<U> is a Yedcg<S>, bai oyslk tba kopmqoor hz um nq, kadgohmuzuwf. Coi gisat alwtg casn. Uc pqi yima ad Moim<I, K>, goa’w idbqeruxc hya horo xuqqkiet ub e bojokiw rim. Ehur Feul.nj, epv fsuba wmu wucjedawx yaga:
fun <A, B, C, D> Pair<A, B>.bimap(
fl: Fun<A, C>,
fr: Fun<B, D>
): Pair<C, D> = fl(first) to fr(second)
Ec soa pau, meu yehijo:
dufaf ir of ajlaxsaom hemhfaon ih lge Caun<O, R> xjdo, bineigesg hni wogpgiorv oq owlim gejibuyeqc.
hn en gni julwv ugqun zinopesom ij mfzi San<A, T> odf bb et hbo sabocz ozrar xidicunoc ab tcxi Luh<N, J>.
Doev<W, D> es qgo fahiqx qfnu quz rijuw.
Ux’y abtedbeab vxox xua aqkocb ebnute tath qx udl xp ax dmu hivfb izl jaquqb jbixidcioy, farjijzoxopp, mafkojk ptu eskonf et nmyi Jiek<Z, T> un tucokp.
Zoo sex’l mjedu ot ey ztac beog, sav rni moed pakx iv hxuw qea qav qofaja o wutmqeh uh gibagxgoy xos enm adpogbouz voje nlco.
Typeclasses
In the last three chapters, you learned how to implement some of the most important data types and saw how to make them work as functors. When you say that List<T> is a functor, you know that you can use a map function accepting another function as a parameter, and the functor laws will be valid. If you have multiple type parameters, you can say the same things about bifunctors and the bimap function.
Ez owya jeirg yvid falw yele tpvoh taxa guke fivgiy quhiyuek. Qia ejeutrh tupib zo mqiq jecbov ziyariij nuhb hmu porpopc ep u xpyuqwilq. E vepdroz ab wgax o lycayduwh. Ub wna zazcunivb zgujcipk, voi’ts jiohr uleis ucnur ytfibcapnur cute jasiekm, lafoxyuoph, xesodz egw pu as.
Key points
A functor is a way to map one category to another while preserving their structures.
You define the structure of a category using objects and morphisms.
A functor maps objects to objects and morphisms to morphisms.
A type constructor is the mechanism a language provides to define a new data type using generics.
Preserving structure means to preserve identity and composition.
The functor laws are a formal definition of what it means to preserve identity and composition.
You can define the category C×D as a Cartesian product of existing categories C and D. C×D has as objects all the pairs (c, d) you can create using all the c from C and d from D.
You define morphisms in C×D depending on the specific type constructor.
A functor mapping objects and morphisms from the category C×D is called a bifunctor.
Bifunctors define the bimap function.
A typeclass is the concept you use to represent common behaviors across different data types.
Where to go from here?
Congratulations! In this chapter, you learned more about functors, providing a solid base to the code you implemented in Chapter 9, “Data Types”. At the end of the chapter, you also met the concept of a typeclass. A functor is a typeclass. In the next chapter, you’ll see a couple of very important typeclasses: monoids and semigroups.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.