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 :).
Understanding the map signature in Haskell is more difficult than any C construct. Now do IO monad.
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.
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 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.
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.
map :: (a -> b) -> [a] -> [b]
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.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.
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."
People usually don't talk that way.
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.)”.
Anyway: He's telling you to do something, and you do it. It doesn't get more imperative than that.
So why hasn't it happened? Some possibilities:
1. People are just ignorant/unenlightened.
2. Haskell is too hard to use for most people. I think that different programmers think in different ways, and therefore find different languages to be "natural". To those whom Haskell fits, it really fits, and they have a hard time understanding why it isn't that way for everyone, so they wind up at 1. But for those who it doesn't fit, it's this brick wall that never makes sense. (Yes, this is about the same as 1, just seen from the other side. It says the problem is the language, not the people - the language really doesn't fit most people very well, and we can change languages easier than we can change people.)
3. Haskell isn't a good fit for many kinds of programming. The kind of programs where it fits, it's like a superpower. The kinds where it doesn't, though, it's like picking your nose with boxing gloves on. (Shout out to Michael Pavlinch, from whom I stole that phrase.)
What kinds of programs fit? "If you can think of your program like a pipe" is the best explanation I've seen - if data flows in, gets transformed, flows out. What kind of program doesn't fit? One with lots of persistent mutable state. Especially, one where the persistent mutable state is due to the problem, not just to the implementation.
Myself, I lean toward a combination of 2 and 3.
4. History. In those types of discussions, there are always "rational" arguments presented, but this one is missing.
> One with lots of persistent mutable state.
You mean like a database? I don't see a problem here. In fact, there is a group of programs large enough, that Haskell fits nicely, that it cannot be 3; REST/HTTP APIs. This is pretty much your data goes in, data goes out.
> You mean like a database?
No, I mean like a routing switcher for a TV station. You have a set of inputs and a set of outputs, and you have various sources of control, and you have commands to switch outputs to different inputs. And when one source of control makes a change, you have to update all the other sources of control about the change, so that they have a current view of the world. The state of the current connections is the fundamental thing in the program - more even than controlling the hardware is.
But in your example, PHP is also a bad choice, and alas, it dwarfs Haskell in popularity. I can't really think of where PHP is a great fit, but Haskell isn't.
Later, Python took its place, since people realized the Java ecosystem was way too complicated and was turning off would-be CS students. Python directly targeted the academic use case by having similarities to C, Java, and Bash——it was not a better language, it just made existing imperative and object-oriented assignments easier for classroom environments. Believe it or not, a lot of programmers and even academics sort of give up on exploring significantly unfamiliar directions after graduating.
[1] https://www.cs.utexas.edu/~EWD/OtherDocs/To%20the%20Budget%2...
Imperative languages do offer many advantages over Haskell, in that most coursework and industry jobs use them and that, consequently, their ecosystems are much further developed. These advantages are a consequence of university programs' alignment with the imperative and object-oriented programming paradigms, to Oracle's benefit.
Your colleagues having never looked back at lisp is hardly evidence that Haskell would have been too difficult for them or that Oracle didn’t have a hand in this.
Functional programming just seems harder for people to get into. Perhaps it's bad for everyone that people don't make that effort, but it doesn't seem like a conspiracy
The promotional campaign that Dijkstra mentions was perhaps orchestrated by Sun Microsystems, though perhaps not since Oracle was indirectly strategically aligned with Java, as the eventual acquisition shows.
Yes, it is more difficult to get into FP. However, asking the question why it became more difficult, when historically the opposite was true, is certainly worthwhile. Surely there was some cause.
He's asking for something that is really inappropriate. He lost in the CS department, and he wants the Budget Council to decide on what languages should be taught? Like they know anything about it!
He lost a political battle, and he's appealing it to the only place he can, and he's buttering them up to do it, but the people that actually know something about the topic decided against him already.
And, you're quoting only one side of the battle. One vocal and eloquent side, but only one side. Maybe look into why the UT CS department made that change? (And not why Dijkstra says they did.)
Political campaigns are often bought.
He mentions the expensive promotional campaign that was paid towards Java.
> the people that actually know something about the topic decided against him already
Matters of pedagogy often involve value judgements and trade-offs, with knowledge alone being unable to provide definitive answers.
However, Sun/Oracle did know that more money would flow their way if undergraduates were to learn Java. The letter suggests that one or both of them decided to act accordingly.
It’s questionable to assert that every knowledgeable faculty member thought that pivoting to Java was the best option. (Dijkstra himself is a counter-example?) From the looks of it, just a single department chair——not the full CS department——had the decision-making authority on this curriculum change.
> inappropriate
Would inappropriateness make his arguments any less true?
Why in an academic context would it be inappropriate to request input from additional stakeholders? If the letter was unconventional, remember that a purpose of the tenure system is to protect unconventionality.
I mean, look, if you had a letter signed by the majority of the department, complaining about the chair's decision, then the Budget Council might consider reversing the chair, on the authority of the expertise of the majority of the department. But overrule the chair on the basis of disagreement by one professor? No way. You can't run a university that way, because there's always at least one professor who disagrees with a decision.
Admittedly, I don’t know anything about who was on the budget committee to which Dijkstra wrote this letter. But it is just ordinary for academics to write proposals outlining their priorities in hopes that the grant/budget/financing committee will bite.
I think you probably mean Sun? Or maybe that Oracle continued what Sun started.
before the first class I get an email from one student asking if they must purchase the book for the class since it $275 (this is years ago) and I was taken aback, what kind of book costs $275 - even for a college textbook that was nuts. I told him to not purchase it until we meet for the first class. I go to the office and see my copy of the book, it is programming the web with oracle forms from oracle press!!!! I talked to Dean and he was like “yea, that is what we need to teach!” needless to say that, none of the kids bought the book, and I did NOT teach oracle forms, and I was never given that class again :)
Sun and Oracle were strategically aligned at the time, so it could have been either or both.
Off the top of my head, memory safety challenges for junior Haskellers (laziness footguns), State monad being fundamentally flawed: there is an inability to get at and log your application state just before a crash, bloated tooling, GHC frequently breaking existing code. Laziness and monadic code makes debugging painfully difficult.
I like Haskell for Advent of Code only.
Could you explain a bit about what you mean here? In what way is it fundamentally flawed?
I'm not saying you're wrong; I just want to understand.
This explains in more detail:
https://github.com/haskell-effectful/effectful/blob/master/t...
I agree loosely with what haskman above says about creating relatively bug free applications, the guard rails are so robust. But those same guard rails mean you can paint yourself into a corner that it is harder to get out of without imperative state, case in point above.
- Space leaks due to laziness are a solved problem. I explain the technique to solve it at: https://h2.jaguarpaw.co.uk/posts/make-invalid-laziness-unrep... This technique has not completely percolated throughout the community, but I am confident that it does actually resolve the "laziness causes space leaks issue"
- Flawed state monad: well, you point out the analysis of its flaws from the effectful documentation. That's correct. The solution is: just use effectful (or another similar effect system. I recommend my own: Bluefin)
- GHC breakage: I've been keeping an inventory of breakage caused by new GHC versions, since GHC 9.8: https://github.com/tomjaguarpaw/tilapia/ There has been very little! The Haskell Foundation Stability Working Group has had a massive effect in removing breakage from the ecosystem.
- Laziness and monadic code makes debugging painfully difficult: I mean, sort of, but if you're using monadic code in the style of a decent effect system like effectful or Bluefin this is a non-problem. It's hardly different from programming in, say, Python from the point of view of introducing debugging printfs or logging statements.
>well, you point out the analysis of its flaws from the effectful documentation. That's correct.
Thinking deeper about this, that there is essentially no way to fix this issue with StateT, because of the type choice, the monad, the composability requirement, all conspiring together to not be undone, does that signal something deeper that is wrong with the flexibility of Haskell, that we can progressively paint ourselves into a corner like this. Could it not happen again, but with another late breaking requirement, for effectful, or bluefin?
Ah, that's great to know, thanks. It's rarely clear to me whether people read or are interested in what I say!
Well, yes, in principle even a design that is perfect according to some spec could be completely wrong if the spec needs to change. and impossible to tweak to match the new spec. This is true of any language or any system. This raises a few important questions:
1. How easy does a language make it to "unpaint" yourself from a corner?
In Haskell it's easier than in any other language I've experienced, due to its legendary refactoring experience. For example, if you "incorrectly" used the State monad and got stuck, you can wrap it up in an abstract type, change all the use sites, check that it still compiles and passes the tests, then change the definition to use new "uncornered" implementation, again check it compiles and passes the tests, then unwrap the abstract type (if you like, this stage is probably less important), then add the new feature supported by the new implementation.
2. How likely is it to paint yourself into a corner in the first place?
In Haskell, again, less likely than any other language I've experienced, because the constructs are so general. There is far more opportunity to tweak a design when you have general constructs to work with. (That said, I've met many Haskell behemoths that couldn't be easily tweaked so, particularly contorted type class hierarchies. I recommend not designing those.)
3. Why won't effectful or Bluefin lead to "corners"?
Because they're just Haskell's IO, wrapped up in a type system that gives fine-grained control over effect tracking. Anything you can do in Bluefin and effectful you can do in IO, and vice versa. So to really paint yourself into a corner with IO-based effect systems it would have to be something that you can't do in IO either, and at that point we're talking about something that can't be done in Haskell at all. So there's no real downside to using IO-based effect systems in that regard.
1. Tooling has historically been a mess, though it's rapidly getting better.
2. Error messages are opaque. They make sense to someone familiar with Haskell, but others cannot make the leap from an error message to the fix easily.
3. It's a jack of all trades. The resulting binaries are not small. Performance can be very good but can be unpredictable. It doesn't compile nicely to the web. Doesn't embed well. There is basically no compelling reason to get into it.
4. The ecosystem is aging. You can find a library for almost any obscure usecase, but it would be many years old, and possibly require tweaking before it even compiles.
Haskell gives you a wide range of tools, from simulated state like the State monad, to real ones like the ST monad and IORef inside the IO monad. For synchronization between threads you have atomic IORef, MVar, and TVar.
If you problem requires you to have persistent mutable state, Haskell helps you manage it so that you can truly separate those persistent mutable state that's due to the problem from those that's due to the implementation.
Maybe now that I'm older and wiser (debatable) a revisit is in order, but lately I prefer dynamically typed languages (like Clojure and Elixir) to statically typed ones. I'll probably add it to my TODO list, but the list is long and time is short.
I don't have a math background. I failed high school maths.
I've been working as a professional Haskell programmer for several years.
What you are saying is just demonstrably not true. So why are you saying it?
I would imagine that probably the majority of Haskell devs would understand it, which is all that matters.
99% of the population doesn't understand anything in Perl, either. That's not a mark against Perl.