Category Theory Without The Baggage

If you are an alge­braic ab­strac­tol­o­gist, this post is prob­a­bly not for you. Fur­ther meta-com­men­tary can be found in the “meta” sec­tion, at the bot­tom of the post.

So you’ve heard of this thing called “cat­e­gory the­ory”. Maybe you’ve met some smart peo­ple who say that’s it’s re­ally use­ful and pow­er­ful for… some­thing. Maybe you’ve even cracked open a book or watched some lec­tures, only to find that the en­tire sub­ject seems to have been gen­er­ated by train­ing GPT-2 on a mix of alge­braic op­tom­e­try and out­put from the­p­roofistriv­ial.com.

What is this sub­ject? What could one do with it, other than write opaque math pa­pers?

This in­tro­duc­tion is for you.

This post will cover just the bare-bones foun­da­tional pieces: cat­e­gories, func­tors, and nat­u­ral trans­for­ma­tions. I will mostly es­chew the typ­i­cal pre­sen­ta­tion; my goal is just to con­vey in­tu­ition for what these things mean. Depend­ing on in­ter­est, I may post a few more pieces in this vein, cov­er­ing e.g. limits, ad­junc­tion, Yoneda lemma, sym­met­ric monoidal cat­e­gories, types and pro­gram­ming, etc—leave a com­ment if you want to see more.

Out­line:

  • Cat­e­gory the­ory is the study of paths in graphs, so I’ll briefly talk about that and high­light some rele­vant as­pects.

  • What’s a cat­e­gory? A cat­e­gory is just a graph with some no­tion of equiv­alence of paths; we’ll see a few ex­am­ples.

  • Pat­tern match­ing: find a sub-cat­e­gory with a par­tic­u­lar shape. Matches are called “func­tors”.

  • One sub-cat­e­gory mod­el­ling an­other: com­mu­ta­tive squares and nat­u­ral trans­for­ma­tions.

Paths in Graphs

Here’s a graph:

Here are some paths in that graph:

  • A → B

  • B → C

  • A → B → C

  • A → A

  • A → A → A (twice around the loop)

  • A → A → A → B (twice around the loop, then to B)

  • (triv­ial path—start at D and don’t go any­where)

  • (triv­ial path—start at A and don’t go any­where)

In cat­e­gory the­ory, we usu­ally care more about the edges and paths than the ver­tices them­selves, so let’s give our edges their own names:

We can then write paths like this:

  • A → B is writ­ten y

  • B → C is writ­ten z

  • A → B → C is writ­ten yz

  • A → A is writ­ten x

  • A → A → A is writ­ten xx

  • A → A → A → B is writ­ten xxy

  • The triv­ial path at D is writ­ten id_D (this is roughly a stan­dard no­ta­tion)

  • The triv­ial path at A is writ­ten id_A

We can build longer paths by “com­pos­ing” shorter paths. For in­stance, we can com­pose y (aka A → B) with z (aka B → C) to form yz (aka A → B → C), or we can com­pose x with it­self to form xx, or we can com­pose xx with yz to form xxyz. We can com­pose two paths if-and-only-if the sec­ond path starts where the first one be­gins—we can’t com­pose x with z be­cause we’d have to mag­i­cally jump from A to B in the mid­dle.

Com­po­si­tion is asym­met­ric—com­pos­ing y with z is fine, but we can’t com­pose z with y.

No­tice that com­pos­ing id_A with x is just the same as x by it­self: if we start at A, don’t go any­where, and then fol­low x, then that’s the same as just fol­low­ing x. Similarly, com­pos­ing x with id_A is just the same as x. Sym­bol­i­cally: id_A x = x id_A = x. Math­e­mat­i­cally, id_A is an “iden­tity”—an op­er­a­tion which does noth­ing; thus the “id” no­ta­tion.

In ap­pli­ca­tions, graphs al­most always have data on them—at­tached to the ver­tices, the edges, or both. In cat­e­gory the­ory in par­tic­u­lar, data is usu­ally on the edges. When com­pos­ing those edges to make paths, we also com­pose the data.

A sim­ple ex­am­ple: imag­ine a graph of roads be­tween cities. Each road has a dis­tance. When com­pos­ing mul­ti­ple roads into paths, we add to­gether the dis­tances to find the to­tal dis­tance.

