But I've found some of the more common symbols end up being how you think about your code when you become more versed.
The big one is <$> for fmap. If you have some functor/monad/thingy, you'll often be like "I have this pure function that I want to apply to the inner value of my functor", and <$> is the perfect analog to $.
The high abstraction level of Haskell makes reaching for these really tempting and easy. And the alternatives are verbose and harder to read in many situations.
compare "capitalise <$> readStrLn" to
do val <- readStrLn
return capitalise val
(though "fmap capitalize readStrLn" isn't too bad)> It seems like my progression as a Haskeller results in forcing myself to write in a harder-to-parse style to make my code shorter, to satisfy some base need for "better" code, even though by most measurements I just made, the longer/explicit/pattern matching code is in fact better.
The "do" example is not only more verbose, but less general (it only applies to monads rather than all functors), and relies on special syntax (despite its awkwardness, "<$>" is still just a function call).
You could also do "capitalise `fmap` readStrLn", which is kind of intermediate between the two :)
[1] https://downloads.haskell.org/~ghc/master/users-guide/glasgo...
I agree that the abundant use of operators can make things difficult in some cases, but this is largely naivety on the part of the reader rather than complexity like Perl.
Perl's obfuscated one-liners are just vanilla things that every perl programmer will understand, while Haskell's weird lines are generally package specific operators that one needs to become familiar with.
Once familiar (unlike in Perl) a seasoned Haskeller will be able to grok a lot of what is going on very quickly.
As a further point, the strictness of it all makes understanding the entire system a much easier experience than many other languages.
It's very common to see newcomers or people who've never used Haskell at all mention <$> and friends in this regard. Then someone will say that if you just used do notation explicitly it'd be a lot clearer. And then someone will point out that do notation might look familiar, but semantically it can be spectacularly different from what one might expect from truly imperative languages (e.g., when using the nondeterminism of the list monad). So by encouraging overuse of do notation, you're making the code feel familiar, but you're not actually making it more understandable; if anything, you're making it harder to understand, because you're making it look like something that it's not.
Additionally, while do notation is very generic and broadly powerful, <$> does exactly one thing: it applies a function to a wrapped value, producing a new wrapped value. Once you've used Haskell a fair bit, you very strongly internalize this and tend to immediately understand code involving <$>. If all this code were expanded out to use do notation, you'd have to spend a lot of extra timing reading through the do notation to realize that, oh, this is just a re-implementation of fmap, over and over again.
How exactly is it a limitation that the language has greater expressive power than most?
Haskell does have serious limitations: laziness is a horrible default, modularity is a joke, reasoning about performance is very difficult, and the correctness of most basic abstractions is conditional on the user never ever using a partial function, not even accidentally - or else hilarity ensues.
But functors, applicatives and monads are godsend.
That is definitely a fair analysis. Though I suppose that is a limitation it is not necessarily a severe one, or one that should matter to most people putting code in production.
Haskell in general is extremely unfriendly to novices; but what you lose in accessibility you gain ten fold in maintainability.
Haskell in general requires far, far more upfront problem solving than something like PHP or Javascript. It's easy to take for granted the implicit coercion, smart defaults, overlapping scopes when working in PHP to iteratively work on a problem.
In haskell you are more likely to need a pretty good idea of what the final solution will look like before you even get the code compiling the first time, unlike php/js where you can write one or two lines, check the output, rinse and repeat.
If you can't understand German, would you say that usage of German increases one's cognitive load? No, that would be preposterous. If you need to speak in German, then you hire Germans or people willing to learn the language. Onboarding might be a challenge, but it wouldn't have anything to do with the performance of those that do know German.
I myself speak English as a second language, because I had to learn it in order to do my job. Seeing you're from NYC, there's a high probability that English was imposed on you from birth. Good for you given our profession, but a lingua franca is context sensitive, temporary and English isn't even the second most spoken language.
And do you know what language is more universal than all natural languages and that doesn't change much? Math. And math uses symbols, not English words. And Haskell is much closer to math than all mainstream languages.
experienced devs may have some talent reading more complicated expressions. It doesn't remove the fact that the expression requires many mental steps to visualize, and that for each of these steps, even an experienced dev may slip.
An example of this is function assembly. When you combine elusive function naming (because we like math, right ?) with assembling of a dozen functions to define a new function, you definitely have a readability problem.
Naming variables is a decision (I am looking at you, function arguments in most mainstream languages). Naming variables once and for all is a big and heavy decision (and now it is time for class members to be looked at).
For one operator in Haskell I often have to invent names for three-to-five variables in C#.
Sure, sometimes an intermediate name helps—that's when I reach for `where` or `let`. But much of the time the extra name would just be extra noise that obscures what the code is doing and makes it harder to read at a glance.