I finally had to divorce the terms in my head, much like watching the film version of a book that I have read. They are..."related" but frustration will result from assuming they will be the same.
I finally had to divorce the terms in my head, much like watching the film version of a book that I have read. They are..."related" but frustration will result from assuming they will be the same.
I'll say, here was one key to my understanding once I saw it. In a purely functional language you want everything to be referentially transparent. That is, an expression should be able to be substituted for its value at any given moment in time. At the very least, you'll want the ability to denote which parts of your program are referentially transparent and which aren't.
This can be expressed as utopian vision: in a purely functional language we'd like everything -- everything -- to be a value.
We now need some way of translating code with side effects into code that returns a value. The logical abstraction is to say that the "value" is not the work itself -- once the work is done it's out of the bag, after all -- but rather the "value" is the machine which can perform that work.
To a mathematician this screams "functor." If you want to move between the two spaces repeatedly this screams "adjoint functors." There's some work you want to do in Space A that's more easily expressed in Space B, so you take a thing in A, transform it to a thing in B, do the work in B, and then transform back to A.
Think in, e.g., Ruby:
sentence.split(' ').map(&:reverse).join(' ')
split(' ') and join(' ') aren't quite inverses of each other, but they are adjoint. These pairs take us from the land of space-separated strings to the land of arrays back to the land of space separated strings.You see that all the time in programming and mathematics. I sometimes call it the "Super Mario Brothers" maneuver when I'm trying to teach it to students. Mario wants to get to the end of the level, so he hits a block, goes up the vine to cloud land, runs through cloud land, and then comes back down.
split(' ') and join(' ') aren't quite inverses of each other, but they are adjoint.
Do you mean you think they are adjoint functors? How are they functors, between which categories?Anyhow, I was playing loose and I know it. If I were writing in a more serious context I'd cross all the t's and dot all the i's. To me the more important thing was this slow gnawing in my gut, seeing hints of natural transformations and adjoint functors here and there and thinking, "Oh come on, there has to be a way to connect these dots."
Non-functional languages like Ruby don't think in this way, so there are hole all over the language.
1. split(" ") is not a functor because (x+y).split(" ") is not equal to x.split(" ") + y.split(" "), for example:
"a BC".split(" ") == ["a", "BC"], but
"a B".split(" ") == ["a B"] and "C".split(" ") == ["C"].
(At least join(" ") is a functor, which here just means monoid homomorphism.) I guess this problem could be fixed by replacing String with StringsThatAreEitherEmptyOrEndInSpace (you need the empty string to be the identity for composition).2. They are not adjoint, even after fixing the monoids to make them homomorphisms! It's easy to easy that if f and g are adjoint monoid homomorphisms, with unit η and counit ε, then the functions m ⟼ ε f(m) and n ⟼ g(n) η are inverses of each other. This does not happen here since, for example, join(' ') is not injective.
Can you fix the example, maybe by regarding them as categories in some other way? My gut feeling is that this won't work, but I haven't thought about it very much.
If, as I suspect, this adjointness is wrong and not easily fixable, does it still have value as an explanation? I think that's a personality question, I find it more confusing than enlightening, but can imagine some people would find it helpful.
But yeah, it's not the greatest example. I don't mean to defend it as something rigorous.