More abstraction ramblings
One thing often missing from “let's rewrite something with some design pattern” articles is that many different ways of writing something that, on the surface, seems like the same code are valid. If you are writing golang because that's what the team uses, you can be as extraordinary a functional programmer as you want; the
if err != style is going to be the best, simply because that's how your team communicates. If you are on a team of Haskell programmers who know how to instantiate a Monad type class, use Monad.
The value of knowing the concept of a monad is that mentally, you can think of both ways of writing the code as the same: chain things together repeatedly, with the next step somehow being related to the previous step. When I write
if err !=, which is my preferred way of doing this kind of error handling, I still think of it as a monad. If I want to refactor this method in terms of saying, passing in an object that counts the types of errors for telemetry, I know that this is equivalent to lifting a state monad into it, at least abstractly. That I, in practice, then pass in a mutexed global object or actually use some liftM / monad transformer machinery doesn't change my thinking.
The way I think of abstraction now is that it has two directions.
One direction is about seeing the abstractions, being able to boil down a concrete instance into a more abstract concept by forgetting the details. By doing that, you can do some more interesting, powerful logic on it. You can use further concepts to uncover new properties, maybe. You can shortcircuit complex refactors by mapping them to a simple transformation in this more abstract space.
The other direction is transforming your abstraction back into something concrete. You don't have to use a language that allows you to formulate things at the new abstraction level, although it's cool if you can. But you can totally write functors in C; in fact, everybody does anyway. More importantly, when you do that transformation back from abstract-land to concrete-land, you can take a lot of shortcuts as a programmer. It's cool that you can do all kinds of fancy stuff with a pure function, but if I want to use a mutating function to encode my functor and it works in practice, I can still think of it as a functor for most cases.
One concrete example I wrote about yesterday is the concept of a product in category theory. It's a very abstract idea, if you have these morphisms and these objects and you can do this and that then it is called a product. The usual approach is to then say “and that's what tuples and structs are” and moving on, but it falls way short of what I think this gives us. It personally allows me to see that if I have a function that parses a string into a date, it's fine for me to say, store a date as a string, because it's abstractly equivalent. If it sounds obvious, it's probably because that's an abstraction you have spent time already forming and internalizing, but probably didn't have precise mathematical language for it that now allows you to mine a huge swath of mathematical literature for further cool tricks.