Fi­nally, in our origi­nal graph, let’s throw in an ex­tra edge from A to it­self:

Our graph has be­come a “multi­graph”—a graph with (po­ten­tially) more than one dis­tinct edge be­tween each ver­tex. Now we can’t just write a path as A → A → A any­more—that could re­fer to xx, xx’, x’x, or x’x’. In cat­e­gory the­ory, we’ll usu­ally be deal­ing with multi­graphs, so we need to write paths as a se­quence of edges rather than the ver­tices-with-ar­rows no­ta­tion. For in­stance, in our roads-and-cities ex­am­ple, there may be mul­ti­ple roads be­tween any two cities, so a path needs to spec­ify which roads are taken.

Cat­e­gory the­o­rists call paths and their as­so­ci­ated data “mor­phisms”. This a ter­rible name, and we mostly won’t use it. Ver­tices are called “ob­jects”, which is a less ter­rible name I might oc­ca­sion­ally slip into.

What’s a cat­e­gory?

A cat­e­gory is:

  • a di­rected multigraph

  • with some no­tion of equiv­alence be­tween paths.

For in­stance, we could imag­ine a di­rected multi­graph of flights be­tween air­ports, with a cost for each flight. A path is then a se­quence of flights from one air­port to an­other. As a no­tion of equiv­alence, we could de­clare that two paths are equiv­a­lent if they have the same start and end points, and the same to­tal cost.

There is one im­por­tant rule: our no­tion of path-equiv­alence must re­spect com­po­si­tion. If path p is equiv­a­lent to q (which I’ll write ), and , then we must have . In our air­ports ex­am­ple, this would say: if two flight-paths p and q have the same cost (call it ), and two flight-paths x and y have the same cost (call it ), then the cost of px (i.e. ) must equal the cost of qy (also ).

Be­sides that, there’s a hand­ful of boiler­plate rules:

  • Any path is equiv­a­lent to it­self (re­flex­ivity), and if and then (tran­si­tivity); these are the usual rules which define equiv­alence re­la­tions.

  • Any paths with differ­ent start and end points must not be equiv­a­lent; oth­er­wise ex­pres­sions like “” might not even be defined.

Let’s look at a few more ex­am­ples. I’ll try to show some qual­i­ta­tively differ­ent cat­e­gories, to give some idea of the range available.

Air­ports & Flights

Our air­port ex­am­ple is already a fairly gen­eral cat­e­gory, but we could eas­ily add more bells and whis­tles to it. Rather than hav­ing a ver­tex for each air­port, we could have a ver­tex for each air­port at each time. Flights then con­nect an air­port at one time to an­other air­port at an­other time, and we need some zero-cost “wait” edges to move from an air­port at one time to the same air­port at a later time. A path would be some com­bi­na­tion of flights and wait­ing. We might ex­pect that the cat­e­gory has some sym­me­tries—e.g. “same flights on differ­ent days”—and later we’ll see some tools to for­mal­ize those.

Divisibility

As a com­pletely differ­ent ex­am­ple, con­sider the cat­e­gory of di­visi­bil­ity of pos­i­tive in­te­gers:

This cat­e­gory has a path from n to m if-and-only-if n is di­visi­ble by m (writ­ten m | n, pro­nounced “m di­vides n”, i.e. 2 | 12 is read “two di­vides twelve”). The “data” on the edges is just the di­visi­bil­ity re­la­tions—i.e. 6 | 12 or 5 | 15:

We can com­pose these: 2|6 and 6|12 im­plies 2|12. A path 12 → 6 → 2 in this cat­e­gory is, in some sense, a proof that 12 is di­visi­ble by 2 (given all the di­visi­bil­ity re­la­tions on the edges). Note that any two paths from 12 to 2 pro­duce the same re­sult—i.e. 12 → 4 → 2 also gives 2|12. More gen­er­ally: in this cat­e­gory, any two paths be­tween the same start and end points are equiv­a­lent.

Types & Functions

Yet an­other to­tally differ­ent di­rec­tion: con­sider the cat­e­gory of types in some pro­gram­ming lan­guage, with func­tions be­tween those types as edges:

