I always maintain that this is just familiarity, Haskell is in truth quite a simple language. It's just that the way it works isn't similar to the languages most people have started with.
I believe there's a strange boundary around the idea of simple vs easy (to quote rich hickey) and I don't know how to call it.. (or if somebody named it before)
functional and logical languages are indeed very simple, small core, very general laws.. (logic, recursion, some types) but grokking this requires unplugging from a certain kind of reality.
Most people live in the land of tools, syntax and features .. they look paradoxically both simpler than sml/haskell so people are seduced by them, yet more complex at the same time (class systems are often large and full of exceptions) but that also makes it like they're learning something advanced, (and familiar, unlike greek single variables and categ-oids :).
People intuitively expect things to happen imperatively (and eagerly). Imperativeness is deeply ingrained in our daily experience, due to how we interact with the world. While gaining familiarity helps, I’m not convinced that having imperative code as the non-default case that needs to be marked specially in the code and necessitates higher-order types is good ergonomics for a general-purpose programming language.
> People intuitively expect things to happen imperatively (and eagerly).
Eagerly? Yes. Imperatively? Not as much as SW devs tend to think.
When the teacher tells you to sort the papers alphabetically, he's communicating functionally, not imperatively.
When the teacher tells you to separate the list of papers by section, he's communicating functionally, not imperatively.
When he tells you to sum up the scores on all the exams, and partition by thresholds (90% and above is an A, 80% above and above is a B, etc), he's communicating functionally, not imperatively.
No one expects to be told to do it in a "for loop" style:
"Take a paper, add up the scores, and if it is more than 90%, put it in this pile. If it is between 80-90%, put it in this pile, ... Then go and do the same to the next paper."
Nope. The fact that he's telling you a high-level command is irrelevant. (If you didn't know what “sort the papers” means, he'd have to tell you in more detail; it's just the difference between calling your built-in sort routine or coding it.)
Anyway: He's telling you to do something, and you do it. It doesn't get more imperative than that.
You’re talking about what vs. how, but imperative vs. pure-functional is both about the how, not the what.
When you’re explaining someone how to sort physical objects, they will think in terms of “okay I’ll do x [a physical mutable state change] and then I’ll have achieved physical state y, and then I’ll do z (etc.)”.
> Understanding the map signature in Haskell is more difficult than any C construct.
This is obviously false. The map type signature is significantly easier to understand than pointers, referencing and dereferencing.
I am an educator in computer science - the former takes about 30-60 seconds to grok (even in Haskell, though it translates to most languages, and even the fully generalised fmap), but it is a rare student that fully understands the latter within a full term of teaching.
Are the students who failed the pointer class the same ones in the fmap class?
I didn’t say “using map” I said understanding the type signature. For example, after introducing map can you write its type signature? That’s abstract reasoning.
Pointers are a problem in Haskell too. They exist in any random access memory system.
Whether pointers exist is irrelevant. What matters is if they're exposed to the programmer. And even then it mostly only matters if they're mutable or if you have to free them manually.
Sure, IORef is a thing, but it's hardly comparable to the prevalence of pointers in C. I use pointers constantly. I don't think I've ever used an IORef.
If you have an array and an index, you have all the complexity of pointers. The only difference is that Haskell will bounds check every array access, which is also a debug option for pointer deref.
That’s an unfair comparison because these are two unrelated concepts. In many languages, pointers are abstracted away anyway. Something more analogous would be map vs a range loop.
And I'd say the average React or Java developer these days understands both pretty well. It's the default way to render a list of things in React. Java streams are also adopted quite well in my experience.
I wouldn't say one is more difficult than the other.
IMO `map` is a really bad example for the point that OP is trying to make, since it's almost everywhere these days.
FlatMap might be a better example, but people call `.then` on Promises all the time.
I think it might just be familiarity at this point. Generally, programming has sort of become more `small f` functional. I'd call purely functional languages like Haskell Capital F Functional, which are still quite obscure.
I suppose an absolute beginner would need someone to explain that Haskell type signatures can be read by slicing at any of the top level arrows, so that becomes either:
> Given a function from `a` to `b`, return a function from a `list of as` to a `list of bs`.
or:
> Given a function from `a` to `b` and a `list of as`, return a `list of bs`.
I find the first to be the more intuitive one: it turns a normal function into a function that acts on lists.
Anecdotally, I've actually found `map` to be one of the most intuitive concepts in all of programming. It was only weird until I'd played around with it for about 10m, and since then I've yet to be surprised by it's behavior in any circumstance. (Although I suppose I haven't tried using it over tricky stuff like `Set`.)
`fmap` is admittedly a bit worse...
fmap :: Functor f => (a -> b) -> f a -> f b
But having learned about `map` above, the two look awfully similar. Sure enough the same two definitions above still work fine if you replace `list` with this new weird `Functor` thing. Then you look up `Functor` you learn that it's just "a thing that you can map over" and the magic is mostly gone. Then you go to actually use the thing and find that in Haskell pretty much everything is a `Functor` that you can `fmap` over and it starts feeling magical again.
You and I have a math part of our brain that appreciate the elegance from the algebraic structure.
I’m saying that thing you did where you start representing concepts by letters which can be populated by concrete objects is not a skill most people have.
Maybe at its core, but Haskell in the wild is monstrously complex because of all the language extensions. Many different people use different sets of extensions so you have to learn them to understand what’s going on!
Not really, the vast majority of extensions just relax unnecessary restrictions. And these days it's easy to just enable GHC2021 or GHC2024 and be happy.
functional and logical languages are indeed very simple, small core, very general laws.. (logic, recursion, some types) but grokking this requires unplugging from a certain kind of reality.
Most people live in the land of tools, syntax and features .. they look paradoxically both simpler than sml/haskell so people are seduced by them, yet more complex at the same time (class systems are often large and full of exceptions) but that also makes it like they're learning something advanced, (and familiar, unlike greek single variables and categ-oids :).