Hacker News new | ask | show | jobs
by united893 2367 days ago
Not sure I ever understood the haskell folks drive. Is it mathematical poetry? An extreme attempt to simplify? Is this an intellectual equivalent of trying to write poetry?

The words catamorphism, semigroup and monoid barely make sense to most programmers.

7 comments

As a former Scala dev who dipped their toes a bit too far into Cats/Matryoshka, it came from a combination of DRY + powerful abstractions. If you have powerful abstractions then it's possible to see commonalities between parts of the codebase that others can't see. "Oh that thing that we're doing over here with lists is the same as that part with futures." That's where DRY comes in and turns that observation of duplicity into unification. Less code equals less bugs, right?

Another angle to view it is what if I told you that you could eliminate an entire class of errors by employing this abstraction? Learn Option once and then never have to deal with NPE's. What about errors due to recursion? Would it be appealing to avoid those errors forever? Concurrency, state, error handling, all things that have appealing abstractions.

If the idea of mathematical connections to seemingly unrelated things appeals to you, or you are tired of dealing with the same bugs in different forms then these abstractions seem very appealing. The problem is that it's a technical solution to a human problem. That doesn't inherently make it bad, but it does imply a different set of tradeoffs.

I think there's a bit of a leap between "Option types are useful" and "using terminology to describe simple and common patterns of computation", though.

I think it's not too far from practicality to describe the solution in OP as along the lines of "solving fizzbuzz by combining infinite streams". It's a quirky/cute solution.

I think there's a jump from "knowing these functions / structures can be useful" and "let's use terms like 'semigroup resonance' and 'catamorphism'".

The concepts really aren't that easy (though they are indeed simple). While I disliked the names too at the beginning, I realize now that they are necessary, because it's important to be precise with your words and the concepts are so general that there simply isn't a better word for it. You can try to call a functor a container since both `List a` and `Maybe a` are functors and contain an `a`. But what about `Int -> a`? It is also a functor and doesn't "contain" anything. Perhaps "producer" would be a better word? `data Phantom a = Phantom` doesn't "produce" anything and is still a functor. Eventually you may arrive at a name like "mappable" which is convention just like "functor" and arguably worse because "functor" is more precise and was there first.

Names like StateT and Reader can surely be improved but the names taken directly from mathematics are already the best we can do imo.

I don't know. `Int -> a` is definitely a container in my mind. Imagine a list. What does it contain? Well, it contains the contents of the list, of course! What is it? We don't know until we look inside. What does `Int -> a` contain? It contains an `a`. Which `a` does it contain? We don't know until we look inside (by passing it an `Int`). The only real difference is that in a list I put the value in first and look at it later. With a function, I look at the value first and put the value in later ;-) I'm being silly, but I really do find that treating it as a container makes it much, much easier for me to reason about it.

However, I completely agree that the word "functor" is useful for exactly the reason you imply. It's a kind of special container. It doesn't work exactly the way you imagine containers should work initially. So I guess it's 6 of 1 half a dozen of the other. I just wanted to speak out because I know there are others like me that find reasoning about functors as if they are containers very useful.

When I'm explaining these concepts, I find myself apologizing for the names, usually by stealing the quip "the math people got here first". Programmers usually take commonplace words and tack on an extra technical definition: list, string, class, object. Semigroup and catamorphism stand out as non-ideomatic names. If they had been called combiner and fold they might have seen more widespread use.
> The words catamorphism, semigroup and monoid barely make sense to most programmers.

Over the last few years I have absorbed a lot of these terms via professional Haskell development and have come to the conclusion that it is quite unfortunate that most of us tend to shy away from this unfamiliar vocabulary. It turns out these are very precise (mathematical) terms which describe common things we see every day in programming.

Addition, multiplication, and string concatenation are all monoids (described as: an associative binary operation with an id element). This might not seem useful to know on the surface, however, once you discover that you can write generic functions over any monoid you can start eliminating large classes of errors that tend to turn up in software development due to the DRY principle.