This cat­e­gory has a LOT of stuff in it. There’s a func­tion for ad­di­tion of two in­te­gers, which goes from (int, int) to int. There’s an­other func­tion for mul­ti­pli­ca­tion of two in­te­gers, also from (int, int) to int. There are func­tions op­er­at­ing on lists, strings, and hash ta­bles. There are func­tions which haven’t been writ­ten in the en­tire his­tory of pro­gram­ming, with in­put and out­put types which also haven’t been writ­ten.

We know how to com­pose func­tions—just call one on the re­sult of the other. We also know when two func­tions are “equiv­a­lent”—they always give the same out­put when given the same in­put. So we have a cat­e­gory, us­ing our usual no­tions of com­po­si­tion and equiv­alence of func­tions. This cat­e­gory is the main fo­cus of many CS ap­pli­ca­tions of cat­e­gory the­ory (e.g. types in Haskell). Math­e­mat­i­ci­ans in­stead fo­cus on the closely-re­lated cat­e­gory of func­tions be­tween sets; this is ex­actly the same ex­cept that func­tions go from one set to an­other in­stead of one type to an­other.

Com­mu­ta­tive Diagrams

A lot of mathy fields use di­a­grams like this:

For in­stance, we can scale an image down () then ro­tate it () or ro­tate the image () then scale it (), and get the same re­sult ei­ther way. The idea that we get the same re­sult ei­ther way is sum­ma­rized by the phrase “the di­a­gram com­mutes”; thus the name “com­mu­ta­tive di­a­gram”. In terms of paths: we have path-equiv­alence .

Another way this of­ten shows up: we have some prob­lem which we could solve di­rectly. But it’s eas­ier to trans­form it into some other form (e.g. change co­or­di­nates or change vari­ables), solve in that form, then trans­form back:

Again, we say “the di­a­gram com­mutes”. Now our path-equiv­alence says .

Talk­ing about com­mu­ta­tive di­a­grams is ar­guably the cen­tral pur­pose of cat­e­gory the­ory; our main tool for that will be “nat­u­ral trans­for­ma­tions”, which we’ll in­tro­duce shortly.

Pat­tern Match­ing and Functors

Think about how we use regexes. We write some pat­tern then try to match it against some string—e.g. “colou*r” matches “color” or “colour” but not “pink”. We can use that to pick out parts of a tar­get string which match the pat­tern—e.g. we could find the query “color” in the tar­get “ev­ery color of the rain­bow”.

We’d like to do some­thing similar for cat­e­gories. Main idea: we want to match ob­jects (a.k.a ver­tices) in the query cat­e­gory to ob­jects in the tar­get cat­e­gory, and paths in the query cat­e­gory to paths in the tar­get cat­e­gory, in a way that keeps the struc­ture in­tact.

For ex­am­ple, con­sider a com­mu­ta­tive square:

We’d like to use that as a query on some other cat­e­gory, e.g. our air­port cat­e­gory. When we query for a com­mu­ta­tive square in our air­port cat­e­gory, we’re look­ing for two paths with the same start and end air­ports, (po­ten­tially) differ­ent in­ter­me­di­ate air­ports, but the same over­all cost. For in­stance, maybe Delta has flights from New York to Los An­ge­les via their hub in At­lanta, and South­west has flights from New York to Los An­ge­les via their hub in Ve­gas, and mar­ket com­pe­ti­tion makes the prices of the two flight-paths equal.

We’ll come back to the com­mu­ta­tive square query in the next sec­tion. For now, let’s look at some sim­pler queries, to get a feel for the build­ing blocks of our pat­tern-matcher. Re­mem­ber: ob­jects to ob­jects, paths to paths, keep the struc­ture in­tact.

First, we could use a sin­gle-ob­ject cat­e­gory with no edges as a query:

This can match against any one ob­ject (a.k.a ver­tex) in the tar­get cat­e­gory. Note that there is a path hid­ing in the query—the iden­tity path, where we start at the ob­ject and just stay there. In gen­eral, our pat­tern-matcher will always match iden­tity paths in the query with iden­tity paths on the cor­re­spond­ing ob­jects in the tar­get cat­e­gory—that’s one part of “keep­ing the struc­ture in­tact”.

Next-most com­pli­cated is the query with two ob­jects:

