Why I am learning category theory
the.scapegoat.dev
the.scapegoat.dev
Maybe I'm missing something but it seems to me that all you can study monads in programming languages without having to also learn the category theory abstraction of it. The same goes for semigroups, monoids, folds, functors, ...
I'm sure there's a benefit in category theory once you want to unify all these abstractions and talk about all of them together, but I see so many people who think they have to learn CT before they can understand monads in e.g. Haskell, and I don't really understand why.
I can see why categories appeal if you're just looking at definitions, but CT often just abstracts/standardizes an approach that folks were already working with when working with rings, groups, topologies, etc. Going through the proofs will teach you when to apply what.
Aluffi (Algebra: Chapter 0) makes a great book that teaches the basics of CT and brings it up while teaching regular algebra. I highly recommend it.
In parallel I developed this very "visual" way of thinking about structure that makes me really resonate with CT. In a way I'm less interested in learning new maths, more so than learning more about how people approach (complex) structures I already know with these very rudimentary concepts, and seeing how their formalizations match my "folk mathematics".
I'm having a lot of fun!
edit: thanks for the book recommendation! that looks like my kind of maths book
I also like the focus on quotient groups and galois fields, as well as using graphs as recurring examples, which have been the only times I really put the pedal to the maths metal in my programming (for some crypto and error correction stuff).
I found groups, limits, continuity, fields to be much more amenable to my thinking. It felt way more similar to the things I really enjoyed (building software in scheme and C and writing exploits in assembly).
For me, I realized thst I need to write the code to accompany the maths, because it helps me disambiguate mathematical notation and put "types" on things.
It helped me a lot to work alongside real maths PhDs and realize that they have a very "intuitive" approach to maths, and don't necessarily yeet lemmas and proofs around all day, instead spending a lot of time brainstorming on the whiteboard and discussing things out, very similarly to software design. It was one of them telling me that my thinking was very mathematical that helped me "deconstruct" the fear of maths that I had built up.
At least for American university-level calculus vs. American university-level analysis/topology over metric spaces.
> self-taught
Ah, makes more sense. I am also mostly self taught for most math. From my experience, I find it hard to believe after you did the exercises in PoMA, that calculus was harder.
Looking at it now, I think maybe I just found it too boring to remember the tricks, and too tedious to not make mistakes, while I found coming up with proofs more "interesting" and requiring less rote memorization.
I wish the university calculus changed the curriculum. I didn't find it particularly useful to learn how to integrate e.g. inverse trig given that WolframAlpha exists. There are lots of other examples of teaching memorization of silly tricks like that. I guess they probably do it because it's easy to test.
While I only got so far in proving stuff about a cyber-physical I was working on, but this was really fun to learn: https://github.com/LS-Lab/KeYmaeraX-release
See also: http://lfcps.org/lfcps/
I myself find beauty in some parts of analysis, but algebra definitely speaks more to me (e.g. I find a proof of Lagrange's Theorem more beautiful than one of the Intermediate Value Theorem). So I can relate.
(we don't talk about the topologists)
Because people think “category theory” means “abstract math” in general, due to cargo-culting in and around the Haskell community.
There's a lot of empty posturing by people who don't really seem to understand a lot of mathematics but still seem to have very strong convictions about certain aspects of it (constructivism is another such thing).
I've only really seen constructivism talked about by people who actually have a strong math background. Because it is hard to speak out against, say, the classical notions of existence that say that there are more real numbers than rational ones unless you actually understand why the classical proofs don't work constructively. And not just as, "We don't allow that proof."
To make that concrete, let's use computable analysis as a foundation for constructivism. In short, we represent Cauchy sequences as computer programs about which we can prove things in our favorite axiom system. You can build up a version of real analysis from that. It is easy to attempt Cantor's diagonal argument. You'll get a concrete program. But it will only represent a real number in our system if our axiom system can prove that every program it proves works, works as proven. This immediately brings up consistency. And so Gödel proves that showing this program represents a computable number in our system would imply our axioms to be inconsistent. (Bad axioms! Bad axioms!) And therefore Cantor's proof fails to produce a number in our system.
Of course classically we would say that if the axioms are consistent, then the program will compute a Cauchy sequence. And so it really does represent a real number even though we couldn't verify it. But whether we accept this alternate argument is a question of philosophy, not logic.
Which I don't mind. If you know your maths, your logic and ideally even your philosophy it's perfectly fine to work in constructivism or even prefer it.
But I've definitely seen some dumb, uninformed hot takes about how maths is just this big conspiracy that doesn't allow dissenting opinions because they've seen some NJ Wildberger video where he shittalks the real numbers.
So it's not even just constructivism per se, more this mix of unprincipled finitism + constructivism + no axiom of choice (or any mixture thereof), as opposed to anything grounded in some rigorous methodology. Basically the "I don't understand set theory or logic but I've heard that Banach Tarski is weird, so mainstream mathematics must be full of shit".
You'd think people wouldn't have hot takes about mathematics, but somehow they do.
As for the specific complaint, once you have Constructivism, Banach Tarski falls apart (even in versions with the axiom of choice). So if you don't like the weirdness, there is no need to accept it. (But you won't get advanced degrees in math unless you can at least temporarily pretend to accept it. Zorn's lemma is just too widely used.)
But can we agree to both dislike people who argue that 0.999... is not 1?
And uses of the axiom of choice are viewed with suspicion even by many who are doing perfectly classical math, so there's nothing wrong with pointing them out as assumptions ("if we admit of choice being applicable to X, we have Y").
As for choice, try doing much functional analysis without Zorn's lemma.
There are parts of math that you can do constructively. But most of it you really can't. And choice is embedded in a lot more math than you'd guess.
Well yes, it's still a different kind of math. What's not true however is this common notion that a constructivist can only ever view all "classical" math as pure nonsense. In many ways, it ought to be a lot easier for someone committed to constructivist semantics to grok a classical development than the converse - because decision procedures, computations etc. are way more of an afterthought to mainstream mathematicians.
But I'm also a strict formalist, or more accurately, I think whichever axioms happen to be useful should be used, without much need for there to be a epistemological commitment.
Logic and proof theory is actually one of the areas in math where category theory is most clearly useful. Intuitively, this is because reflexivity ("A leads to A") and transitivity ("A leads to B and B leads to C, hence A leads to C") are well-known rules of any kind of proof writing or indeed of argumentation in general, and they exactly describe a category.
As someone familiar with algebraic objects and relations etc., I don’t see what category theory is adding in terms of practical usefulness in software development.
Also, reflexivity, transitivity and symmetry just define an equivalence relation, no need for CT there.
Eg Stuff about types and endofunctors feels like trivial examples using the jargon just to use it. OTOH discovering the Top->Grp functor in algebraic topology is kind of unexpected and revealing.
For example if it were 2008 and you want to be inventor Applicative functors for use in Haskell, then knowing lax monoidal functors from CT might be helpful. But if you want to just use Applicative functors, you don't need to learn lax monoidal functors first.
So programmers probably don't need to learn CT because they can just let the computer scientists devise their abstractions for them. But if you want to be a computer scientist and one day devise your own abstractions[0], then maybe CT would be helpful.
Your description is the single best sales pitch for learning it that I've ever heard. I'm legitimately interested now — in a way that I simply wasn't before your comment.
Everyone else who tries to hype up CT is always like, "Whoa, bro, don't you know that addition is actually a monoid endofunctor over the set of all hyper-reals?" (Or something.) I guess that sort of breathless mathematical revelation is supposed to be so mind-blowing that anyone who hears it gets instantly hooked. My usual response was always just, "So what?"
But you're telling me I can become a better engineer? A meta-engineer, who can engineer things to engineer with? Yeah, I'd definitely be up for that.
https://corecursive.com/050-sam-ritchie-portal-abstractions-...
I think I might have linked it in the article.
That said, if I had to guess at what would be effective at getting up to speed without spending 25 years, would be to checkout https://github.com/hmemcpy/milewski-ctfp-pdf Category Theory for Programmers. Milewski was one of those people who were like "Oh, it's simply Yoneda this and Yoneda that", and he figured it out all himself in parallel without seeing my proof.
But I doubt it will be like, knowing Category Theory will enable you to have super powers for abstraction design. Rather it will be a matter of having enough mathematical tools at your disposal plus the right inspiration at the right time to realize that ones of those tools can happen to solve your abstraction programming problem you happen to be facing at some moment, in a way that is not obvious, if you are lucky. In fact, you likely have to first guess at what the right abstraction is and then fall back on Category Theory to verify the sanity of your guess.
Independent of mathematical numerical systems used, everthing gets loaded at hoursX0000. Everything else after that is just bracket arrangements.
If let things stack up, then it's all about what can be accomplished before zeroing out.
https://bartoszmilewski.com/2014/10/28/category-theory-for-p...
However, as I started off with, I’m always willing to try something out or see the reason in something. Can anyone give me a practical applied way in which category theory is a benefit to your design rather than just creating higher level jargon to label your current design with?
This made me chuckle, because I am at this very moment trying to apply the "tagless final style" described here[0] to a custom GUI in a personal-for-fun-and-learning ocaml project : )
[0] https://okmij.org/ftp/tagless-final/course/optimizations.htm...
"foo" + "bar" -> "foobar"
"foo" + "" -> "foo"
That makes it a monoid. Instead of talking about OOP patterns, knowing that the "+" operator is a monoid for string objects lets us write code that is composable.Similarly, with arrays:
[:foo,:bar] + [:baz] -> [:foo,:bar,:baz]
[:foo,:bar] + [] -> [:foo,:bar]
Some language platforms will implement the first concat, but not the latter. Without the latter, you couldn't do things like, accept an empty user input. You would have to write code like: defaults + user_input unless user_input_array.empty?
This applies to things like, hashes, or even more complex objects -- such as ActiveRecord named scopes, even if they don't use a "+" operator. (But maybe, Hash objects can be "+" together instead of calling merge())This is all applicable to how you can design libraries and DSLs that seem intuitive and easy to use. Instead of just thinking about bundling a number of methods together with a data structure, you can make sure that a single method within that bundle works the way you expected across a number of objects. You might even be using these ideas in your code, but don't realize it.
I think this was GP's point. They complain that no-one explains why the category theory _jargon_ is useful. As you point out, the jargon is not needed in order to use the concept. Someone might even come up with the concept independently of the jargon.
Where is Category Theory?
I believe this is what's at hand here. What is that something? What is that broad fashion? An example would be welcome
In fact I think that one of the great things about software is that you can not care about being very formal, and you can just bruteforce your way through a lot of things. DO your database calls really compose? or can pgbouncer throw some nonsense in there? Well maybe it breaks the abstraction, but we can just not care and put in a cloudformation alert in there and move on.
What applicable insight did you gain over the normal algebraic definition (https://en.wikipedia.org/wiki/Monoid)?
Nothing is rocket science, and you can totally get there without even the traditional monoid definition, but seeing the same diagrams I kept drawing before porting something to more "traditional" code patterns was quite validating.
completely rough whatever that tries to match stuff I did on a ps2 protocol decoder a while ago. Being able to invert state machines that use time ticks to advance requires adding additional counters to make them work properly, iirc. As you can tell none of this is formal, in fact I worked on this before starting my CT joint, but you can maybe see why I resonate more with the CT formulation of things.
The bigger picture here is that when I do embedded dev, I only have so much memory to keep an event log, so I need to be able to work backwards from my last none state, instead of traditionally walking forward from an initial state and incoming events. That this whole concept of "swapping the arrows around" is something that reputable people actually study was super inspiring.
https://media.hachyderm.io/media_attachments/files/109/434/7...
I have no idea. I don't think I have really yet to grok the ideas around categories and morphisms. I'm very slow at learning this stuff.
I do know that each of the small number of intuitions I have gained has sharpened how I reason about my code, that once I grokked a single intuition, they seem both simple and profound, and that there are even more that I don't yet understand.
So for example, when you wrote "Also having commutative operations makes for Abelian Groups, which enable operations to be reordered, and makes for implicit parallelism", I never thought about that. Initially, I could almost feel like something coming together. After I let that sink in for a bit, and then it starts opening doors in my mind. And then I realize it's something that I have used before, but never saw it in this way.
I was mainly answering the parent comment about why someone might want to drink the kool-aide as it were. I suppose my answer is, even though I know there are some powerful ideas with CT itself, the intuitions along the way each have many applications in my day-to-day work.
Strings are a (free) monoid with respect to concatenation, sure, but it is easier to learn what a monoid is using strings as an example, rather to try and "learn" about strings by discussing monoids first. Why this is deemed useful by some is beyond me.
Again, these are all very possible without knowing category theory, but category theory studies what the abstractions behind this could be. I find that the huge amount of intuition (driven by some formalism picked up along the way, but even more so by just writing a lot of code) I developed is reflected in what I see in category theory. It's why I genuinely think that web development is just like embedded development: https://the.scapegoat.dev/embedded-programming-is-like-web-d... which seems to be way more controversial than I thought it would be.
For example, in the MapReduce framework, this idea gives rise to “Combiners” that summarize the results of each map worker to massively lower the cost of shuffling the results of the Map stage over the network prior to the Reduce stage.
Another example: In distributed database systems, this idea allows many kinds of addition-like aggregations to be performed more efficiently by computing the local sum for each group under the active GROUP BY clause before combining the groups’ subtotals to yield the wanted gobal totals.
Basically, in any situation in which you have to compute a global sum of some kind over a collection of items, and some of those items are local to each worker, you can compute the global sum faster and with fewer resources whenever you can take advantage of a monoidal structure.
One point that really stuck with me is how people who apply category theory (say, people at the topos institute) use the concepts as little tools, and everytime a problem crosses their way, they try out all the little tools to see if one of them works, similar to how Feynman describes carrying out problems in his head until one day a technique unlocks something).
Having more general abstractions just allows them to be applied to more problems.
Specific reports needed various kinds of customization. Some needed to call the client's web API to get some extra data for each row (and since this was across the internet, it needed to be async). Some needed to accumulate statistics on each row and sum them over the whole report at the end. One needed to query an ancillary database for each row, but all those queries had to be part of the same transaction so that they would be consistent.
Now in theory you can do ad-hoc things for each of those cases. You could make the per-row method always async (i.e. return Future), so that you can override it to do a web API call sometimes. You could stash the statistics in a mutable variable in the report that accumulates them, remembering to do locking. You could use a session on the ancillary database bound to a threadlocal to do the transaction management (most database libraries assume that's how you're doing things anyway), and as long as your returned Futures are never actually async then it would probably work. But realistically it would be very hard to do safely, and I'd never have spotted the underlying symmetry that let me pull out the high-level logic. More likely we'd've stuck with three distinct copy-pasted versions of the report code, with all the maintenance burden that implies.
In principle an abstraction never tells you something you didn't already know - whatever properties you're using, you could've always worked them out "by hand" for that specific case. Like, imagine programming without the concept of a "collection" or any idea of the things you can do on a collection generically (such as traverse it) - instead you just figure out that it's possible to traverse a linked list or a red-black tree or an array, so you write the code that works on all of them when you need it. That's absolutely a way that you can program. But if you don't have this vocabulary of concepts and patterns then realistically you miss so many chances to unify and simplify your code. And if you have the rigorous category-theory concepts, rather than fuzzier design patterns, then you have quick, objective rules for figuring out when your patterns apply - and, even more importantly, when they don't. You can use library code for handling those concepts with confidence, instead of e.g. wondering whether it's ok for your custom iterator to expect the library to always call hasNext() before it calls next(). It's a huge multiplier in practice.
Everything after that point was not easy, and somewhere, there was a belief forming that maybe I was not so good at math.
Two things I encountered changed my perspective on that. The first was reading a math graduate student's experience where they talk about struggling with concepts, like groping about in a dark room until one day, you find the light switch and you see it all come together. The other is Kalid Azad's "Math, Better Explained" series. His approach is to introduce the intuition first, before going into the rigor. Between those two, I realized that I had never really learned how to learn math.
Once I started with the intuition, I also realized why people get stuck on things. There are people who don't get negative numbers. They keep looking for something concrete, as if there were something intrinsic to reality that makes negative numbers "real". And there isn't. And yet, in my day-to-day life, I work with negative numbers. I am far better able to reason and navigate the modern world because I grok the intuition of negative numbers.
Then there are the cultures where there isn't even a notion of natural numbers. Their counting system is, "one", "two", and "many". In their day to day life, to echo your sentiment, they can go on their whole life without the "insight" that things can be countable. Many of them probably won't even care that they don't have the intuition of countable things. And yet, in this modern culture, I find myself introducing the idea to my toddler.
Ultimately, it's up to each individual's decision how far they want to go with this. Each culture and civilization has a norm of a minimum set of intuitions in order for that person to navigate that civilization. Category Theory is outside that norm, even among the software engineer subculture. Perhaps one day, it will be the norm, but not today. Beyond that, no one can make you grok a mathematical intuition, as useful as they are once grokked.
Complex numbers: the number line is just a metaphor. What would happen if you make it 2D? Oh you can solve negative roots. Whenever a number needs to spin or have a negative root, it’s a useful tool. Numbers are tools. Cool, that’s “real” enough for me.
I know I will never ever use it. Or at least, not in my current state of how I live my life. I liked learning it, there’s something to be said for what is beyond your horizon. But I do think just like complex numbers, category theory needs a motivation that is compelling. In retrospect, learning complex numbers is most likely not useful for me.
Oh wait a second, complex numbers helped me to understand trig. I never understood it in high school but 3B1B made it intuitive with complex numbers
Nevermind, I’ll just let this all stand here. It’s fun to be open and convinced by myself to the other side while writing my comment :’)
I’m sure if I would have learned category theory, I would have written a similar comment as I think there are a lot of parallels there.
I remember my high school teacher teaching imaginary numbers in the trig class, and one of the other kids asked what they could be used for. This was an honors class and the kid skipped a math grade. Our math teacher couldn’t talk about it off the bat, and unconvincingly told us about applications in electrical and electronic engineering. We still went through it, but none of use knew why any of it was relevant.
I think if he had said “numbers can rotate”, maybe some of us (maybe not me!) might have been intrigued by the idea. It would have been a way to describe how EM rotate.
My own personal motivation for pursuing CT has to do with working with paradigms, and how are they related, and how they are not. Categories and morphisms seem to talk about the kind of things we can talk about with paradigms, yet much more precisely.
So for example anything you can do to “string” type that isn’t based on human language you should be able to do to an array of bytes, integers, or “well-behaved” objects. (Also “escaped” strings or strings in various encodings such as UTF-16 or MBCS.)
Show me a language that implements all of those functions with interchangeable and uniform syntax!
What does it even mean when I say “well behaved”? It means that those objects need to have some traits in common with characters, such as: copyable, comparable, etc…
To implement regex for objects would also require a “range” trait and perhaps others. Category Theory lets us talk about these traits with a formal language with mathematically proven strict rules.
With C++ template meta programming it’s possible to get most of the above, but not all, and it’s not used that way in most code. It’s also weakly typed in a sense that C++ can’t express the required trait bounds properly. Rust and Haskell try as well but still fall short.
Category Theory shows us what we should aspire to. Unfortunately the tooling hasn’t matured enough yet, but now that GATs have made it into Rust I’m hopeful there will be some progress!
Monoids are really simple, so you probably won't get a lot more examples there. But monads are a different story. Once you know the pattern, you really miss them. For example, Promises in Javascript are like painful versions of monads. There is a lot of special-case language support and reasoning around Promises that only apply to Promises. In a language with native support for monads, you could use the same code or libraries to handle patterns arising when using promises, as patterns arising with other monads like I/O.
In summary, if you never try it, you may never know or care what you're missing, but that doesn't mean you're not missing anything...
For example, it allows me to quickly draw a single arrow in a big architecture diagram because I know I'll be able to take a list of promises and turn it into a promise of a list, and then take a function that takes a list and returns more lists, and flatmap it on top of that. Even if Promise is "not really" a monad, and I will probably have to write the machinery manually.
It's what I mean when I say "you can bulldoze your way through a lot of broken abstractions when programming", but at the conceptual stage and when designing, you can get a lot of mileage out of the theoretical constructs.
Sorry for the wall of text, but I think to maybe help you understand why people (like me) like to work with it a bit more explicitly, I'll have to make it more concrete and give lots of examples. The good stuff comes at the end.
So let's say you have a list or array type. You want to aggregate all the things inside. Let me write pseudo code from here
let balances = List(1, 4, 23, 7)
let overallBalance = ???
How do you calculate that? Well it's simple - use a for-loop or call .reduce on it or maybe your language even has a builtin sum() function that works for lists of numbers right? let overallBalance = sum(balances)
Now what happens if you want to concatenate strings instead? Can you reuse sum()? Probably not - you will have to hope that your language / std lib has another function for that. Or you have to fall back to implementing it yourself.Not so if monoids are explicitly supported. Because then, it will be exactly(!) the same function (which has been implemented only once) - no type-overloading or anything.
let overallBalance = combineAll(balances)
let concattenated = combineAll(listOfStrings)
Okay, seems a little bit helpful but also doesn't seem super readable, so I guess that's maybe not very convincing. But the reason why I personally love to work with monoids as an explicit concept is the thing that comes next.Let's say you got bitten because you used plain numbers (or even proper money-types) for the balances but in the code at some point you mixed up two things and you added a balance to the users age (or something like that) because you used the wrong variable by accident.
You decide to make Balance an explicit type
class Balance( private innerValue of type number/money )
So the code changes let balances = List(Balance(1), Balance(4), Balance(23), Balance(7))
let overallBalance = sum(balances)
But the second line stops compiling. The std lib sum function doesn't support your custom balance class for obvious reasons. You will have to unwrap the inner values and then wrap them again (both for the sum() method or in your handwritten for-loop).In case you use a duck-typed language where you can just "delegate" the plus-operation to the inner value: congratulations, you are already using monoids without calling it like that. Unfortunately, there is no protection against problems, such as that + might mean different things on different types and they can be used with sum() but cause unexpected results (read: bugs).
In case you use a language that has good supports for monoids, you essentially have to add just one line:
a monoid exists for class Balance using Balance.innerValue
That's it. You can now do let balances = List(Balance(1), Balance(4), Balance(23), Balance(7))
let overallBalance = combineAll(balances)
And, magically, "overallBalance" will be of type Balance and be the aggregated balance.
In case you think that it can't work as easy as that, I'm happy to show you some runnable code in a concrete language that does exactly that. :)On top of that, it does not end here.
Let's take it a step further. Let's say don't only have the account-balances of a single person (that would be the example above). You have that for multiple people.
So essentially, you've got
let listOfBalances = List(
List(Balance(1), Balance(4)),
List(Balance(23), Balance(7)),
List(),
List(Balance(42)
)
And you want to calculate the overall combined balance. Now it gets interesting. Even in a duck-typed language, you can't use sum() anymore, because the inner lists don't support that. You will have to to fall back to a manual two step process, such as sum(listOfBalances.map(balances => sum(balances)))
But with monoids it's different. Since we know how to combine balances in a monoidic way, we also automatically know how to do that for a list that contains balances. In fact, we can do so for any list that contains something that we know of how to combine it. That means, without any other code changes required, you can simply do let overallBalance = combineAll(combineAll(listOfBalances))
This is recursive and goes as far as you want. And it does not only work with lists, but also Maps and other structures. Imagine you have a map with keys that are strings and values that are of any type but constrained to be a monoid. E.g. Map("user1" -> Balance(3), "user2" -> Balance(5)). Or Map("user1" -> List(Balance(2), Balance(3), "user2" -> List(Balance(5))). Or even maps of maps.Now if we have two of those and we know that the values are monoids, we can combine them as well, using again the same function, no matter what is inside. E.g.:
let map1 = Map("user1" -> Balance(3), "user2" -> Balance(4))
let map2 = Map("user2" -> Balance(5), "user3" -> Balance(6))
let aggregated = combine(map1, map2)
And the result will be Map("user1" -> Balance(3), "user2" -> Balance(9), "user3" -> Balance(6))
This is such a powerful concept and makes a lot of things so convenient that I'm always crying when I work in a language that does not support it and I have to handroll all of my aggregations.One note at the end: all of this can be absolutely typesafe in the sense that if you try to call combine/combineAll on something that isn't combinable (= is not a monoid) it will fail to compile and tell you so. This is not theory, I use this every day at work.
And it still doesn't explain why string concatenation being a monoid is useful in ruby. It's useful in Haskell because implementing one of their category-theory typeclasses means that type now inherits a stdlib-sized API that works for any monoid, not concrete types. But even Haskell hasn't proven that it's worth the jargon or abstraction after all these years; every other language remains productive without designing APIs at such a high level of abstraction.
Looking at the paper it is very dense and abstract, also 50 pages long.
Edit: on reflection I am doing a bit of sealioning which was not my intention but it does look that way. I'll try to read your paper but if you assure me cat theory really allowed you to do those things you claim, I'll accept you at your word.
Category theory is the language of diagrams; it studies math from that perspective. However, it turns out that category theory is equivalent to type theory (ie, what computers use). So the reason why diagrams can be reliably used to represent the semantics of type theory expressions is category theory.
My workflow of developing ubiquitous language with clients and then converting that into a diagram mapping classes of things and their relationships followed by translating that diagram into typed statements expressing the same semantics is applied category theory.
The “category theory types” are just names for patterns in those diagrams.
Trying to understand them without first getting that foundational relationship is the path to frustration — much like reading GoF before groking OOP.
Just to elaborate on this a little, a program of type B in context of variables of types A1, A2, ..., An, can be modeled as an arrow (A1 * A2 * ... * An) -> B. (More elaborate type theories get, well, more elaborate.) The various ways you might put programs together (e.g. if-expressions, loops, ...) become various ways you can assemble arrows in your category. Sequential composition in a category is the most fundamental, since it describes dataflow from the output side of one program to the input site of another; but the other ways of composing programs together appear as additional structure on the category.
Categories take "semicolon" as the most primitive notion, and that's all a category really is. In fact, if you've heard monads called "the programmable semicolon", it's because any monad induces a related category whose effectful programs `a ~> b` are really pure programs `a -> m b`, where `m` is the monad.
To understand category theory's appeal in a programming context, you must understand the duality of power in programming languages. Some people call a language "powerful" when it lets you do whatever you want in the particular scope you are currently operating in, so, for instance, Ruby is a very powerful language because you can be sitting in some method in some class somewhere, and you can reach into any other class you like and rewrite that other class's methods entirely. Some call a language powerful when the language itself is capable of understanding what it is your code is doing, and further manipulating it, such as by being able to optimize it well, or offer further features on top of your language like lifetime management in Rust. This type of power comes from restricting what a programmer is capable of doing at any given moment.
If your idea of "power" comes from the first sort of language, then category theory is going to be of very little value to you in my opinion. You pretty much have to be using something as deeply restrictive as Haskell to care about it at all. I am not making a value judgment here. If you never want to program in Haskell, then by all means keep walking past category theory.
Haskell is very much a language that is powerful in the second way. It restricts the programmer moreso than any other comparable language; the only languages that go farther essentially go right past the limit of what you could call a "general purpose language" and I daresay there are those who would assert that Haskell is already past that point.
Category theory then becomes interesting because it is simultaneously a set of deeply restricted primitives that are also capable of doing a lot of real work, and category theory provides a vocabulary for talking about such restricted primitives in a coherent way. There is some sense in which it doesn't "need" to be category theory; it is perfectly possible to come to an understanding of all the relevant concepts purely in a Haskell context without reference to "category theory", hence the completely correct claim that Haskell in no way necessitates "understanding category theory". You will hear terminology from it, but you won't need to separately "study" it to understand Haskell; you'll simply pick up on how Haskell is using the terminology on your own just fine. (Real mathematical category theory actually differs in a couple of slight, but important ways from what Haskell uses.)
So, it becomes possible it Haskell to say things like "You don't really need a full monad for that; an applicative can do that just fine", and this has meaning (basically, "you used more power than was necessary, so you're missing out on the additional things that can be built on top of a lower-power, higher-guarantee primitive as a result") in the community.
This mindset of using minimal-power primitives and "category theory" are not technically the same thing, but once you get deeply into using minimal-power primitives pervasively, and composing them together, you are basically stepping into category theory space anyhow. You can't help but end up rediscovering the same things category theorists discovered; there are a hundred paths to category theory and this is one of the more clear ones of them, despite the aforementioned slight differences. I'd say it's a fair thing to understand it as this progression; Haskell was the language that decided to study how much we could do with how little, and it so happens the branch of math that this ends up leading to is category theory, but as a programmer it isn't necessary to "study" category theory in Haskell. You'll osmose it just fine, if not better by concretely using it than any dry study could produce.
To be honest, I'm not even convinced there's that much value in "studying" category theory as its own thing as a programmer. I never studied it formally, and I wrote Haskell just fine, and I understand what the people using category theory terms are talking about in a Haskell context. If nothing else, you can always check the Haskell code.
There is a nearby parallel universe where the Haskell-Prime community in the late 1990s and early 2000s was otherwise the same, but nobody involved knew category theory terminology, so "monad" is called something else, nobody talks about category theory, and nobody has any angst over it, despite the language being otherwise completely identical other than the renames. In this universe, some people do point out that this thing in Haskell-Prime corresponds to some terms in category theory, but this is seen as nothing more than moderately interesting trivia and yet another place where category theory was rediscovered.
But I bet their Monad-Prime tutorials still suck.
> But I bet their Monad-Prime tutorials still suck.
Funniest thing I've read today. Thank you for that gem.
I want to emphasize how true this is. In the Haskell 1.2 report, before Haskell Monads existed, the type of `main` was more or less:
type Dialogue = [Response] -> [Request]
main :: Dialogue
The main function was took a lazy list of, let's call it OS responses, and issued a lazy list of requests to the OS, whose response you would find at some point on the input list. Your task was to keep your lazy generation of `Request`s in sync with yourself as you consumed their `Response`s in turn. Read ahead one to many `Response`s and your `main` would dead-lock. Forget to consume a `Response` and you would start reading the wrong `Response` to your request.The suggestion was to use continuation passing style (something that later we see conforms to the Continuation monad interface) to keep things in check, but this was not enforced, and by all accounts was a nightmare.
So yes, Haskell's laziness straight-jacket (Haskell was basically invented to study the use of lazy programming without having to purchase Miranda) basically means they had to invent the use of Monads for programming, or at the very least invent the IO monad.
Those who learn either or both, have a larger set of mental abstractions that largely transcend any one programming language. They can say, this thing and that thing share a common salient property through a certain abstract pattern. There is a certain sense of mathematical beauty in recognizing common patterns and mechanisms. There is also the choice of subtly or drastically altering one's coding style. The same with each, a programmer can use just the right design pattern in just the right place and perhaps simplify or codify a section so that it's easier to understand the next time around (or decide not to!) or they can go completely wild and over-pattern their code. The same applies to category theory. One can recognize that some section is actually a closed reduction and accepts any semigroup and then decide to codify this into the program or not.
I tend to be a collector of ideas, so learning category theory and it's application in programming gives me all these little gems and ways to re-understabd code. Sometimes I use them and sometimes I don't, but my life is more joyful just knowing them regardless.
"The Essence of the Iterator Pattern"[0] posed an open problem in type theory as to whether or not "lawful traversals", of type `∀ F:Applicative. (X -> F X) -> (B -> F B)` could appear for anything other than what is called a "finitary container"[1], B := ∃S. X^(P S) where the (P S) is a natural number (or equivalently a polynomial functor B := ∃n. C_n * X^n). Or rather, there was an open question as to what laws are needed so that "lawful traversals" would imply that B is a finitary container.
Eventually I came up with a fairly long proof[2] that the original laws proposed in "The Essence of the Iterator Pattern" were in fact adequate to ensure that B is a finitary container. Mauro Jaskelioff[3] rewrote my proof in the language of category theory to be few lines long: use Yoneda lemma twice and a few other manipulations.
Anyhow, the upshot of reframing this as a simple argument in Category Theory meant that, with the help of James Deikun, I was able go generalize the argument to other types of containers. In particular we were able to device a new kind of "optic"[6] for "Naperian containers" (also known as a representable functors) which are of the form B:=P->X (aka containers with a fixed shape).
Since then people have been able to come up with a whole host of classes of containers, optics for those classes, representations for those optics, and a complete characterization of operations available for those classes of containers, and how to make different container classes transparently composable with each other[4].
For example, we know that finitary containers are characterized by their traversal function (i.e the essence of the iterator pattern). Naperian containers are characterized by their zip function. Unary containers are characterized by a pair of getter and setter functions, something implicitly understood by many programmers. And so on.
I don't know if all this means that people should learn category theory. In fact, I really only know the basics, and I can almost but not quite follow "Doubles for Monoidal Categories"[5]. But now-a-days I have an "optical" view of the containers in, and forming, my data structures, slotting them into various named classes of containers and then knowing exactly what operations are available and how to compose them.
[0]https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator...
[1]https://en.wikipedia.org/wiki/Container_(type_theory)
[2]https://github.com/coq-contribs/traversable-fincontainer
[3]https://arxiv.org/abs/1402.1699
[4]https://arxiv.org/abs/2001.07488
[5]https://golem.ph.utexas.edu/category/2019/11/doubles_for_mon...
Would I be correct in saying — there are less complex objects, which are defined in category theory, that are useful in a generic way in combination with one another?
If the answer to that question is yes, is there a place I look to find the mappings of these software topics/designs to these category theory objects such that I can compose them together without needing the underlying design? If that were the case, I could see the use in understanding category theory so that I could compose more modular and generically useful code.
I have to admit though, I’m still fairly speculative and my thinking on this is very abstract and probably very flawed.
Now of course this sounds trivial, but you can build much more refined concepts out of that simple idea. For example, creating product and sum types.
And that's where the rabbit hole starts.
The new "optics" people are have designed in reference 4 are not themselves "elements of category theory", but category theory provides the theoretical framework that these composable optics live in. Once that framework is identified, it becomes much easier to say "oh, this new optic definition I've just identified, fits into this framework, and so does this other optic, and so forth"
To make a loose analogy, one could imagine identifying triangles and cubes and such and such that have these rotational and reflective symmetries, and notice how you can compose those operations, and then see how performing specific shuffles of cards is also composable and has some properties similar to rotating cubes in that repeated operations have a certain period and other such things. Then when someone introduces you to group theory and says yes both the cube rotations and the card shuffling can all be seen as a group and group theory can be used to generically answer your questions about these otherwise seemingly very different operations. And then you start seeing that lots of things are groups, like that Rubik cube puzzle you have, or lines through an elliptic curve. Coming up with new optics is analogous to noticing various things are groups, a task that is much easier when you have group theory and know how to recognize a group when you see it.
That said, I think a better answer to your question may be in one of my later replies https://news.ycombinator.com/item?id=33806744: Category Theory has been useful for devising an entirely new (functional) programming abstraction, the theory of "optics" in my case. But for day-to-day programming you don't normally need to invent entirely new programming abstractions, you can just use the ones that have already been developed by the computer scientists.
Typically when something is confusing to me and I see nothing to grasp onto, it usually means something is flawed or poorly communicated. In other words, when no one can explain why something has utility (in everyday programming) and there are a fair number of responses, then the chance of bad communication goes down and bad reasonings (non-answers) goes up. I have a feeling it is not coincidence that you have the opinion it is not utilitarian in everyday programming and that I responded to your initial post.
Alot of the generic interfaces you design in OOP end up being useless. Never re-used and pointless. You never needed to make these interfaces in the first place
In category theory, you will be able to create and identify interfaces that are universal, general and widely used throughout your code. Category theory allows you to organize your code efficiently via interfaces and thus enable Maximum reuse of logic.
When creating an interface you largely use your gut. Category theory allows you to draw from established interfaces that are more fundamental. Basically class types in haskell.
If you want to see the benefit of category theory, you really need to use haskell. Monads for example are a pattern from category theory.
Also a lot of what people are talking about in this thread is exactly what I'm saying^^. I'm just saying it in less complicated jargon.
Or Scala. They're the only two common-use languages I'm aware of whose type systems are expressive enough to define what a monad (or a category, or a ...) is internally. Of course you can identify particular monads (or ...) in any language, but you can't talk about them inside most languages.
I wager that you can get pretty far with compile-time macros, even as rudimentary as C's, to encoder a fair bit of generic monad machinery.
A good example is LINQ, they use monads and functors. They just gave them other names.
- Triviality
The most trivial cases are actually the most important. If I build a little DSL, I give it an explicit "identity" construction in the internal representation, and use it liberally in the implementation, transformation phases, and tests, even though obviously its denotation as a no-op will be optimized away by my little DSL compiler, and it may not even be exposed as an atomic construct in the user-facing representation.
- Free constructions
When it's difficult to separate a model from an implementation, when simple interfaces are not enough because the model and implementation are substantially complex, multilayered systems on their own, often the answer is to use a free construction.
Free constructions are a killer tool for allowing the late-binding of rather complex concerns, while allowing for reasoning, transformation, and testing of the underlying components without worrying about that late-bound behavior. "Late-bound" here does not have to mean OO-style dynamic dispatch -- in fact now you have more expressive power to fuse some behaviors together in a staged manner with no runtime indirection, or to make other behaviors legitimately dynamic, differing from one call to the next. Tradeoffs in the flexibility/performance space are easier to make, to change, and to self-document.
This is particularly useful when you need to start addressing "meta" concerns as actual user-facing concerns -- when your project matures from a simple application that does Foo into a suite of tools for manipulating and reasoning about Foo and Fooness. Your Foo engine currently just does Foo, but now you want it to not just do Foo, but return a description of all the sub-Foo tasks it performed -- but only if the configuration has requested those details, because sometimes the user just wants to Foo. So you embed the basic, non-meta-level entities representing the actual component tasks in a free construction, compose them within that free construction, and implement the configurable details of that composition elsewhere.
- "Sameness"
Making clear distinctions between different notions of sameness is important. Identity, equality, isomorphism, adjointness -- sure, you might never explicitly implement an adjunction, but these are important notions to keep separate, at least conceptually if not concretely in source code entities, as a piece of software grows more complex. If you treat two things as fundamentally the same in more ways than they really are, you reach a point where you now can no longer recover crucial information in a later phase of computation. In an indexed/fibered construction, this X may be basically the same as that X, but this X, viewed as the image of a transformation applied to Y is quite different from that X, viewed as the image of a transformation applied to Z, although the images are equal. So can I just pass my X's around everywhere, or do I need to pass or at least store somewhere the references to Y and Z? What if computationally I can only get an X as the result of applying a function to Y, but conceptually the functional dependency (that is, as a mathematical function) points the other way around, from X to Y? Paying attention to these easy-to-miss distinctions can save you from code-architecting yourself into a corner. You may discover that the true reserve currency of the problem domain is Y when it looks like X, or vice versa.
Patterns for decomposing problems and composing solutions.
To ask “How is it useful?” Is to ask “How are design patterns useful in OO?”.
But you already know the answer…
Is there an advantage over just inventing your own vocabulary? Yeah! You don’t have to reinvent 70+ years of Mathematics, or teach your language to other people before you can have a meaningful design discussion.
What do those 70 years of mathematics do for me as a software engineer?
>or teach your language to other people before you can have a meaningful design discussion.
It's the other way around. Most engineers will have no clue what you are saying.
You mean other than inventing the entire industry in which you work? It's a weird question that I have no idea how to answer for you.
What is 70+ years of computer science doing for you as a software engineer? Are you standing on the shoulder of giants; or are you inventing/re-discovering everything by yourself from first principles?
>It's the other way around. Most engineers will have no clue what you are saying.
So how do engineers understand each other if we all invent our own meta-language to speak about software design in the abstract?
Misscommunication is what happens by default unless you intentionally become part of a community and learn the meta-language. Mathematics is one such community which has existed for millenia and benefits from the network effect. It's an anti-entropy thing.
There are many parallels here to the Tower of Babel.
No, what does category theory unlock from mathematics that I can not get without it.
You can re-discover and re-invent all of computation, Mathematics and physics.
Heck, you don’t even need computers if you have pen and paper.
You can do absolutely everything all by yourself. All you need is infinite time.
more pragmatically, libraries like redux, react are heavily influenced by concepts from functional programming, rust has novel concepts from type theory, data engineering in the cloud age leverages a lot of algebraic concepts to achieve massive data throughput. We have parser combinators, lambdas, stronger typing, map and flatmap in most languages these days. These all come directly from mathematics.
Functional programming concepts don't require learning category theory
>rust has novel concepts from type theory
Type theory isn't category theory. Rust's borrow checker was not inspired by affine types.
https://smallcultfollowing.com/babysteps//blog/2012/02/15/re...
>data engineering in the cloud age leverages a lot of algebraic concepts to achieve massive data throughput
This doesn't requite category theory and those are basic concepts from algebra that you can learn outside of the context of algebra.
>We have parser combinators, lambdas, stronger typing, map and flatmap
These don't require category theory either.
What does that give you? For me, I think it gives me an easier way to see common abstractions across different problems I work on.
I am at the beginning of my CT journey itself, but a layman's understanding of monads, functors and applicatives gets me really far writing pretty much the same code when i do bare-metal embedded or frontend javascript. The point is not that I couldn't write bare-metal code or frontend code without, is that I am much quicker seeing "my javascript promises are like a monad" and "my coroutines are like a monad" and being able to shrink my cognitive load.
This is such a reductionist world-view. Programming concepts don't require you to learn the theory of computation either, but having a theoretical/abstract grounding for what computation is disconnected from any particular programming language/model of computation helps. A lot.
>Type theory isn't category theory
It depends on what you mean by "isn't".
There is a 1:1 correspondence between type theory and category theory constructs.
https://ncatlab.org/nlab/show/computational+trilogy#rosetta_...
The 80/20 rule really applies here. Most of the time there's only a few key type classes that people use. Pretty much just Monad, Monoid, Applicative, Functor. If you grok those, then what people say makes a lot more sense.
> Can anyone give me a practical applied way in which category theory is a benefit to your design
Monad is probably the best example, because so many different, important libraries use it. Once you know how to code against one of them, you kinda know how to code against all of them!
The following is code written in the STM monad. It handles all the locking and concurrency for me:
transfer x from to = do
f <- getAccountMoney from
when (f < x) abort
t <- getAccountMoney to
setAccountMoney to (t + x)
setAccountMoney from (f - x)
This same code could be in the State monad (excluding the line with 'abort' on it). Or it could be in the ST monad. Or the IO monad. Each of those is doing radically different things under the hood, which I don't have the brainpower to understand. But because they expose a monadic interface to me, I already know how to drive them using simple, readable, high-level code.As well as the 4 monads mentioned above, parsers are monadic, same with Maybe and Either, List. The main concurrency library 'async' is also monadic (though it just uses the IO monad rather than defining an Async monad).
Often the people writing your mainstream libraries know this stuff too. If you're calling flatMap (or thenCompose), you're calling monadic code. The writers just shirk from calling it monadic because they're worried people will suddenly not understand it if it has a scary name.
1. Because the underlying concept for handling composing locks (STM, which I don't know much about, but I'm going to assume is a way to wrap operations within a transaction) is sound
2. it is easy to define the corresponding monad
3. which makes it usable in a do notation
4. producing readable and future-proof code
The step 2 is almost trivial, 1 is where the thinking is at. The do notation is nice to have, but not that important (to me at least). I have no problem writing this in CPS style when using javascript or c++:
const transfer = (x, from, to, onSuccess, onError) => {
return getAccountMoney(from, (f) => {
if (f < x) {
return onError();
} else {
return getAccountMoney(to, (t) => {
return setAccountMoney(to, t+x, () => {
return setAccountMoney(from, f-x, onSuccess, onError);
}, onError);
}, onError);
}, onError);
}
}
The main point is that I can add 80 edge cases to this and it will continue to compose nicely, not a
single lock in sight.Oh and also when you recognize your design to be something from ct its probably quality. Shit code cant be described with simple math (simple as in all math is simple, not as in math that is easy to understand).
I think of category theory as an easy way to remember things. If some concept can be expressed as category theory, it can often be expressed in a very simple way that’s easy to remember. However, if you try to teach math using category theory from the beginning, it feels a little like trying to teach literature to someone who can’t read yet.
Anything directly useful from category theory can be expressed as something more concrete, so you don’t NEED category theory, in that sense. But category theory has the funny ability to generalize results. So you take some theorem you figured out, express it using category theory, and realize that it applies to a ton of different fields that you weren’t even considering.
The effort to reward of category theory is not especially high for most people. It did give us some cool things which are slowly filtering into day to day life—algebraic data types and software transactional memory are two developments that owe a lot to category theory.
The part where it gets wacky is when you realize that “categories” is, itself, a category.
Mathematics is the mathematics of abstraction. That's all you're doing in math, from the beginning to the end. What's the same between "I have two sheep in this pen and five in that one" and "I have two apples in this basket and five in that one"? Hmm, you can abstract out 2+5=7, since it works the same in both contexts.
Everything in math is creating and exploring abstractions. The good ones tend to stick around, the bad ones tend to be forgotten.
Category theory is the mathematics of composition. How much knowledge can you throw away and create reusable patterns of composition? How many interesting things can you find by adding back the smallest bits of additional structure on top of that minimum? How well do those things compose together, anyway?
> Category theory is the mathematics of composition.
I don't know why you're being downvoted (tone?) but you're right. Categories formalize exactly and only the notion of composition; its power is that this notion appears everywhere when you know what to look for. Most mathematical abstractions have composition baked in at one level or another, so I think the author should be pardoned for their phrasing; but I find yours more enlightening.
It will be a lot less rewarding than you expect RE: your engineering ability/understanding.
I have no formal critique of him, or even concrete things to point at to say why it doesn't work for me, but I wanted to add my comment in case someone else was also feeling discouraged by not taking much away from the writings/lectures of an often and highly recommended person.
I generally learn very well from lectures/essays, but that hasn't been true for me re: his work.
It might just be a stylistic thing. I can't quite pinpoint it.
Edit: Eugenia Cheng also makes more sense to me than Bartosz.
My complaint has nothing to do with whether CT is useful or practical. By contrast, abstract algebra can be taught (e.g., in Herstein's books) as pure abstraction (very dry and without presenting any real-world connections), but you reach Lagrange's theorem right away -- which is an simple-but-awesome result that will wake up your brain. You reach Cayley's theorem and others quickly, and each is more exciting that the last. And this is all while still in the realm of purely-abstract math.
One of the most interesting things in CT are adjoints. They happen literally everywhere. For example, static analysis via abstract interpretation is an example of adjoint (which in this case is called Galois connection). Free structures also give rise to adjoints.
as with every abstraction, its value is in the value it brings you. That sounds a bit tautological, but I think that's the crux of the matter. If you like abstraction, and abstractions help you think, you are going to find value in them. If you feel they bring you nothing, then there is no need for you to use them.
- Adjoints preserve limits/colimits.
- Adjoint functors give rise to a monad
- They are connected to universal morphisms
Again, with my abstract algebra example from above: after just a couple of basic abstract algebra definitions, you learn about subgroups. Simple enough, and not particularly exciting so far. But then you quickly reach Lagrange's Theorem, which shows that in a finite group, the number of subgroups divides the order of the parent group. And that means... if the group has a prime number of elements, then it can't contain any (non-trivial) subgroups at all! That's super cool and not at all obvious in the original definition of groups and subgroup. And it keeps going from there, with a brain-punishing amount of results that all just emerge from the basic definitions of a group, ring, and field.
In contrast, category theory just felt empty. This is definition of a functor. This is a monad. Here's different kinds of morphisms. Etc.
Dunno, maybe I just needed to keep reading. But my sense from flipping forward through my CT books is that it was mostly just more concept definitions.
- An operation may be "functorial", meaning that it preserves more structure than perhaps originally thought. For instance, the "fundamental group" operation is indeed functorial, which means that it acts on continuous functions in a nice way as well as topological spaces. Other examples are tensor-product, vector-space-duality, forming function-spaces in certain categories, etc.
- Two categories may be isomorphic, but this is trivial.
- Two categories may be equivalent, which while a weaker notion than isomorphism, is sufficient for most things which can be expressed in categorical language to be true for both categories. This is helpful when one category is well-understood and the other one is an object of present interest. (One application is showing that the category of representations of a fixed quiver Q is Krull-Schmidt, by showing that it's equivalent to another category with the Krull-Schmidt property).
- A functor between two categories may admit a left-adjoint. It then immediately preserves all limits (it's "continuous") which immediately means that a great deal of structure gets preserved by it.
- A functor between two categories may preserve all limits. It may (under some circumstances, expressed by the "adjoint functor theorem") therefore admit a left adjoint. This may be a non-trivial fact of interest in its own right. It's related to dualities in optimisation and game theory.
- There's isolated results like the Seifert Van-Kampen Theorem (which states that the Fundamental-Group functor preserves pushouts) which would be difficult to express without categorical language.
Ultimately, Category Theory appears to be a language for compressing complicated facts about structure-preservation of operations.
Category theory is helpful in advanced algebra, and helpful too in advanced topology, and is in its absolute element in any area which combines the two, like algebraic topology and algebraic geometry. In the latter two areas, you've got lots of functors between algebraic categories, lots of functors between topological categories, and even functors going between algebraic categories and topological categories.
There's also categorical logic, which is where the CS-adjacent stuff seems to be found. But this is of little interest to everyday programming, and is very forbidding for people who lack the requisite mathematical maturity. Only the most dedicated should enter its harsh plains, and should expect to gain nothing without tremendous efforts and sacrifice.
IMO, the main value of category theory is unifying existing math knowledge in one theory. I.e. it helps you see connections between seemingly unrelated areas of math. I.e. in some sense it's a pure abstraction.
- Adjoint between integers and real numbers: https://math.stackexchange.com/questions/598075/find-the-lef...
- Adjoint between lattices in abstract interpretation. I.e. abstraction relation between abstract and real interpreter.
I don't know enough about the second example.
Yep. Most of the example of adjoints trivial if you know the field of math where they are used. The interesting part is why it happens almost everywhere.
P.S. I don't believe programmers need to know category theory. However, it's beautiful by itself like many art.
- You don't need category theory to be a programmer (or good or 10x or 100x programmer)
- It makes sense to learn category theory because its beautiful like it makes sense to study arts, literature or philosophy. Not because it useful, but because it gives you pleasure.
The fact it's so rare to find a counterintuitive fact in CT, so that you rarely find yourself proving something and you mostly spend your time constructing effective tools, it's a feature not a bug! McBride's "Don't touch the green slime!" is a great paper/saying about this principle. Work with the compiler, not against it. The compiler is very dumb so it understands only trivial things.
There's a legacy pseudomachist culture of 'hard is good' in mathematics (but really, it's transversal to science) which rewards working hard and not working smart.
This is correct. The only somewhat interesting result in Category theory is the Yoneda Lemma, everything else is machinery for diagram chasing. It's ubiquitous in the same way that short exact sequences are ubiquitous -- and about as interesting as short exact sequences.
I think most hobbyists or those who are intellectually curious would be better off studying physics or engineering first, and then picking up the math with applications along the way. For example, you can study multivariable calculus and then complex analysis, which naturally leads to questions of topology as you find try to solve integral equations, and then obstruction theory can come up. Lots of cool stuff to study that is actually interesting. I would never foist category theory on someone who finds math to be interesting and enjoyable -- that would be like throwing a match that just caught flame into the rain.
https://personal.cis.strath.ac.uk/conor.mcbride/PolyTest.pdf
Many relate his work as closely tied to Gödel's incompleteness theorem but for biology. The purpose of using category theory is that it is general enough to not talk about specific parts of an organism's construction, but rather how their general functional parts relate to each other.
Software engineers and (non-categorical) mathematicians are both forced to deal with patterns of abstraction on a regular basis, so they naturally pick up a lot of what category theory concerns itself with. This can make category theory sound like quite a lot of words for very little gain. But I do think that formalizing one's intuition -- taking something understood implicitly and giving it explicit form -- can be a useful tool in its own right.
Most of the material out there on category theory attempts to formalize the algebraist's intuition. Software engineers then have to learn algebra just to learn category theory. I don't think that's essential to category theory, and I think we're starting to see more and more "elementary" category theory that doesn't take some other whole edifice as given. The fact that so many software engineers exist who do find value in a categorical perspective (it's more than you'd expect!) suggests that we'll get there eventually.
There seems to be a tiny handful of people that can use category theory as a resource to craft something relevant to software (the stereotypical example in my mind being Edward Kmett of Haskell Fame), but I am certainly not one of them, and that is not something that would change with learning more Category Theory (whatever that might mean: Proving some theorems, discovering more categories, ...)
To the author: I am looking forward to a retrospective at a later time. I wish you a good journey, happy diagram-chasing!
As I've learned more about category theory (and its applications), I've found string diagrams to be a really nice tool. But string diagrams flip the above around -- morphisms are boxes, and objects are wires. A wire is just a point-to-point link; it doesn't do anything on its own, it just connects a port of one type with a matching port on another site. It's the graphical realization of the composition operation itself.
Meanwhile, morphisms are the things that actually have behavior and real identities, so we draw them as boxes with inputs and outputs and give them names. This lines up with my understanding that in category theory, it's the morphisms that matter; the objects are no more than labels governing how you can stick the morphisms together. You may as well call the morphisms "widgets" or "components", and call the objects "interfaces", because that's literally correct.
This ends up really nice in the context of distributed systems, where concurrency gives you a monoidal category. In string diagrams, concurrently is quite literally the result of putting two diagrams side-by-side, rather than connecting them end-to-end. It's lovely, and some of the research I'm doing right now is actually founded on formalizing the message-passing causal diagrams distsys researchers and practitioners use every day, and using them as a vehicle for building programs that are more amenable to being proven correct. (Distributed systems are hard to verify for both humans and computers; I'd like to think that what I'm doing will be just as good for giving human practitioners a good framework for convincing themselves their code is correct -- or better, making bugs more obvious.)
>I hope that category theory will allow me to formalize my intuitive understanding of abstraction, put words to it, and use those words (or, better, words others have written) to explain my thinking.
Note how this goal doesn't include anything about improving the author's software engineering ability. The goal is to be able to use category theory to describe existing concepts. Why not us just describe it in ways that 99% of software engineers already understand? Why not learn good software design directly instead of through a subset of category theory which you have to translate?
However, it is "generative" knowledge. Having a good intuition for monads is what allows me to break down a fair amount of the mundane tasks I have to do (say, breaking down and migrating infrastructure configuration) in steps that work and stand the test of time. That's the thing about say, monad. An applied monad just looks utterly trivial, because, really, they are. If they weren't they wouldn't be worth using.
It also allows me to mine the literature or reuse things I've worked out in the past, for example exploiting monoidal structure for optimizing computation by leveraging associativity, for example.
Does it for everybody? Most certainly not.
This is an algebraic concept and it is possible to learn this idea directly in a software engineering context than a mathematical one.
Learning almost anything is useful in some capacity. Is everything you learn equally important? Maybe, maybe not, but you never know in advance how things might end up useful.
Of all the things one can study, math seems to be a pretty safe bet in terms of ROI.
I am not against learning category theory because you find it interesting, but one should be honest that it isn't going to benefit ones software engineering ability at least compared to studying good design directly.
>math seems to be a pretty safe bet in terms of ROI.
Ask most people how much math they have used of which they learned it university or high school. There are niches of math that are useful to niches. Study the wrong niche of math when you are in the wrong niche that doesn't use it, it will be a poor investment.
Understanding and learning to identify more and more monads was a big threshold for me. I never write "monad" in my code, because that's not I apply this knowledge. It's knowing how to write say, an interrupt handler, so that it will compose cleanly with 3 more interrupt handlers, or in such a way that I can easily simulate my embedded system with 5 lines of javascript.
Although, applying CT to lisp ( . ) bit more handy (to quot(.) or quote()
I find her YouTube talks and lectures to be easier to follow.
Most CT texts introduce categories around page 1, and the Yoneda lemma a handful of pages in. Cheng's book builds up intuitions until chapter 8, where categories are defined over the course of several pages, and supposedly (I'm not there yet) ends the book with the Yoneda lemma.
You might think this means the book is mostly fluff. First: if you read it and think that, you're probably a more advanced reader than she's targeting. Second: goodness no, it's packed to the brim with categorical intuitions; there's a whole way of thinking that she's trying to motivate. Categories are just a formalization of this way of thinking; if you're not onboard with the thinking, the formalization is going to be hollow to you no matter what.
Do recommend.
As for me it helped me with understanding some of what was being discussed on r/haskell. It didn't help one iota getting me a Haskell job though, failed every Haskell code pairing I've done.
(edit: typo)
Google search first result:
Functor - Wikipedia
https://en.wikipedia.org › wiki › Functor
In mathematics, specifically category theory, a functor is a
> monoidYou can know these things and still know nothing about category theory.
As far as mathematics is generally concerned, I think the category theory concept is the origin. Monoids, on the other hand, did exist before category theory; the modern conception as one-object categories is nice, but it's definitely an import.
To give credit where credit is due, most of the time some application functionality being described through FP jargon gives a better spec of its behavior than the same through OOP jargon.
https://publish-01.obsidian.md/access/017fdca1a82df1fa88d36b...
https://publish-01.obsidian.md/access/017fdca1a82df1fa88d36b...
https://publish-01.obsidian.md/access/017fdca1a82df1fa88d36b...
This looks very mundane but I do think about it very mathematically as well: user interaction with a search engine. The "encode" arrow for example is very much about NLP and tokenizing, which is a functor from the "category" of natural language to the functor of "lucene tokens" which then has a functor to "lucene queries". This is of course the very mundane typing of functions as:
function parseQuery(query: NaturalLanguageString): LuceneQuery
nothing spectacular, but the abstract approach means I know I can cache/batch/precompute/distribute/pipeline/modularize it.
Similar mathematical concepts apply to all the other arrows, even if some are a bit wild (how does a search result influence a person's ideas?) But it means I can try to model the "wildness", and create say a probabilistic model to exercise and understand how my search engine actually performs (see for example click models and probabilistic graphical models)
I hope that makes sense?
edit: forgot picture link
https://redux.js.org/understanding/history-and-design/prior-...
Although Redux is like Elm, the language (Typescript or ES) make immutability hard so it doesn’t feel as natural a paradigm.
Also pragmatically calling setState from an event is just easier for small projects.
Having no side effects in JS is easy (just don't do it!) but immutability takes some effort.
React requires immutability so that if it sees the reference to an object again, it knows that it contains the same data. If it promised to work when mutating objects it would continuously need to deep search inside them to see what changed.
In JS, some array operations mutate the array, some copy it, you have to know specifically what operation you are using. In Elm, nothing mutates objects. All built in functions and functions you create will not do this.
In short - you can do (state, action) => state in any programming language, but mistakes caused by mutations are impossible in Elm by design.
Hope that makes sense.
So hopefully nobody is feeling intimidated by fancy mathy stuff. Haskell is just this cool, productive programming language.
Will it make you write code that is more optimal and yet harder to penetrate by others?
const renderer = props => <h1>Hello {props.pageName}</h1>
const Title = ReactComponent(renderer).contramap(props => { pageName: props.title })
Title.fold({ title: 'Home page' }) // <h1>Hello Home page</h1>