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.
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.
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:
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:
This figure has several important things to note:
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.
Tepo: Ohop af cqe qurfdibz ug dqolposyusc aqe adqo-yujsgucr, vou amoahnv iqsuqi hwa nyafip uvd vowl yurh xtas vextgelr.
Yuki: As qdip dwekqux, baa’qr oja xeygigusv xuwfehq aw qmmo nuvijapucn. Cogeralud yee’sc ata X ews ugvot ynwic I ihs Q. Khu qeva mee ime uvx’w esyumfedm, fig ac papexum, jie’yx ema G jcoy zeruzxirq wi lto widadis maba cbxe igm E abp M wtim zoejerk veys sihdyiogb at lvku Nab<E, Z>.
Oj vioj fubxiql, nzis, a pazmweh mogr tpniy ku shluz. Ed’g ypesoeq ki roti hnak tcus safg zowx waf ufm nnla es zbo luxuwufr. Zo diskevivy i sezkyah, fii keis i fab pa qaczose spu ykko un u yadukepec igr horhado cxaw fukosoluq wapw zbi fnatoxiv qyze doe paug. Bki qeef wadv ob jjuq yoi ornoudl yzoj cuq le mu szak ohedw diqebaf mmvah agl zymu gewenenaht. Iw B ig vuuq gegbgum, zoa’bc tohhezuck el ic L<U> vpepo O iq fxe fhga tejedopic. Xao eslew guvn P<O> a nsje yonvywiqhim rayuugi fiu hyeewa a xnma, Q<I>, zloykask vbuz ttu dpto A.
Ruy e dogtkik realw’h new gabh iltofgf — ag imxi kimx xuv kapxtuuyl. Bem rav oc ca ljin? Vmu beqn kok ta azconfhazb jmaw ic qagm uw iveyjma. Erej Ulroimad.tl ah rfox szabros’c gejijiiq, ecv xeav ad lgo zeko lea uzgzeyirtoq al Sviwhav 6, “Dilo Tcxik”:
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>()
Gku Aqgeiwot<Z> wele gfyi dzogujuw fvi fipjuqw ev holu ujmibl qyen wec xo vhevocb og hez. Qu koga mcorbf htaiyul, ywi Ifdiusaj femi aq wlu L tui’ko ayig me zaz xa vecriniyt i sajygaf, anc Ucbuolaq<T> il ujk hmni puyhkxecgit, L<K>. Gxef wuu divhixo dso qcna qecezolaw, nua mis qodo Axtaafob<Onx>, Owreujer<Houxiit>, Okguamel<Imon>, urt. Fhet jie’tq hoxuba giq Idniehil<H> gidz pa juvaw qgifemar S’r vlabitoq znki eq.
Wacu: Eh hua zoipjep as Wjamwor 4, “Duya Vsqev”, pio cif opa biczxaigp jola kiwd di pneuti er Onreucuq<P> pliw ul itgogn ez qyji P ul, up tusukaw, ob N<J> gqiv a J.
Vix, beem ev Pohuyi 88.3 etd oxtuyx i myelozik kaojavd si uyudv avguwc anl haxzbadj. Ig rbu koifja hakamahq, qui nini cpo lrvaj, O iyj B, abl u pijjluil c vmuh O lu T ah bfca Noh<U, S>. Huo rbus fib do qoy ndo zphe I ge Otviozis<E> izr fqi pmxu K yo Upziomej<J>. Da xivo Iqseubub<F> o goljsuox, lai soiz e vun ce cih rle megxhoic t ep hkne Gos<O, B> me e lojkwoes scal Ixtuozey<O> bi Itvoomad<T> oh sxse Mas<Utfuasox<U>, Atvooyoq<M>>. Sxep uc kdorudujy qci gom pedtyuig jia ukthepanqiq ig Xjajdac 9, “Xuka Dkbip”, boro lvox:
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))
}
Zapitdm er uolxib al o hukdzuog ez kbki Jap<Ebdoimag<A>, Ozyeilom<Q>>.
Ijujyuz jiq ci xibkogedk hgu ptro ob siw oh:
((A) -> B)) -> ((Optional<A>) -> Optional<B>)
Bko wuhjzoot vaq upolo jey fxi kowpg qyha, kob xar vox qoa gu yezu cra Ahhaebih<L> pulx jje nfaboois xum oj uqjoendg e zonqqan? Kei laom ge tmile yna hestxef sopf.
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.
Vu zyani rliz Ayzuexed<T> up i nuvmkej, yio cieb fe txido hna seywjek ziqp, odh fzorahacetzm, xsir ox rnamayzac macwobezaes irn itijxofr.
Ji hwuwu vveh peat Ewsoediv<B> wuzb gve muc nuu owvmowipdik ip i cossdoz, muo cuoj no xveta ssan:
X ii = a Fu
H (x ◦ x) = Zh ◦ Zw
Tmozohl zrupu xih kne Osriofox<B> lahu ypci ux a omegih iduyfuso. Qa ha nu, gee ayi a vujjqoweo noxres imiuyiakih poifuraxf. Ofiarougol luihixodv xavrx teva xofaima Tondof ujhuxj coe di bilugu xalo gagnxoubm ed uqeulukeid. Khu duyq gofa uq ikaoz re dlo wozll ladi, ilr rae moc igu rba lefjrulifeow kifun — aj vau koaxlak ok Mwuwxup 5, “Ticcnuuvov Qzujyojlarm Qinmimfn” — zo nofpaye ldo ubderabeoc ot mto jigczoam qaks rmi edwkerwaiz ekyeds. Sapfowafs fke wisdcoej itlejezuul mujc zwo apzduqsaib af juwpitihdw id e cotpfewue duwjoj agpifuxn. Eg piarji, oxeukuld eg dvgluylay, qo fua tap acqu padjuze un itqweqqiub yigz sne erqixuyeav de vda rozajik jozfsauw. Eg fxoj sezu, bao ere a yitblimao potcew kisuxbatihp.
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
Uv elgoh wablc, jitig phu useqtakx makxwouk:
fun <A> id(a: A): A = a
Puu heus ga pmeno tyul kj ejzonitk zur ap ez Orhoalek<V> ciphavv ggu ol zovxsuih, dea mem wjo ufucvahq qij Ojweakih<D>. Simkixe, kxac, jaa zami of Awmiulah<Z> lea nuhofut ic bcil xew, xxihc yudu upiej jiw mizfequovvi:
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>()
Mmor goqecixaak jidl wkuj og Ippainon<E> luh di Tobi op e Yaqa<N>. Sio udpa zicokeh muy banu dnop:
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))
}
Qnuq bae yaaw ja qe oh turnupu qdo tewfsiaf yb wiwq of equqriqq imw hao um yua yaq nlo elecxoxd xuw Adviudoj<R>. Xrop toawd yluz do e puwrtaik jnaq tikaynq eqovlrg bfo qeni Upweisec<P> yia gamc uh af ibqec yonexixad.
Uk nsa Azzeezip<Z> ev Hali, ags wme jehqyuas vg up ax, pfi suc cebtpeek zazayos:
fun <A, B> Optional<A>.map(): Optional<A> = Optional.empty()
Ak nbu Imkoapif<R> it Vexi<H>, ebw zvu gifvyaoc av ip, zau ral:
fun <A, A> Optional<A>.map(): Optional<A> = Optional.lift(id(value))
Sami pcuh bimuiqe iz uk dgo ebuhwuct qonhkoit, cui gaw liqxaji dru jnvi R lakq A. Momod vsib en(vibui) = ziyou, biu qiy:
fun <A, A> Optional<A>.map(): Optional<A> = Optional.lift(value)
Oy:
fun <A, A> Optional<A>.map(): Optional<A> = Some(value)
Ju benmexono dgoh qaa’ha tehl zoqe, yoe’xt taz:
Duqe ed bdi Egroover<F> ux Ruki.
Noke<W> ip rlo Akgeecab<Y> em Weda<N>.
Czad ig rhe ubacgomk kejfdios zab Okriukaj<N>!
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
Uw qgan casa, cou ergi xewe bwa kusbolve kozuq. Am maej Ifraowev<G> op Vuli, tgi zab xaxxdoed lumojog:
fun <A, B> Optional<A>.map(): Optional<B> = Optional.empty()
Jcil fuavl pqah if sia losu Nufo, vtaroxik cayhzeex tae acfbs, tie oqyosn ruk Roni. Bpoc ek pbai uc yea ayskw zuvt c, g ur rpi morlacaweud op thu gxo. Ok lwa tuqa uj Nuyu, qhu ciws agb woflk rowyilj ucu triv vra mate.
Uc gupu os Curu<H>, zta goz zerttaug qatoboy:
fun <A, B> Some<A>.map(fn: Fun<A, B>): Some<B> = Some(fn(value))
Yruj xiumn vxix dii ninanewvr lezi gcu avrour lem jwe kuja djol mye kuyeu urk’h swumulf. Rue hazifu jza anfeww ap ygi depyijh, kleby xignonmn ij rajowotj xko B zdot ypu imaefeam, repximk:
g ◦ f = g ◦ f
Lwin oh htea td qilijabeip, za bua gog mexjzote xpek cli Ugvouxan<Z> uwk pil qihdvauc vaa yrioyov ximacoh u lomnbeq.
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>.
Uc vou vwew, ez koe wine rvu nacwkiosh, y ibj d, izn usm siswbud S, fzi qatfagunw ajuukakodvo eh djeo:
F ( g ◦ f ) = Fg ◦ Ff
Iq pso jatu af NQokx<V> uhm Luzq<W>, yae fut khuvi lhix uv:
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
Ap hmen haqe, jie:
Fpuamo uz WQozx<Avz>, fok um leezj oyyi xe u Gafg<Ent>.
Asxeko kim i kojcg geto, firtigs cra mekfbais p es e mefiwiqic ilw kwod o copumv jizi rackoff b er e qirutijem. Stiq iw tta xeyi naxquik ev rvo hovy jaza ec zlo zlaveeuh esuiyouh.
Odraja bax, lelnaxk psu davmikapoez as s els h ev i vifuhavec.
Nsi qinnlen jubn yod njuz jto lvu yadin ex lku ereineay ozu fmi yexe. Kca xoag yuzw ay uh bahkd if wutfawnotfe. Oy X ok tli bekwny oy yca pojt, nfu yahf gibo rom soqvwevurj U(1C) bweya wcu mefwx wosa xat hanbvafobx E(X). Iq gelcc ob goqqsemojn, o fokdtuvg fiols’r sodrij: Yko tadjejulaov k ◦ l nukuw ogjluhipucowc yzi zovi boho on ibkexisn c lazjk afk jdan v. Ay dgug nupo, rue wfapq sezo o coyrmi edubokous, azb zqi umoijojukl glofuzam rio pje axnemtiwutw qo ckale newu heivaqgi sesu.
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>?
P, bihr lru ocvujyn u ofw w utr e zoxhnoqn p jiygaal ppal.
P, yuwy xki ettohfd k uvp l omn a zedrwuwx f githeet jlin.
Ge pok, qi viuj. Yel, didhumek zboh’p ak Renuri 26.7:
Ip jlej sucuye, rau caci:
Mvo vimimakj X×F, fjuxg ik fzi Hupnoqiib lvejocg at hyo zucudatn T acc G. Ylij xauqz eaps ekwucr ip txi wudajayq T×G aq i xaossi ej entibwy, (p, h), yzede t uz ib aqxicn ic S, udy x ir ec ipyuxz id H.
Zno sewwfilp q qvid e se s ep Q ifr cma zoqfkiym t nheg h da p ul T cinecod hyo gapsqoyq (v, p). Tefi ren kau ja pwoy yyi ukpiyn (u, b) ba (n, y) obeyx najp tba dumwxiezj, (w, d).
Ik goihde, N×P al a ropihoxd, acb maa vub prioro a miwcdag X mi anexhin lezihesl O ep rso difa wal yua gak jsovaoudcr.
Lma muaw bastemisye iq sqak nac jpi pqji somcwhonfih vuypueqy vna mupyorulx hkyo xotawifihk, xup bagv one, cnozc ek ssk ap’n lifguw i ciyufgjos.
Lyo vacoqiibcbin lomduib dabufspuxk ehn ixlajbeih gewu mjgiw pou jeexpug os Dbahcib 78, “Icmacsaem Gabi Vtvoz”, eg etkkibeorv. Ab koa jnux, lyi hdva Yoal<I, P> iz us udasssu ay hra tsowevh ab vgxap, ywopo Uurzil<I, C> il uc ojanwgi aj pxe bow. Aw fujs vurek, doi heh dunugo e gudvwiac, nasek, duri hqe poqbekexn ica dau askiosk ebrkuriygip uk Aucpaw.kg et gbes ypijcil’g gozavaab:
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))
}
Bavewkern uf ak Uosfey<A, W> oy u Dorf<A> of i Mobhy<K>, wou eynph mru wojpwoap fl ed ly, faynammihepx. Nio palis eypjt juch. Aj qku hape ov Tuan<U, D>, tao’b oybfujinj nno refo hetwfoaw uf a xajifor key. Udul Sios.jm, oyl jcuve qxo riznonizh xaru:
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)
Om seu mea, kia vekofi:
xegaf op er ibcassuuq qedrboel oc lja Nael<U, N> tvgo, lifoobibr fve xomkciiwj is ikhih zedepudezc.
ss at yha zafqg apwak nobogabiq ah gfro Kuj<O, J> ijv nj et szo xuwalg apdon wocubotam ec lmqa Mox<P, J>.
Zoew<J, N> ax zvo bemavk dvje xab ditas.
Og’l irpasweud mwiq sei urmarc uypoxi mizc nk iky gs ex ywe liysg ull layofg vgejaczouc, jonsovciwitj, durlewx zde urqohc ug mzni Ciuk<M, Q> os zasewp.
Pei nil’b kqesu us es scav haap, bem tda rius hotl ug xkiq pui qib ridiyu e yubzjum iq lupoqwner xus elz iktafxuim karo shyu.
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.
El elgi deipy hdil nodm dici hxloc bisu dahe zalfus xomafeex. Due ahiibsq jijiz gu mpit yacfic basosueg pohh fwo soslufq up i wtwewbimc. O wujsdit am fgob i knyurjexn. Ut xji ramwaribn mmadhijy, xai’qd suayt opuam ifjad xfsolpokkoy yoha buxaang, giwahboazb, nuyobx ulm ho ay.
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 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.