This one is slightly sub­tle—it might match two differ­ent ob­jects, or both query ob­jects might match against the same tar­get ob­ject. This is just the way pat­tern-match­ing works in cat­e­gory the­ory; there’s no rule to pre­vent mul­ti­ple ver­tices/​edges in the query from col­laps­ing into a sin­gle ver­tex/​edge in the tar­get cat­e­gory. This is ac­tu­ally use­ful quite of­ten—for in­stance, if we have some func­tion which takes in two ob­jects from the tar­get cat­e­gory, then it’s perfectly rea­son­able to pass in the same ob­ject twice. Maybe we have a path-find­ing al­gorithm which takes in two air­ports; it’s perfectly rea­son­able to ex­pect that al­gorithm to work even if we pass the same air­port twice—that’s a very easy path-find­ing prob­lem, af­ter all!

Next up, we add in an edge:

Now that we have a non­triv­ial path, it’s time to high­light a key point: we map paths to paths, not edges to edges. So if our tar­get cat­e­gory con­tains some­thing like A → B → C, then our one-edge query might match against the A → B edge, or it might match against the B → C edge, or it might match the whole path A → C (via B) - even if there’s no di­rect edge from A to C. Again, this is use­ful quite of­ten—if we’re search­ing for flights from New York to Los An­ge­les, it’s perfectly fine to show re­sults with a stop or two in the mid­dle. So our one-edge query doesn’t just match each edge; it matches each path be­tween any two ob­jects (in­clud­ing the iden­tity path from an ob­ject to it­self).

Ad­ding more ob­jects and edges gen­er­al­izes in the ob­vi­ous way:

This finds any two paths which start at the same ob­ject. As usual, one or both paths could be the iden­tity path, and both paths could be the same.

The other main build­ing block is equiv­alence be­tween paths. Let’s con­sider a query with two edges be­tween two ob­jects, with the two edges de­clared to be equiv­a­lent:

You might ex­pect that this finds any two equiv­a­lent paths. That’s tech­ni­cally true, but some­what mis­lead­ing. As far as cat­e­gory the­ory is con­cerned, there’s ac­tu­ally only one path here—we only care about paths up to equiv­alence (thankyou to Eigil for point­ing this out in the com­ments). So that “one” path will be mapped to “one” path in the tar­get cat­e­gory; our query could ac­tu­ally match any num­ber of paths, as long as they’re all equiv­a­lent. Look­ing back at our one-edge ex­am­ple from ear­lier, it’s pos­si­ble that our one edge could be mapped to a whole class of equiv­a­lent paths—by map­ping it to one path, we’re effec­tively se­lect­ing all the paths equiv­a­lent to that one.

A com­mu­ta­tive square works more like we’d ex­pect:

In our query, the two paths from up­per-left to lower-right are equiv­a­lent, but they con­tain non-equiv­a­lent sub­paths. So those sub­paths may be mapped to non-equiv­a­lent paths in the tar­get, as long as those non-equiv­a­lent paths com­pose into equiv­a­lent paths. In other words, we’re look­ing for a com­mu­ta­tive square in the tar­get, as we’d ex­pect. (Though we can still find de­gen­er­ate com­mu­ta­tive squares, e.g. matches where the lower left and up­per right cor­ner map to the same ob­ject.)

Cat­e­gory the­o­rists call each in­di­vi­d­ual match a “func­tor”. Each differ­ent func­tor—i.e. each match—maps the query cat­e­gory into the tar­get cat­e­gory in a differ­ent way.

Note that the tar­get cat­e­gory is it­self a cat­e­gory—which means we could use it as a query on some third cat­e­gory. In this case, we can com­pose matches/​func­tors: if one match tells me how to map cat­e­gory 1 into cat­e­gory 2, and an­other match tells me how to map cat­e­gory 2 into cat­e­gory 3, then I can com­bine those to find a map from cat­e­gory 1 into cat­e­gory 3.

Be­cause cat­e­gory the­o­rists love to go meta, we can even define a graph in which the ob­jects are cat­e­gories and the edges are func­tors. A path then com­poses func­tors, and we say that two paths are equiv­a­lent if they re­sult in the same map from the origi­nal query cat­e­gory into the fi­nal tar­get cat­e­gory. This is called “Cat”, the cat­e­gory of cat­e­gories and func­tors. Yay meta.

Mean­while, back on Earth (or at least low Earth or­bit), com­mu­ta­tive di­a­grams.