> An extreme attempt to simplify?

It turns out that the more generic you make something, the harder it is to do the wrong operations on it.

    id :: a -> a
If 'a' is a generic type value for all possible types, is it possible to return anything other than the initial value passed in?
Its about learning-fatigue due to needing to function in real life, especially since the learning curve is steep.

A family man might read a book about FP patterns to solve common issues, agile, QS+testing, concurrency, etc (actually seen tgese read by 50yo collegues) - but not a single one has a category theory book on their table.

Its a question of time invested vs benefits to current occupation.

FP terminology is the domain of the young and the driven, who also are of academic age. (Probably winy find any junior-high kids dabbling with semigroups anytime soon)

For what it's worth I'm a 40yo family man who has been in the industry for 20 years :)
It's a desire to be lazy ;)

The gist is - by having many simple-but-powerful tools along with many simple-but-powerful ways of composing such tools, it makes programming real problems way less intense. The cost of course is the learning curve. So I think of it has shifting variable cost of development to fixed costs upfront.

The end result in my experience is being able to handle more complexity than otherwise (good for side-projects.) Or to handle the same complexity with less time and effort (good for FT engineering...to make room for said side-projects.)

(Those terms actually have very general and simple definitions that I've seen interns learn in one teaching session. But they are infinitely useful problem solving techniques that I use all the time.)

I'd rather describe it as mathematical code golfing.
I dont think this answer should be downvoted.

The linguistics issue here is real and should be addressed or we will end up into two camps, and one will eventually give way to the other.

And math-hardcorers be warned, Life prefers the simple.

Depends on what you're doing. Scott Wlaschin did the best modern take on that here: https://fsharpforfunandprofit.com/posts/13-ways-of-looking-a.... It shows incrementally going from a purely (haha) procedural implementation all the way up to brain-exploding free monads, all doing essentially the same thing: a basic implementation of the old turtle game.

My take is you should know all these, and choose the appropriate level for what you're doing. You don't need to crank it up to 13 everywhere just because it's there. I spend most of my time (web apis, business logic) around level 7. If you dial the abstraction level too far up, not only does the code seem confusing, but it's also easy to paint yourself into an abstraction corner, where a new requirement comes in that breaks the abstraction, so you end up either tearing it down and rebuilding it completely, or building up a terrible "spaghetti abstraction" which is the worst kind of spaghetti. It's a bit ironic to think that abstractions are exactly what's supposed to prevent you from being painted into a corner, but it can happen easily. (https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstracti... is a good reference).

When writing things like compilers, the abstraction level can comfortably go higher. In fact it should. It makes challenging logic far easier to reason about and more consistent to specify and implement when you can think about it mathematically. Other things like rules engines and such can live comfortably in between the two. Conversely when writing timing-critical device code then the appropriate level of abstraction is much lower. A good reference for this style is the k8s PV code. Note the comments starting on line 55 (tldr: DO NOT SIMPLIFY THIS CODE) https://github.com/kubernetes/kubernetes/blob/ec2e767e593953...

So as developers we have to determine what the appropriate level of abstraction should be for our use cases. In fact I'd offer that's one of the most important parts of software design. But it's important to know all of them so we can choose accordingly.

keep in mind that that last statement is relative to your education, etc.

The haskell model is much more intuitive and productive for me than the weird oo-esque models of “usual” languages like python, c++, and java.

May i ask what is your background?
I studied math for a couple years in university and have been a professional python programmer for a while.

I’m familiar with the theory behind popular styles like OO and have used them professionally but ultimately I find Haskells incredibly simple and constrained type system (closed data types, no subtyping, essentially total inference, purity, etc) easier to think in than the Wild West of python or java, say, since there are fewer modeling choices to make.

I did haskell for a while and mathematical poetry is actually a very fitting term.