In my experience, programmers who try to "think generically" tend to write overly abstract code that just gets them into trouble and leaves a mess for the next programmer. So maybe knowing category theory makes you a worse programmer?
It seems like this question would be more convincingly answered by showing how a practical program can be improved by knowing some category theory.
> In my experience, programmers who try to "think generically" tend to write overly abstract code that just gets them into trouble and leaves a mess for the next programmer.
Amen to that. I think part of what makes a programmer "good" is knowing where to draw the line between flexibility and maintainability. Personally, I start with very little abstract code, and only "blow it out" into abstraction when the flexibility is demanded by other code/interfaces. The trick to managing this kind of style is to never say "no" (barring special circumstances) when flexibility/abstraction is required, so you're rarely writing kludges, and to always say "yes" when you realize that some abstractions have consolidated and can be de-abstracted, so you're rarely leaving cruft.
I think that might be a language-dependent phenomenon. Most mainstream languages aren't suited to building useful abstractions. But we might all be talking past each other without examples.
I think the opposite is true: more abstract code is easier to maintain. Why? Because the abstractions constrain what you can do in well-defined ways. Code for your particular domain tends to be somewhat undefined. What sorts of behaviors should you allow? What sorts shouldn't you allow? The answers are going to differ with each domain.
On the other hand, these questions are very well defined for mathematical concepts. If all you know is that you're operating on a functor or on a monad, the possible behavior you can rely on is based on concrete mathematical laws. As a simple example, take lists. If you just treat lists as lists, there are all sorts of possible problems you can run into: off-by-one errors, not accounting for empty lists and so on. If you write code across all functors, on the other hand, you cannot make any of those errors.
Essentially, abstraction like this lets you concentrate all the possible errors in a very small portion of your code. You can write the abstract code assuming that the functor laws or the monad laws or whatever applicable laws hold. Moreover, since the code is polymorphic, the type system guarantees that you cannot do anything not provided by the abstraction you chose. Code that does something beyond the abstraction is simply not well-formed--it relies on properties of the type you simply cannot access.
This means that all your code will be valid given that the type behaves like a proper functor or monad or whatever. Now the only code you have to test for your particular domain is the functor or monad instance. Once you prove that your monad instance follows the monad laws--which really isn't very difficult in most cases--you know that all your abstract code also works for this particular domain. So now the only place you can have an error in is the code making your type an instance of some type class. This should be much easier to maintain than having a bunch of ad-hoc code for each particular domain!
In short: more abstract code is easier to maintain because it restricts the programs you can write. If you rely on as little knowledge about your particular domain as possible, you will have far fewer places to make a mistake. Mathematical abstractions are particularly good because they tend to be far better defined and understood than purely programming abstractions. Something like Iterable doesn't have the sort of mathematical laws that classes like Functor and Monad do.
I'll agree that if you have an important invariant then it's nice if the compiler can check it. But the question to ask is: where did these constraints come from? How do you know they're true? What if they change?
If they're business requirements: can you undo them if the business changes? If they're environmental constraints: do you really know mobile phones or browsers or the server OS that well, and what do you do when a new version of the environment comes out and invalidates your assumptions?
Generally speaking, it seems better to take our inspiration from scientists doing empirical work, rather than from mathematicians. Use tests to figure out how the code you depend on really behaves, not how you wish it would behave.
Agree. The urge to over-abstract systems seems to stem from the same human tendency to see patterns where none exist. In the latter, we ascribe hidden structure, in the former, we create hidden structure.
It seems like this question would be more convincingly answered by showing how a practical program can be improved by knowing some category theory.