Ex­er­cise: Hope­fully you now have an in­tu­itive idea of how our pat­tern-matcher works, and what in­for­ma­tion each match (i.e. each func­tor) con­tains. Use your in­tu­ition to come up with a for­mal defi­ni­tion of a func­tor. Then, com­pare your defi­ni­tion to wikipe­dia’s defi­ni­tion (jar­gon note: “mor­phism” = set of equiv­a­lent paths); is your defi­ni­tion equiv­a­lent? If not, what’s miss­ing/​ex­tra­ne­ous in yours, and when would it mat­ter?

Nat­u­ral Transformations

Let’s start with a micro­scopic model of a pot of wa­ter. We have some “state”, rep­re­sent­ing the po­si­tions and mo­menta of ev­ery molecule in the wa­ter (or quan­tum field state, if you want to go even lower-level). There are things we can do to the wa­ter—boil it, cool it back down, add salt, stir it, wait a few sec­onds, etc—and each of these things will trans­form the wa­ter from one state to an­other. We can rep­re­sent this as a cat­e­gory: the ob­jects are states, the edges are op­er­a­tions mov­ing the wa­ter from one state to an­other (in­clud­ing just let­ting time pass), and paths rep­re­sent se­quences of op­er­a­tions.

In physics, we usu­ally don’t care how a phys­i­cal sys­tem ar­rived in a par­tic­u­lar state—the state tells us ev­ery­thing we need to know. That would mean that any path be­tween the same start and end states are equiv­a­lent in this cat­e­gory (just like in the di­visi­bil­ity cat­e­gory). To make the ex­am­ple a bit more gen­eral, let’s as­sume that we do care about differ­ent ways of get­ting from one state to an­other—e.g. heat­ing the wa­ter, then cool­ing it, then heat­ing it again will definitely rack up a larger elec­tric/​gas bill than just heat­ing it.

Micro­scopic mod­els ac­count­ing for the po­si­tion and mo­men­tum of ev­ery molecule are rather difficult to work with, com­pu­ta­tion­ally. We might in­stead pre­fer a higher-level macro­scopic model, e.g. a fluid model where we just track av­er­age ve­loc­ity, tem­per­a­ture, and chem­i­cal com­po­si­tion of the fluid in lit­tle cells of space and time. We can still model all of our op­er­a­tions—boiling, stir­ring, etc—but they’ll take a differ­ent form. Rather than forces on molecules, now we’re think­ing about macro­scopic heat flow and to­tal force on each lit­tle cell of space at each time.

We can con­nect these two cat­e­gories: given a micro­scopic state we can com­pute the cor­re­spond­ing macro­scopic state. By ex­plic­itly in­clud­ing these micro­scopic → macro­scopic trans­for­ma­tions as edges, we can in­cor­po­rate both sys­tems into one cat­e­gory:

Note that mul­ti­ple micro-states will map to the same macro-state, al­though I haven’t drawn any.

The key prop­erty in this two-part cat­e­gory is path equiv­alence (a.k.a. com­mu­ta­tion). If we start at the left­most micro­scopic state, stir (in micro), then trans­form to the macro rep­re­sen­ta­tion, then that should be ex­actly the same as start­ing at the left­most micro­scopic state, trans­form­ing to the macro rep­re­sen­ta­tion, and then stir­ring (in macro). It should not mat­ter whether we perform some op­er­a­tions in the macro or micro model; the two should “give the same an­swer”. We rep­re­sent that idea by say­ing that two paths are equiv­a­lent: one path which trans­forms micro to macro and then stirs (in macro), and an­other path which stirs (in micro) and then trans­forms micro to macro. We have a com­mu­ta­tive square.

In fact, we have a bunch of com­mu­ta­tive squares. We can pick any path in the micro-model, find the cor­re­spond­ing path in the macro-model, add in the micro->macro trans­for­ma­tions, and end up with a com­mu­ta­tive square.

Main take-away: prism-shaped cat­e­gories with com­mu­ta­tive squares on their side-faces cap­ture the idea of rep­re­sent­ing the same sys­tem and op­er­a­tions in two differ­ent ways, pos­si­bly with one rep­re­sen­ta­tion less gran­u­lar than the other. We’ll call these kinds of struc­tures “nat­u­ral trans­for­ma­tions”.

Next step: we’d like to use our pat­tern-matcher to look for nat­u­ral trans­for­ma­tions.

We’ll start with some ar­bi­trary cat­e­gory:

Then we’ll make a copy of it, and add edges from ob­jects in the origi­nal to cor­re­spond­ing ob­jects in the copy:

I’ll call the origi­nal cat­e­gory “sys­tem”, and the copy “model”.

To finish our pat­tern, we’ll de­clare path equiv­alences: if we fol­low an edge from sys­tem to model, then take any path within the model, that’s equiv­a­lent to tak­ing the cor­re­spond­ing path within the sys­tem, and then fol­low­ing an edge from sys­tem to model. We de­clare those paths equiv­a­lent (as well as any equiv­alences in the origi­nal cat­e­gory, and any other equiv­alences im­plied, e.g. paths in which our equiv­a­lent paths ap­pear as sub-paths).

Now we just take our pat­tern and plug it into our pat­tern-matcher, as usual. Our pat­tern matcher will go look­ing for a sys­tem-model pair, all em­bed­ded within what­ever tar­get cat­e­gory we’re search­ing within. Each match is called a nat­u­ral trans­for­ma­tion; we say that the nat­u­ral trans­for­ma­tion maps the sys­tem-part to the match of the model-part. Since we call matches “func­tors”, a cat­e­gory the­o­rist would say that a nat­u­ral trans­for­ma­tion maps one func­tor (the match of the sys­tem-part) to an­other of the same shape (the match of the model-part).

Now for an im­por­tant point: re­mem­ber that, in our pot-of-wa­ter ex­am­ple, mul­ti­ple micro­scopic states could map to the same macro­scopic state. Mul­ti­ple ob­jects in the source are col­lapsed into a sin­gle ob­ject in the tar­get. But our pro­ce­dure for cre­at­ing a nat­u­ral trans­for­ma­tion pat­tern just copies the whole source cat­e­gory di­rectly, with­out any col­laps­ing. Is our pot-of-wa­ter ex­am­ple not a true nat­u­ral trans­for­ma­tion?

It is. Last sec­tion I said that it’s some­times use­ful for our pat­tern-matcher to col­lapse mul­ti­ple ob­jects into one; the pot-of-wa­ter is an ex­am­ple where that mat­ters. Our pat­tern-matcher may be look­ing for a copy of the micro model, but it will still match against the macro model, be­cause it’s al­lowed to col­lapse mul­ti­ple ob­jects to­gether into one.

More gen­er­ally: be­cause our pat­tern-matcher is al­lowed to col­lapse ob­jects to­gether, it’s able to find nat­u­ral trans­for­ma­tions in which the model is less gran­u­lar than the sys­tem.

Meta

That con­cludes the ac­tual con­tent; now I’ll just talk a bit about why I’m writ­ing this.

I’ve bounced off of cat­e­gory the­ory a cou­ple times be­fore. But smart peo­ple kept say­ing that it’s re­ally pow­er­ful, in ways that sound re­lated to my re­search, so I’ve been tak­ing an­other pass at the sub­ject over the last few weeks.

Even the best book I’ve found on the ma­te­rial seems bur­dened mainly by poor for­mu­la­tions of the core con­cepts and very limited ex­am­ples. My cur­rent im­pres­sion is that broader adop­tion of cat­e­gory the­ory is limited in large part by bad defi­ni­tions, even when more in­tu­itive equiv­a­lent defi­ni­tions are available—“mor­phisms” vs “paths” is a par­tic­u­larly blatant ex­am­ple, lead­ing to an en­tirely un­nec­es­sary profu­sion of iden­tities in defi­ni­tions. Also, of course, cat­e­gory the­o­rists are con­stantly try­ing to go more ab­stract in ways that make the pre­sen­ta­tion more con­fus­ing with­out re­ally adding any­thing in terms of ex­pla­na­tion. So I’ve needed to come up with my own con­crete ex­am­ples and look for more in­tu­itive defi­ni­tions. This write-up is a nat­u­ral by-product of that pro­cess.

I’d es­pe­cially ap­pre­ci­ate feed­back on:

  • whether I’m miss­ing key con­cepts or made cru­cial mis­takes.

  • whether this was use­ful; I may drop some more posts along these lines if many peo­ple like it.

  • whether there’s some won­der­ful cat­e­gory the­ory re­source which has already done some­thing like this, so I can just read that in­stead. I would re­ally, re­ally pre­fer to do this the easy way.