Learning Haskell is no harder than learning any other programming language
williamyaoh.com
williamyaoh.com
But, even if you finish the Haskell Book(http://haskellbook.com), which is like 1300 pages, you're still going to be unable to contribute to a serious code base. Anyone who says otherwise is lying. Now, you have to understand at least 20 language extensions which you find randomly at the top of files {-# LANGUAGE ExtensionHere #-}. Now you have to understand how to really structure a program as either a stack of monad transformers, or free monads or anything else. Then you get into concurrency and to do that you have to understand how Haskell actually works, what non-strict computation does etc. etc. Otherwise you're going to get some nasty behaviour.
You think I'm done? Let's get to Lens. You can use Lens after a relatively short time of reading the docs. But to understand Lens? Very few people actually understand Lens.
Don't get me wrong, Haskell has spoiled me, and I don't really want to touch any other language (I still like Clojure, Rust, Python, Erlang). Once you get past that the language is a joy to use.
This is abstraction. If there's one thing Haskell does well it's abstraction.
EDIT: It's really bizarre. We see these same responses to all the Haskell-or-Idris-or-whatever threads -- I wonder if there's some imposter syndrome going where "I can't immediately read/write Haskell" somehow morphs into "Haskell is useless". IME it's really rare for people who actually program in Haskell to have serious issues with the language. Yes there are issues from a smaller ecosystem, package management was bad (Stack fixed that), etc. etc. but there are very few fundamental problems with the language. Something so small as just Pattern Matching is a huge increase in productivity. Thankfully, quite a few languages have adopted pattern matching these days (Scala, Rust, TS, maybe even C++23?).
(The really big payoff comes from granular effects, but I'm sure the rest of the world will realize in about 20-30 years' time. The Erlang people already have, albeit in a different way.)
There's no difference in practice either for a sufficiently small dataset.
> in practice there is a right way and a wrong way
Sure, but that's true of all technologies.
Yes, Haskell can't help you escape the limitations of our world — or indeed our hardware — but it doesn't pretend to either.
As a non-Haskell user, just for reference what's "sufficiently small"?
If you were building a website for your local Italian restaurant, what would your needs be? Do you need an ElasticSearch cluster to handle customer menu item queries? Do you need a database at all?
In Haskell's case it's best to avoid lists entirely, as they're _usually_ not the optimal data structure. But best for whom? Does the beginner care that a set operation would be more effective than a list operation for their given use-case?
In my day job, I frequently generate records returned from a database along with local changes to be posted later, and say compute the sum of one of the columns. That sounded like something I'd use folding for, with my limited knowledge, so I was just curious at which point (order of magnitude) I'd have to worry about doing it this way or that.
But if lists are not to be used, what should I use for the above? And will the data structure you propose be fine with either fold?
At this point (taking into account the extent of your knowledge as you yourself described it), my advice would be to just use basic folding and lists. At some point in the future, if you observe some performance issue in exactly that area, you might remember this and think "hmm, memory consumption is bit excessive here; maybe I'll try a foldr instead", or "finding the intersection of these two big lists is a little slow; maybe I'll convert them both to sets instead?"
So what happens in practice with unfortunately large datasets?
>Yes, Haskell can't help you escape the limitations of our world — or indeed our hardware — but it doesn't pretend to either
Then what is Haskell's value-proposition when it comes to solving real-world problems?
You take a different approach. Haskell provides plenty of options.
> Then what is Haskell's value-proposition when it comes to solving real-world problems?
There are many. You can consult your favourite search engine to learn more.
So do other languages. Why is Haskell special in this regard?
>There are many. You can consult your favourite search engine to learn more.
None that seem to address the problem of diminishing returns.
I never suggested other programming languages don’t also have value. Again, if you want to educate yourself further on a specific technology’s benefits, I invite you to make use of a search engine instead of sea-lioning on a technical forum.
> None that seem to address the problem of diminishing returns.
That’s your opinion. Nobody is forcing you to like Haskell. You are free to just ignore it.
If I am in the business of system reliability, I will choose the language with shallower rabbit holes.
Abstraction layers are great for builders and terrible for fixers. I am both, so I need to strike a balance.
- Haddock hyperlinked source makes it easy to understand libraries if you need to dig into internals
- ghci makes it easy to interactively learn the language - and a new codebase!
- Haskell can dump the stages of its codegen pipeline (Core, STG, Cmm)
- Profiling and Event logging exist and are easy to use
- You can even write an inspection test to assert certain behavior holds. For instance, that no allocations occur or that some abstraction (e.g. Generic) is truly erased by GHC's optimizations
I cannot imagine anyone using a database effectively on any significant amount of data without understanding indexes, how different joins work, why join order is important, what effect join orders have on performance, etc. Get to a certain scale and it's not enough to know about indexes; you need to understand the structure of b-trees, disk I/O performance, how CPU cache performance affects b-tree navigation even when index is cached in memory, how to use compound indexes effectively to reduce random access through the index, etc.
The constraints of CPU and memory never go away, and if you're trying to scale something, you're going to be limited on either or both of those resources. That in turn forces you to understand execution and memory behaviour of the abstractions you're working with. All abstractions leak when pushed.
You'd be surprised just what proportion of systems running in the market operate on amounts of data you would not deem "significant".
And as I said in another comment, Haskell doesn't try to pretend that computations run with no hardware constraints.
> All abstractions leak when pushed
Yeah. But we might have wildly different ideas for where that boundary is.
But conversely you probably didn't have to understand what filesystem the database runs on, whether it is in a RAID array, whether the network connection was over Ethernet or T1, etc.
All abstractions leak. The question is how leaky they are. In my experience Haskell abstractions are much less leaky than most.
Why? You seem unsatisfied with any of the previous layers of abstraction, so where does it end? When can I truly call myself a user of a database?
When you treat it as a black-to-grey box, not a grey-to-white box.
Laziness is a definitely a double-edged sword, with sharp edges.
It's definitely a wart of laziness, but it's also pretty easy to avoid.
As far as abtraction goes, laziness doesn't leak. In fact, it's a big reason Haskell performance composes. The head . sort example is contrived, but it holds true in more complicated & useful examples as well!
I have to understand all the intricacies of the system, so people who choose to treat databases as black boxes don't have to. There is only so far this abstraction holds.
Stateless (immutable) code scales horizontally. ACID[1] doesn't.
Turns out NTFS has a maximum number of file fragments a file can have, and if it exceeds that it will refuse to grow the file (for obvious reasons).
Hardly everyday stuff though.
There is also other aspect of this, even if I do not understand joins or b-trees I can measure performance so I can figure out which combintion of joins are the faster. The reason that I prefer performance testing over getting to know the theorethical background for certain things is because in many cases your performance also depend on the implementation details, not only on which tree data structure backs your implementation.
Implementation details follow the contours of the fundamental algorithms and data structures. You understand the outline of the implementation details - the bones, if you will - from that. And you can dive into the source (or disassembler - I've done that on Windows) to fine tune knowledge of other specifics, with e.g. gdb stack traces on the database engine when it is abnormally slow to focus attention.
Without having gone to battle together over some performance problems, or me writing significantly longer battle-story posts, we're probably not going to be able to communicate effectively here.
How does this work in the context of CSS? Do people making websites need to understand how WebKit paints the screen?
The word “effectively” seems rather arbitrary here too.
For example I have seen many having a hard time understanding why it is trivially easy to align an element to the top of the screen but tricky to align something to the bottom of the screen - something which would be symmetric and equally simple in a typical application GUI framework.
But understanding how layouts are generated makes this clear.
[0] https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
That's a lie. The basics of optics can be taught to even new Haskell programmers in an hour or so. Don't start in the deep end with generic optics with scary signatures like (Profunctor p, Functor f) => p a (f b) -> p s (f t). Start with something concrete like (String -> IO String) -> (User -> IO User) and then introduce the type variables one at a time. I've taught the basics of lenses, traversais, folds and other useful optics many times.
I mean, the rule of three is just insane. How is anyone supposed to anticipate that behavior?
Those Haskell threads are full of exaggerations. I don't know/use a lot of Haskell concepts and I can still produce software with it. You can be just fine with IO and passing everything as arguments. Which is, well, what article is talking about.
I think the major troubles for beginners with C++ is that you can't do anything out of the box like work with files, create a directory or perform a HTTP request. Whereas python or java are ready to use.
The closest thing i can think of in Python is passing a mutable object (like an empty list) as a default param value. C++ is littered with bug-prone landmines like that.
Every time someone has suggested I learn Haskell the discussion goes similar to suggesting I learn German. Sure German from a language perspective has some advantages over English in some situations. Some even argue that it's an objectively better language. But I live in Pennsylvania and speak English as a first language. Outside of moving to Germany/Switzerland/Austria, how do the advantages of German provide enough benefit for me to invest the massive amounts of time to become fluent?
Sure if we could turn back the clock on Computer Science education and have everyone learn lisp as their first language maybe we'd all be avid Haskellers these days and be better off for it. But given how history went it is "harder" to learn and less productive to work in due to external factors alone, regardless of how intrinsically easy/hard the language may be (which is entirely subjective).
I'd go as far as saying it's essential for anyone who likes programming beyond just a profession. Same with lisp.
You don't need to learn all of the language extensions or how to architect serious applications with free as the OP said but it's very useful knowledge and one of the pedestals from which all other languages should be judged.
Plus you'll understand why Idris and dependent types are an interesting future development in safety and language design. While also understanding the source and inspiration of many features in far more popular languages like JS and Rust. And there may be a real future in it via PureScript and other similar projects.
In C++, you can focus on subsets of the language that eschew whole huge paradigms (like templating or inheritance) and you’re roughly no worse off in terms of the scope of practical programs you can write with basic software patterns.
You can’t do this in Haskell. You really do have to learn all the complex hierarchy of paradigm-committing patterns and use nearly all of them nearly all the time, making it much harder to learn than C++ even though C++ is complicated.
Languanges can be complicated in different ways, and Haskell is unique in that you have to engage with every complicated aspect of it nearly all the time.
Err, no you don't. The only slightly unusual concepts Haskell 98 has (from a functional programming point of view) are monads, type classes and laziness. Haskell 98 is a perfectly decent language to write computer programs in. In fact it's even perfectly decent if you largely avoid monads and type classes!
The "you can focus on subsets of the language" claim is no weaker for Haskell than it is for C++.
Exactly. The way they manifest in Haskell requires huge time investment before you can write basic programs. For example, how do you write dynamic dispatch in Haskell 98?
If you give an answer that either (a) involves exotic use of type classes or (b) say “don’t desire dynamic dispatch in a functional paradigm and instead restructure the whole program to avoid needing it” then you’ve proven my point.
That fact that you think monads, type classes and laziness, being “three” concepts (except really they unpack into way more top-level concepts, especially the first two), means you have a huge blind spot. Your experience makes you think of them as self-contained things but they aren’t and even just those three things yield huge complexity sprawl in Haskell.
Your claim now seems to be "Haskell contains a great deal of complexity" or "You can't implement dynamic dispatch in Haskell" which are different things entirely.
(FWIW I'm not quite sure what dynamic dispatch is or whether I've ever needed it, in Haskell, C++ or Python)
Suppose you’re using C++ but you don’t want any objects, exceptions or templating. Ok, you’ll be fine. It will be a lot like C, but you’ll be fine. Nothing will be substantially harder to solve, design or implement.
People write professional software in OCaml and Scheme so I hardly believe your suggestion is impossible.
> Suppose you’re using C++ but you don’t want any objects, exceptions or templating. Ok, you’ll be fine. It will be a lot like C, but you’ll be fine.
Sure, and the same applies to Haskell, except with Standard ML instead of C.
I'm not suggesting that one would be particularly productive like that, only that the level of complexity of Haskell is about the same order as the level of complexity of C++, and one can reduce one's subset of Haskell (all the way down to SML if necessary) in the same way that one can reduce one's subset of C++ (all the way down to C if necessary).
> Nothing will be substantially harder to solve, design or implement.
That surely can't be the case. If it were then objects, exceptions and templating would never have been implemented.
Is that what you mean? Or is this really about something else?
But soon after you realize that all you need is any text editor and a terminal (to run ghci(d)).
The analogy with Promises, which by now everybody knows, even if pedantically flawed because they don't fit this or that criterium of Monads, is quite useful to get the idea...
I wouldn't make that assumption. Between people who program in languages that don't offer promises, and people who use promises but still get them wrong, there's not exactly what I'd call a strong base of understanding.
In other words, it's not.
"Category Theory" is for mathematicians and people interested in highly abstract mathematical theory. It doesn't help you write Haskell programs.
"The wise student will focus their attention on definitions and examples, without leaning too heavily on any particular metaphor. Intuition will come, in time, on its own."
typeclassopedia admits that the intuition is missing. I would go on to argue that just going from the definition alone you would think that a functor is anything that is mappable and that fmap simply maps a function across a functor to produce a new functor.
The intuition that cannot be grasped without some category theory is that fmap actually lifts a regular function of standard types into a function between functors. A functor is more than just fmaps.
There is literally not enough information from the definition of the type class Functor and from examples of usages of that definition for a programmer to truly understand the concept of a functor. That is the conclusion I am deriving. It is not wrong. You are wrong. What's going on is you are deriving a conclusion convenient to your view point.
Sure you can get by programming haskell without category theory just like you can program without knowing the notion of an algorithm. However in both cases you are worse off without the knowledge.
Couldn't you also say that about C? If you think K&R C is enough of an introduction, wait until you see the Linux kernel!
Lens is a library, not part of Haskell the language, it's also not particularly widely used. If you are going to conflate the ecosystem with the language, then we could equally talk about the complexity of dependency injection frameworks, AbstractSingletonProxyFactoryBeans, aspect-oriented bytecode weaving, and enterprise Java beans when discussing how much "easier" Java programming is.
I have programmed both Java and Haskell professionally on multiple codebases. I honestly found more ad-hoc and incidental complexity in the Java world. At least the Haskell libraries generally were consistent in the abstractions used. Java the language, also had a lot of hidden complexity, for example its memory model (required knowledge for concurrent programming).
Haskell has a different kind of difficulty. One must expect to feel dumb for a long time, because it's composed of hard concepts. Those are much simpler concepts, but they aren't any quicker to learn.
Edit: oh wow, it actually exists
It's not, you still need to create the one instance when it is first needed. It's possibly excessive abstraction, but it's not an oxymoron.
You have a Factory<T> interface. Depending on T and context, it may be reasonable to provide an implementation of Factory<T> that returns a singleton, something taken from an object pool, or a new instance of an object.
I haven't worked in an IoC-heavy world for awhile, but a SingletonFactory is neither silly nor an anti-pattern.
And, yes, Lens is where I stopped. I grokked the basic mechanism and used the library in some limited cases but a real understanding of the whole zoo of Lens-related types always eluded me. Doubtless I could have figured it out given time, but working on my own toward my own goals it was a real slog. So I started learning Rust instead. :)
Oh, and TH is an absolute nightmare. Just putting that out there. No, I don't know how I'd do it better.
(After all, I've been interested in lens since it first came out seven or so years ago. I'd consider myself an expert in that type of technology and yet there are still parts I don't understand!)
Entertainment is maybe not the right word, though it was definitely entertaining. Learning Haskell was the beginning of a personal renaissance in my approach to programming, and as a self-taught programmer (and professional software engineer looking to expand my horizons) that was a huge deal.
Ah, well that puts quite a different spin on things!
(that very well may be the case)
I think the point is that Haskell is quite powerful and useful without learning the whole of it. Basically, nobody knows the whole of it.
This is a perspective worth considering. The bones of Haskell are great and it's academic origins (god bless them) have it running around in knight's armor and a tutu (or something).
You could most likely use it effectively on a properly advised, disciplined team ("we don't use language extensions").
It might also get dressed up in other clothing and be what we're all using some day. The power and expressivity seem immense.
Imagine, for example, if the Rust documentation team got a hold of it. Holy crap!
How is this different from Ruby with Rails, or Erlang and OTP? Or Python and Tensorsflow?
> Now, you have to understand at least 20 language extensions which you find randomly at the top of files {-# LANGUAGE ExtensionHere #-}.
I absolutely agree it can be super irritating when you find a new extension that radically changes syntax. I have made jokes and complaints you can find in my comment history on this very site about it.
But I don't think this is very different from C# or C++. Folks tend to exclude and create style guides for their language. At least Haskell has the decency to label these features explicitly.
Most of the really exciting libraries of 2018-2019, fused-effects and polysemy come to top of mind but there are many others, don't use terribly exotic extensions. My favorite web framework Spock also doesn't use anything to exotic either (and in fact uses nearly identical extensions to the more popuplar Servant, nearly).
So I think that the community is moving forward with a consensus on what the valuable and expected extensions are. What we could do to improve this is make the consensus more accessible to newcomers both by talking about it (and not in the context of Alexis's great extensions post last year [0] or Chris Martin's suggestsions and awareness-raising efforts (e.g., [1]). Hopefully the community can agree to raft a bunch of uncontroversial extensions together and say, "This is GHC 2020, we just agree this is the default language spec unless you tell ghc otherwise."
> You think I'm done? Let's get to Lens. You can use Lens after a relatively short time of reading the docs. But to understand Lens? Very few people actually understand Lens.
Is this any different from ANY data structure library an the majority of its consumers though? I still run into senior software engineers with amazing history who still don't understand things like, "What is the asymptotic runtime of a modern sorting algorithm" or "what alternatives to cardinality estimation could we use here than the default library bloom filter?" or more frustratingly to me, "Why this HAMT is not in fact constant time access even in practice for your specific use case, yes they're rad all praies Bagwell but it's the wrong structure for this case."
We could write a whole thing about sane uses of lens and ways to resist its excesses. Maybe now that I have finally quit twitter, I will do that this year.
[0]: https://lexi-lambda.github.io/blog/2018/02/10/an-opinionated...
[1]: https://twitter.com/chris__martin/status/1102457521380442112
Most languages are easy to learn, and that's what the article is talking about. Mastery is a whole other topic.
Anyone who has experienced things you didn't should be discredited, because obviously your personal take is the definitive take about it.
I like that with Go or Erlang everything I learned 5 or more years ago has still stuck to me. With D I can be effective quickly. With Rust I struggle a bit. Rust is probably great for building a web browser but doing backend web development feels way more work than Go or even Python (CherryPy). Haskell I dont even remember a darn thing anymore.
The thing about imperative and OO programming is that it’s hard to do well. I honestly havent seen more than 1/10 developers write ”good” OO code even after 10 or 15 years as professionals. Large scale OO is a cognitive load that requires extreme focus, skill and talent. I prefer functional (or OO using an extreme functional discipline like all immutable types etc) because I don’t have that talent, focus and skill.
My point isn’t that Haskell should be the language of choice. I think it’s a great language but I think e.g laziness makes it too hard to reason about performance and behavior. Today I’d recommend F# I think.
I think if I had to learn Haskell first, then I would've just given up at the start.
To my knowledge across 4 years there, nobody ever dropped out because of the Haskell.
People usually struggled with the maths instead.
People also did not have more problems getting Haskell concepts right than linked list modification in Java.
I've met a lot of people (at work and post-grad) who have graduated from colleges without an idea of what a functional language is or the concepts behind them. I don't blame them, but its a pity to find so many workarounds in legacy code which could have benefited from functions as parameters and less state in general.
We had Haskell in college too, almost 30 years ago, and even then it was one of the hardest courses at school. This was before IO monad, just as the language was being created in the early 90s.
I believe (based on exactly zero hard data) that FP fits the way some peoples' minds work, and procedural fits how other (many more) peoples' minds work. The ability to pass a Haskell class is not the same as the ability to program competently in at least one language.
We only get better software if the people who don't pass the class proceed to not write software, as opposed to writing software anyway without the training available in the following courses (and presumably writing worse software) or being replaced in industry by others who could not have passed that filter.
If neither of those happens then we presumably will have better software. We will also have less software. It's not clear whether that trade-off is the right one.
I managed to get through some of them, was weed out from others. That's life.
I don't think getting started in haskell is that hard. If you're just doing theoretical exercises like building data structures and their related operations then it might even be easier than other languages.
Building a bigger application that does things like create a mini game or act as the backend for a web service, you know the cool stuff, becomes a lot harder.
Same. I'm perfectly happy to fall into the pit of success[1].
"You are already smart enough to write Haskell"... if you accept that you are too dumb for most of Haskell and are therefore okay with being cut off from most of its ecosystem.
Digging into a dependency due to a bug or custom feature is something that usually happens on any bigger project of mine. If I have to expect that I won't be able to work in a dependencies codebase because it will most likely contain (multiple) concepts that I won't be able to understand, then that's a big no-no.
I say the same is for haskell.
Of course you can optimally keep out of the dependencies, and a lot of people don't even think about digging into them and unnecessary limit themselves, but as I said in my initial comment, every bigger project I worked on involved tweaking dependencies in one way or another.
Edit: Ok so I tried it. Template Haskell seems to break it, which my projects use heavily. And does not even report syntax errors in some files. Seems like yet another dead end for me as an editor setup, unfortunately.
I know Carmack has presented a case to code in a pure functional style here https://www.gamasutra.com/view/news/169296/Indepth_Functiona... but that somewhat precludes the necessity of switching to another language from C++. Further, he advocates for a new keyword 'pure' to assist the compiler like const does, perhaps not knowing that const doesn't actually help the compiler out in practice.
I would add that the elitist signaling that is rampant in functional programming in general and the Haskell community in particular is also not helpful.
And look, defining a problem functionally works great on 10% of the cases, but it complicates some 40% of other cases (very imprecise numbers).
The world is not functional, as it turns out. And a lot of those cases where you can write functionally don't gain much from a performance or correctness perspective compared to the procedural version
What does this even mean? If the world is not functional, then what is it? Is the world procedural? And what world are we talking about? Our planet, physically? Are you then discounting the worlds of mathematics and logic? You don’t gain performance? What performance? Program execution speed? Development pace and time to market?
Compared to the procedural version? Is your view that procedural programming is inherently better — for any definition of the word — regardless of context? Would SQL queries be easier to write if we told the query planner what to do? Is the entire field of logic programming — and by extension expert systems and AI — just a big waste of time?
So many vague aphorisms which do nothing to further the debate. And Haskellers are the ones getting called “elitist”!
SQL queries are exactly one of those cases where functional expression of a problem outperforms the procedural expression, and that's why they're used where it matters.
Because neither of those are true.
You’ve conceded that SQL queries are one case where a functional approach is more ergonomic (after first asserting that the world is not functional, whatever that means). Why aren’t there other cases? Are you sure there aren’t other cases? One could argue that a functional approach maps more practically and ergonomically for the majority of general programming work than other mainstream approach.
No, I'm saying the functional abstraction doesn't work or is clunky in a lot of cases and that Haskell's approach of making it the core of the language is misguided (Lisp is less opinionated than Haskell for one).
> One could argue that a functional approach maps more practically and ergonomically for the majority of general programming work than other mainstream approach.
They could argue, but they would be wrong.
SQL works because it's a strict abstraction on a very defined problem.
Functional is great when all you're thinking about is numbers or data structures.
But throw in some form of IO or even random numbers and you have to leave the magical functional world.
And you know why SQL works so fine? Because there are a lot of people working hard in C/C++ doing kernel and low-level work so that the "magic" is not lost on the higher level abstractions. And can you work without a GC?
Functional programming certainly doesn't work literally everywhere, but to say Haskell's design is "misguided" is your opinion, and it's one that some of the biggest names in the industry reject. How much experience do you have designing programming languages? Or even just building non-trivial systems in Haskell? Judging by the evident ignorance masked with strong opinions I'd say around about the square root of diddly-nothing.
> But throw in some form of IO or even random numbers and you have to leave the magical functional world.
Wrong. Functional programming handles IO and randomness just fine.
> And you know why SQL works so fine? Because there are a lot of people working hard in C/C++ doing kernel and low-level work so that the "magic" is not lost on the higher level abstractions
Are you suggesting there aren't a lot of people working hard on GHC? Because if you are — and you seem to be — then again you would be wrong.
The reason SQL (really - relational algebra) works so well is precisely because relational data is strongly normalized [1].
But the data is only a model of reality, not reality itself. And when your immutable model of reality needs to be updated strong normalisation is a curse, not a blessing. The data structure is the code structure in a strongly-typed system[2]
Strong normalisation makes change very expensive.
[1] https://en.wikipedia.org/wiki/Normalization_property_(abstra...
Exactly. I don't know why Haskell fanboys insist on abstracting us so far from the machine. Declarative programming is really unhelpful because it says nothing about the order in which code executes.
We live in a mutable physical universe that corresponds to a procedural program. One thing happens after another according to the laws of physics (which are the procedural program for our universe). Electrons move one after another around circuits. Instructions in the CPU happen one after another according to the procedural machine code.
The universe would never consider running 2020 before 2019 and a CPU will never execute later instructions before earlier instructions.
Haskell fanboys talk about immutable data structures like it's beneficial to go back in time and try again. But it's a bad fit for the CPU. The CPU would never run some code and then decide it wants to go back and have another go.
Except thanks to a compile-time optimization.
I believe it is because abstractions are the way we have always made progress. Is the C code that's so close to the machine not just an abstraction of the underlying assembly, which is an abstraction of the micro operations of your particular processor, which in turn is an abstraction of the gate operations? The abstractions allow us to offload a significant part of mental task. Imagine trying to write an HTML web page in C. Sure it's doable with a lot of effort, but is it as simple as writing it using abstractions such as the DOM?
> We live in a mutable physical universe that corresponds to a procedural program. One thing happens after another according to the laws of physics (which are the procedural program for our universe).
You just proved why abstractions are useful. "One thing happens after another" is simply our abstraction of what actually happens, as demonstrated by e.g. the quantum eraser experiment [1][2].
[1] https://en.wikipedia.org/wiki/Delayed-choice_quantum_eraser
>The abstractions allow us to offload a significant part of mental task
Edge/corner cases in our abstractions is also how propagation of uncertainty[1] happens. You can't off-load error-correction [2]
[1] https://en.wikipedia.org/wiki/Propagation_of_uncertainty
All of this is possible only with careful bookkeeping of the microarchitectural state. I agree the CPU is a stateful object. But even at the lowest level of interface we have with the CPU, machine code, there are massive gains from moving away from a strict procedural execution to something slightly more declarative. The hardware looks at a window of, say, 10 instructions, deduces the intent, and executes the 10 instructions in a better order, which has the same effect. (And yes, it's hard for me to wrap my head around it, but there is a benefit from doing this dynamically at runtime in addition to whatever compile-time analysis.) In short, it is beneficial to go back and have another go.
This was demonstrated also in https://snr.stanford.edu/salsify/. If you encode a frame of video, but your network is all of the sudden to slow, you might desire to encode that frame at a lower quality. Because these codecs are extremely stateful (that's now temporal compression works), you have to be very careful about managing the state so for can "go back and have another go".
I am less confident about it, but what you say about the universe also seems wrong. What physical laws do you know take the form of specifying the next state in terms of the preceding state? And literally many of them are time-reversible.
Not sure what the parody was in computing 2020 before 2019.
https://en.wikipedia.org/wiki/Superscalar_processor
> a CPU will never execute later instructions before earlier instructions
https://en.wikipedia.org/wiki/Out-of-order_execution
> The CPU would never run some code and then decide it wants to go back and have another go
Even C abstracts the machine, the ISO C standard is written for an abstract machine, not high level Assembly like many think it is.
Abstracting the maching is wonderfull, it means that my application, if coded properly, can scale from a single CPU to a multicore CPU + coupled with GPGPU, distributed across a data cluster.
Have a look at Feynamm's Connection Machines.
> What does this even mean? If the world is not functional, then what is it?
The world is full of special cases, of corner cases, of exceptions, of holes, of particular conditions, of differences for first or last element, and other irregularities; it is also full of sequential events. Applying a function to a starting set, to produce a resulting set or value is very lean with clean nice mathematical objects and relations, but not with many real-world problems.
> And Haskellers are the ones getting called “elitist”!
Well, one may say that answering with questions could fit the bill...
True
> Applying a function to a starting set, to produce a resulting set or value is very lean with clean nice mathematical objects and relations
True
> but not with many real-world problems.
Debatable, but in any case a non-sequitur. Are you sure you're talking about functional languages as they're used in reality?
I once wrote a translator between an absurdly messy XML schema called FpML (Financial products Markup Language) and a heterogeneous collection of custom data types with all sorts of "special cases, corner cases, exceptions, holes, etc.". I wrote it in Haskell. It was a perfect fit.
Yes. All of which are modelled in Haskell in a pretty straightforward manner. I’d argue Haskell models adversity like this better than most languages.
> but not with many real-world problems.
Haskell is a general purpose language. People use it to solve real world problems every day. I do. My colleagues do. A huge amount of people do.
> Well, one may say that answering with questions could fit the bill...
I see. So trying to define the terms to enable constructive discourse is elitist. Got it.
If you want me to be more concrete and assertive, fine. No problem. Here we go.
You are wrong.
Have you watched the talk "Are we there yet?" by Rich Hickey? He makes a convincing case, referencing the philosophy of Alfred North Whitehead, that "the world is functional".
http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hic...
The case is convincing, but it requires you to reject your own, human, faculties.
Makes the use/expression of language rather awkward when you can't remember any words.
Why not?
To give a functional Haskell flavoured example, I can use the Reader and Writer monads (which are functional) to create a whole bunch of operations which write to and read from a shared collection of data. That feels a lot like memory to me.
Indeed, the Reader monad is defined as:
> Computations which read values from a shared environment.
I just don't understand the whole "you can't have memory / order of operations / persistence / whatever else" as an argument against functional concepts when they have been implemented in functional ways decades ago. The modern Haskell implementation of the writer monad is an implementation of 1995 paper.
Edit: it looks like who I responded to doesn't actually want to have a reasonable discussion, but for anyone else reading along, it's entirely possible to have functional "state" or "memory" - what makes it functional is that the state / memory must be explicitly acknowledged.
Trying do dismiss functional computation in this way is essentially a no true Scotsman; functional computation is useless because it can't do X (X being memory or persistence or whatever), but when someone presents a functional computation that does do X, it's somehow not a "real" functional computation precisely because it does X. Redefining functional computation as "something that can't do X" doesn't help anyone, and doesn't actually help with discussing the pros and cons of functional programming since you're not actually discussing functional computation but some inaccurate designed-to-be-useless definition mislabeled as functional computation.
It sounds mutable.
There you go. No mutation!
It's what all data storage systems do.
An immutable data store sounds pretty useless.
https://en.wikipedia.org/wiki/Persistence_%28computer_scienc...
Once again, you are free to ignore the things you don’t understand. Your trolling is mostly harmless.
Is that why you are ignoring the complexity behind persistence/mutability/state?
Even the Abstract Turing machine has ticker tape you know...
Isn't state/memory assumed by default when talking about computation?
What kind of computations you can perform on a Turing machine without a ticker tape?
What are you applying your α-conversion and β-reduction operations to?
Where this falls short is domains that are extremely ill-suited to keeping track of past states over time (his identity idea) simply because it would break performance or be hard to model in those terms, say simulations, game development, directly working with hardware and so on.
Much of his argument relies on the GC and internal implementation being able to optimise away the inefficiences of the functional model that needs to recreate new entities over and over, but this simply is not always enough.
This also is very obvious if you look into the domains where Clojure or other declarative functional languages have success, it's almost always business logic / data pipeline work.
edit: And in fact a lot of his most salient points aren't really as much about functional programming as they are about lose coupling and dynamic typing. A lot of his criticism of state and identity in languages like Java isn't related to Java not being functional, it's related to Java breaking with the idea of late binding and treating objects as receivers of messages rather than "do-ers".
Performance seems to be the problem there more than the model not fitting. And Rich’s answer would probably be that Clojure isn’t the right tool for those tasks in the same way that any general-purpose GC’d language isn’t.
Modeling a game or sim as a successive series of immutable states sounds great to me, so I’m curious where you see a mismatch with them. Abstracting hardware properties this way sounds useful too, but I’ve not worked with it enough to comment.
because I don't really think the functional model describes it in an intuitive way. You can model a sim or game at a high level like worldstate1 -> worldstate2 etc.. but it doesn't really tell you much, because often you don't really care what the state was a second ago anyway in particular not in its entirety, and because at that high level of abstraction you don't really get any useful information out of it, so there's no point in tracking it in the same way it makes sense to track a medical history or a bunch of business transactions.
Rather instead of thinking of games or sims as high level abstract transitioning states we tend to reason about them as a sort of network of persistent agents or submodules and that lends itself much closer to a OO or message based view of the world.
I think in many systems that are highly complex and change very incrementally reasoning about things in terms of functions doesn't really tell you much. You can for example reason about a person as like say a billion states through time but there's not much benefit to it at least to me.
I agree that looking at a whole worldstate at once is unlikely to be useful. But I do see great value in keeping state changes isolated to the edges of a system, and acting upon it with pure functions. If you have the means to easily get at the piece of state tree that you’re interested in, you can reason more clearly about how each piece of a simulation reacts to changes by just feeding the relevant state chunk to it and seeing what comes back. That takes more work to set up or repeat if each agent tracks its own internal state.
I haven’t used Clojure to make a game before, so I’m speculating here. I find myself avoiding internal state by habit lately, though of course “collections of relevant functions” are valuable tools. I just lean towards using namespaced functions instead of objects with methods.
My interpretation is the exact opposite. Newtonian physics suggests the fundamentally deterministic world that QM can't because in the latter the only thing that is deterministic is probabilities.
Programming languages aren’t about the final result but about how you decompose the result into modular abstractions.
If I tell someone “I’m not smart enough to use php”, I’m admitting a weakness. I genuinely don’t have the mental capacity to keep details straight when using it. If I tried to use php for at a job, I’d move at such low velocity that I’d get PIP’d.
Or like saying “I can’t write code without automated tests.” there are genuinely people who, if they try to write code without tests get stuck for hours and don’t know how to move forward.
I’d think someone this is the same sentiment. I’m willing to believe that the Haskell community is elitist in other ways. But how is “I’m not smart enough to X.” An expression of that?
Imagine a programmer who, when she studies Haskell, discovers that the way it resembles category theory provides really effective mental affordances to her memory. She feels that Haskell's formalism makes it easier for her to have a clear mental model. When she writes and reads Haskell, she can predict what that code does to data and her predictions are largely correct. She feels confident in her understanding. This means that if she has a business goal she needs to fulfil, she's confident she can estimate that task, communicate about with stakeholders its complexity. and execute it.
Imagine that this programmer, when she starts to study PHP, does not find similar affordances. She finds it difficult to build a conceptual structure of it in her head. She finds that she forgets things or overlooks things when she tries to write PHP. If she tries to predict how a piece of code she writes in it behaves, she frequently is wrong. She feels very nervous about it. Consequently, when asked to plan adding a feature to a PHP task, she doesn't feel she's got a good enough grasp of her tools to answer. She worries about the risk to the business of her blowing past estimates and leaving making errors in production.
She tells someone "I don't think I'm smart enough to write PHP".
1) Is this statement a lie? 2) Is this statement elitist? 3) Is she a person who could exist?
What if her positive feelings about Haskell lead her to evangelise it in a way that ignores the fact that another person's brain might work in a different way to hers. She claims to this other person that Haskell is easy.
4) Does that statement make her elitist?
-----
My answers: No, No, Yes, Kinda yea.
Functional programming is just one of the paradigms, and useful in it's own domain. Its not a end all solution to everything.
What are you basing this on?
It's a fresh, and pragmatic too, take on how to do pure functional programming, but without advanced concepts (like higher-kinded types).
could be (and maybe even has been) ported over to other languages besides Scala
Ah, so Common Lisp then?
You know what, use whatever makes you happy, while we continue to avoid success at all cost.
After reading it, the language just clicked with me, although it is for complete beginners, it is a great read nonetheless.
Doing practical stuff like webdev, apps etc is a bit trickier, partly because Haskell is not the first language of choice for these kinds of projects, and thus lacks much needed documentation for starting up and best practices in those spheres.
Your life won't be easier if you solve problems, because solving problems is hard. But you get a lot of benefits from using any language which has features similar to Haskell because of the above reasons. If you are into programming language research, compilers, theorem provers etc, Haskell comes with a lot of idioms, features and tools that it is a viable language for the tasks at hand.
[.] a lot of languages rightly claim it like lisps and modern lisp derivatives, but Haskell brings with it a top of the line type system, (imo) a clearer syntax and tooling than OCaml, and a decent support from the industry and the academia..
For me, the quest is to be able to build more with less effort, with less bugs and make things more maintainable for the future. For this, I've found Clojure (of the 10+ languages I've professionally worked in) to be the best one, but like all the rest of the languages, it doesn't fit every single use case. But most of them, so far at least.
I could go on and on giving you arguments for/against, but I think these goals are the same for most programmers who aim to learn/use some of the lesser known languages.
Idempotence eliminates the need for a whole bunch of retry and error checking code, which themselves are often highly stateful and susceptible to race conditions and bugs.
List comprehensions can make allow for certain guarantees about parallelizing which is becoming ever more relevant as processors scale with threads / cores rather than clock speed. All without explicit programmer intervention.
Strong typing means the compiler can check types at compile time rather than runtime - this (largly) eliminates entire classes of errors before they happen. If errors happen at compile time, the programmer fixes it, if errors happen at runtime the user gets annoyed.
These are just trivial examples of some of the benefits of these technologies - see how these strategies eliminate not single errors but entire classes of errors. Selecting the right technologies means less bugs, less headaches for users, less support calls, less firefighting, and THAT is how your life will be easier with Haskell, for example
Strong typing means you'll spend your time waiting for the compiler to finish, or thinking how to unwrap this monad stack in a way that doesn't suck. It also means large parts of your app will have very unstable interfaces with way too many dependencies, you'll constantly be fighting with cabal or whatever, you'll often have to do busy work to keep track with external dependencies, you'll constantly be tempted to represent a different or additional subset of your invariants in the type system using the newest fancy language extension (that slows your compiles down even further, and may have subtle interactions with some of the other extensions you're already using).
Idempotence isn't a golden hammer, it's not suitable for every task. It is suitable for 1 way purchase code, increasing robustness in faulty networks, initializations. It's not suitable for logging.
It's not suitable for making list processing fast (that was some guarantees of list comprehensions, I think you got confused there)
> Idempotence makes it extremely hard to control when something actually happens or how much memory is used.
No it doesn't, that's totally wrong. Idempotence doesn't dictate execution time or memory used, it's not an implementation detail it's an abstraction level higher than that : it's a system strategy. This sounds like you're confusing idempotence with lazy evaluation.
> Strong typing means you'll spend your time waiting for the compiler to finish,
Compilers are really fast these days, only compiling the changes. "Waiting for compiler to finish" hasn't been a problem since the 90's, even on larger codebases (100,000+ LOC).
Also - So you cant be bothered to wait for the compiler to check your code, then the entity that's going to do it will be your users, in production. So maybe you can spend the time you saved not waiting for the compiler answering the support tickets coming in?
> thinking how to unwrap this monad stack in a way that doesn't suck
Subjective, no examples. This is just whining.
> It also means large parts of your app will have very unstable interfaces with way too many dependencies, you'll constantly be fighting with cabal or whatever, you'll often have to do busy work to keep track with external dependencies, you'll constantly be tempted to represent a different or additional subset of your invariants in the type system using the newest fancy language extension (that slows your compiles down even further, and may have subtle interactions with some of the other extensions you're already using)
Unstable interfaces with way too many dependencies is not a language problem it's a system design problem. It sounds like you have inexperienced software architects making poor decisions.
Won't reply to everything you've said, but just one little thing that I remember vividly. I was trying to get this 300-400 lines basic OpenGL setup code to work. Each time I made a little change it took 10+ seconds to compile. I went with the minimal types needed to interface with this OpenGL library, which was nothing fancy by Haskell standards, but just a "straightforward" C wrapper.
Template Haskell is the worst for it, but I did have a code generator produce code that took about 25G of memory to compile recently, which was very, very slow on a 16Gb machine ;)
You can see this is false by running ghc in type-check-only mode ("-fno-code"). Type checking is less than 10% of compile time. If Haskell takes a long time to compile it has nothing to do with types.
(and you mean "static typing")
> (and you mean "static typing")
Let's not split hairs. And I think "strong" is actually what I want to say (maybe better: "advanced" or "complex"). C also has "static typing" and I explicitly don't mean a simple type system like that.
Indeed, and with some language extensions type checking may never terminate! But library code only has to be compiled once and if you write code that takes a long time to type check that's on you. Bog standard type safe Haskell 2010 code type checks in the blink of an eye.
If your argument is "advanced type system features are too seductive for people to avoid" then I'd be more inclined to agree with you. But that's not what you said.
> > (and you mean "static typing")
> Let's not split hairs.
Well, Python has strong typing and doesn't take long to compile. Perhaps you meant "some advanced features of Haskell's type system". If so I'd agree with you. But don't discourage people from Haskell by making false statements about it and then accuse me of splitting hairs.
As far as Haskell itself goes I've written a little in the past in comments on here about why it's useful:
https://news.ycombinator.com/item?id=20111321
https://news.ycombinator.com/item?id=20260095
These days when I want to start a new project the decision on whether to use Haskell or Rust is the biggest consideration I make -- the expressiveness and safety and correctness Haskell can provide versus the raw speed, ease of build, memory safety rust provides.
First, I suppose those three things fit together in the sense that they make list comprehensions safe and fast.
But as you say, Haskell isn't the only language that gives you that.
I think the killer feature of haskell is that the community went through ridiculous contortions and pain to get rid of side effects.
Which means that Haskell programs exclude certain kinds of bugs - so it's not just that you can program in a functional style, but that you can be fairly certain there's no "shifting sands" under your functions.
On the other hand, I see many haskell programmers say that the powerful type checker forces them to think a bit differently, and perhaps harder, when writing - with the result that once the type signature fits, the function tends to "just work".
Anyway, I never did really enjoy Haskell - not as much as StandardML anyway. Sadly there doesn't seem to be any sml with good real-world libraries, so I've not really been using that either, outside of university :/
Perhaps looking at one of the few popular haskell utilities, and see if you feel Haskell is a good fit? I'm honestly not certain myself.
https://github.com/jgm/pandoc/blob/master/src/Text/Pandoc/Re...
A comprehensive test suite would achieve the same thing, but I'm rarely confident that my test suites are truly comprehensive.
Maybe your answer will be that that the stored program is an abstraction of the cables and plugboards, so you can be more productive.
Today, there are several additional layers of abstraction on top of that, functional programming is one of them.
"I do believe that there is real value in pursuing functional programming, but it would be irresponsible to exhort everyone to abandon their C++ compilers and start coding in Lisp, Haskell, or, to be blunt, any other fringe language."
Even Carmack has a subliminal jab at languages on the basis of popularity from time to time lol
Now. there are many reasons why one would want to use Haskell. For instance, to have fun, to understand better some pattern, to write expanding-brain memes, or, because they are good at solving problems with it. It is fine if your reasons do not intersect with other people's reasons. You can try finding answers whether Haskell matches your reasons in some other essays [1,2,3]. Maybe it is true for many people that their needs and desires are entirely covered with their own toolset. As a curious/optimistic person I find it incredibly pedant to say I'll never ever need to learn/use something (there's different goodness in everything).
Personally, practicing Haskell led me to appreciate the importance and trade-offs that occur when isolating/interleaving side-effects. Similarly, it gave me some vocabulary to articulate my thoughts about properties of systems. Both are super important in the large when you architect softwares and systems. There are other ways to learn that (e.g., a collection of specialized languages), but at least for me, Haskell helped me build an intuition around these topics. That said, the prevalence of negative and derailing comments in discusions about Haskell can be demotivating (but our industry is like this ️).
[0] https://patrickmn.com/software/the-haskell-pyramid/ [1] https://www.snoyman.com/blog/2017/12/what-makes-haskell-uniq... [2] https://www.tweag.io/posts/2019-09-06-why-haskell-is-importa... [3] http://blog.vmchale.com/article/functional-haskell
Would it be reasonable to declare violin sucks! I've played piano for 30 years, vibrato is so hard it must be wrong, tell me why I should learn the violin?
Actually probably a lot will come with you, your dexterity and music theory will be quite useful, as will your ear.
But really, the instruments are very different and if you want to level up your sense of pitch or really learn how to sing a melody, the violin can be great for that...
Most of us here are programmers, a lot of us professionals.
Music is the output of musicians, as perhaps code is the output of programmers.
But music comes in all kinds of flavours: classical vs jazz vs pop
Performance vs composition
Recital vs improvisation
And then choice of instrument within any of those areas.
I feel a lot of people misplace their aversion to Haskell because they fail to recognize how different Haskell is.
It's a bit like when English speakers think Chinese is a "hard" language. It's not, linguistically it's actually a pretty simple language (whether the script is easy/hard is up for grabs...)
Actually Chinese is just different to English so you have few anchors and familiar friends to base things off so it feels hard cos you're starting afresh, like a child. And yet children of a china manage quite fine learning chinese....
Meanwhile, ask a Spanish speaker about their experiences learning Portuguese...
Haskell is rather similar to a violin in that respect.
Rightly or wrongly, in programming we do expect this. We keep saying things like how languages are only tools, and how terms like "Java programmer" is as absurd as "Casio mathematician".
If you have effortlessly switched between imperative OO languages many times before, it's easy to think that any language you can't quickly learn must be because it's fundamentally and abnormally difficult.
Let us not forget the roots of Haskell as a research language where ideas in functional programming are to be tried out. When it emerged in the 1980s it was literally because a committee wanted a solid foundation to replace a disparate mélange of functional programming languages. It succeeded in that, and immediately introduced new features then considered highly novel (e.g. type classes). In this sense, Haskell will never be as practical and pragmatic as Go, where moderately modern language features aren't even in the language. Choosing a language like Go could be a valid choice, and so is choosing a language like Haskell.
What I don’t like are some (not all) of the people who use Haskell and talk about it online. They can be incredibly obnoxious. Haskell is not a tool they work with, it’s their entrance to a class of software engineers that you’re not capable of being part of.
Just for the record: Anyone can learn Haskell. If you’re struggling with it that’s because of a tooling / literature issue. Haskell people (even the nice ones) are bad at explaining the language. The tooling isn’t very friendly and the language is a shit sandwich to debug.
The concepts therein are no more difficult to grasp than any other in programming. Is a Monad a box or a burrito? Please stop. It’s a language feature that allows you to do certain things more concisely than the alternative. Stop pretending it’s magic and show people how to use it and why they should. Stop drawing pictures.
No, algebraic data types are not hard to understand. You just make a big deal out of them because you think the word algebraic sounds cool.
I could keep going but I think I’ve made my point.
Dear Haskell people: Get over yourselves. Do not mistake the larger software community’s disinterest in your pet language for their inability to use it. That’s simply not the case. If people had to use it even ‘lowly js devs’ would master it.
Everyone else: If you haven’t used it already it is a very cool language and worth learning even if just for personal edification. Don’t let the self appointed high priests turn you off. Don’t expect their help either.
Could you link me to an example? I have some measure of authority in the community and would like to help decrease the amount of obnoxiousness but I can't unless I know where it happens.
> Dear Haskell people: Get over yourselves. Do not mistake the larger software community’s disinterest in your pet language for their inability to use it. That’s simply not the case. If people had to use it even ‘lowly js devs’ would master it.
This is really interesting because the Haskell community I hang out with (mostly on Reddit) would love it if "lowly js devs" would master it. In fact we all too often get told by non-Haskellers that "Haskell will never succeed because it's too hard for most programmers to learn"!
Great, so using those simple properties let's reimplement Eclipse. Let me use tensor flow. Replace my Ruby on rail website.
This to me (as a non haskell user) implies other languages can't have anything interesting to offer, as they can be so trivially replaced. That is the type of attitude I see from haskell programmers.
Good enough for you?
But what it does, is pushing everybody to write simple code which is easy to understand. So many times I found the documentation (of some library) to be incomplete, but jumping right into the code answered the questions I had. I can't say that about every language.
But after seeing its gc performance sitting at the single-digit ms timeframes with huge heaps (60 gigabytes!) I realized I needed to hold my nose.
What helped me make peace with it is its automated testing story. It's baked into the language in a way I've rarely seen in any other runtime.
Instead, I really like:
1. Stephen Diehl's “Eightfold Path to Monad Satori” from “What I Wish I Knew When Learning Haskell”:
http://dev.stephendiehl.com/hask/#monads
“Much ink has been spilled waxing lyrical about the supposed mystique of monads. Instead, I suggest a path to enlightenment:
1. Don't read the monad tutorials. 2. No really, don't read the monad tutorials. 3. Learn about Haskell types. 4. Learn what a typeclass is. 5. Read the Typeclassopedia. 6. Read the monad definitions. 7. Use monads in real code. 8. Don't write monad-analogy tutorials.
In other words, the only path to understanding monads is to read the fine source, fire up GHC, and write some code. Analogies and metaphors will not lead to understanding.”
2. Chris/kqr's “The ‘What Are Monads?’ Fallacy?”, which mirrors the advice from Stephen Diehl:
https://two-wrongs.com/the-what-are-monads-fallacy
“Instead, learn to use specific monads. Learn how Maybe a works, learn how Either e a works. Learn how IO a and [a] and r -> a works. Those are all monads. Learn to use them with the >>= operator and with do notation.
Once you've learned how to work with all of those, you'll have a really good idea of how monads can be used.
Asking "What is a monad?" to learn how to use monads is as wrong as asking "What is a musical instrument?" to learn how to play musical instruments. It's a good start, but it won't teach you very much.”
This is such a great analogy!
Another good analogy might be learning circular breathing for ordinary everyday conversation.
You might hear the circular breathing is an amazing thing that allows you to do things you couldn't otherwise do. If you are trying to use that trick without ever learning to play an instrument then there will be a great air of mystery to it all.
Monads solve problems that are only made apparent by other design decisions in Haskell. In a non-functional, loosely typed context they are not so useful.
Philip Wadler's original paper on Comprehending Monads [PDF]: https://ncatlab.org/nlab/files/WadlerMonads.pdf
Philip Wadler and Simon Peyton-Jones' paper on Imperative Functional Programming [PDF]: https://www.microsoft.com/en-us/research/wp-content/uploads/...
Simon Peyton-Jones mentions both papers and talks about why monads came to be part of Haskell in his talk on the history of Haskell here, with examples that are a little more accessible than the above papers: https://www.youtube.com/watch?v=re96UgMk6GQ [introduction to purity and then monads starts around 30:07]
It's a funny talk and it's worth watching in full, but here's a nice soundbite:
“So what did we do for IO? Well, we just didn't have any. [audience laughs] So the joy of being an academic, right, is you can design a language — in 1990, I beg to inform you — that had no input/output. A Haskell program was simply a function from string to string. That was what it was. … But this was a bit embarrassing…”
He goes on to talk about other ideas they explored to create effects, why they settled on monads, and why he wishes now they had called them something like “workflows” (as F# later did[1]) to make them sound less intimidating.
(Simon and Philip will both be at Haskell Exchange 2019 in London this coming week if anyone else, like me, enjoys spending two days as the dumbest person in the room: https://skillsmatter.com/conferences/11741-haskell-exchange-... )
[1]: https://blogs.msdn.microsoft.com/doriancorompt/2012/05/25/7-...
This is key. I've tried to write a "Monads in JavaScript" article many times over the years, but it's pointless, because you have to preface it with 100 imaginary constraints.
I can't recollect I have ever learned a programming concept through metaphors. The only way I have learned concepts is though solving tasks in a language and thereby learning to use the tools available.
I actually think Haskell is a very enjoyable language, but there is a culture around it which treats the type concepts (arrows, monads etc.) like goals in themselves rather than tools to achieve something useful.
Could you perhaps link to one? I know of plenty of misguided tutorials that try to be helpful but aren't. I've never come across one I'd call "condescending".
> I can't recollect I have ever learned a programming concept through metaphors. The only way I have learned concepts is though solving tasks in a language and thereby learning to use the tools available.
Yet for some reason newcomers (reputedly) want to know what a monad is before trying to use one! I agree with you: they should go ahead and use one and not worry about what it is!
Ultimately most of these are just imitations of the 'poignant guide to ruby' which is probably responsible for starting the trend of whimsical and zany programming guides.
> Yet for some reason newcomers (reputedly) want to know what a monad is before trying to use one! I agree with you: they should go ahead and use one and not worry about what it is!
Chapter 9: Input and Output
Chapter 12: A Fistful of Monads
No, that doesn't seem to be the case to me.
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Hello World!\n");
return 0;
}
class HelloWorld {
static public void main(String args[]) {
System.out.println("Hello World!");
}
}
main :: IO ()
main = print "hello world"
Hello worlds in C, Java and Haskell. I can write them and run them without needing to fully understand what's behind "*argv[]", "static public void" or "IO ()".I've taught some Java classes. I can get people writing and running trivially useful code before explaining the concepts of "class" and "static". I explain that parts of the code will remain a mystery for a little while until I explain them, I promise to do so, and of course I fulfill that promise as soon as I can.
I feel neither Java nor Haskell are intrinsically bad just because there's no practical way to teach them completely on progressively layered concepts. Even (e.g.) Lisp has a risk of using special forms before understanding them.
I guess I should write a Haskell tutorial which starts with "hello world" without trying to teach monads beforehand!
To write any program in Haskell, you define `main`, the IO action that does the work of your program. `putStrLn` is a function that takes a String and returns an IO action that prints the string.
So we can write "hello world" simply:
main = putStrLn "Hello, World"
I'm curious whether you actually think that's more useful than building understanding at the REPL.Again, I think this depends on you way of learning. For some people it might be possible to "build understanding" gradually over weeks until you are finally ready to write "hello world" having learnt all the fundamentals of the language. That just doesn't work for me. I need to learn through writing programs which actually does something.
You keep describing the alternative as if it's a bunch of theory before any practice. It's actually just a slightly different form of practice than maybe you are used to.
I am sympathetic to the notion that it might not work for you, but you haven't spoken at all to the actual distinction. If it really does make a big difference for you, I'm very interested in any unpacking you can do.
It is not an accident that almost any tutorial for any language or framework or platform starts with "hello world". Because you want to start with the minimal but real, working program - and build from there.
In either case you are simply printing a string to the terminal. The standalone program is easier to compose in your shell, which in some contexts matters a lot, but I don't see that it does here. Where is the difference?
Further, if we define "interact with the outside world" in a way that excludes the programmer reading things off the screen, then it's plainly wrong that all such programs are "literally useless". Calculators, for instance, have delivered a tremendous amount of value. I've personally run something at a REPL (in various languages) plenty of times because I had actual use for the value to be printed and didn't need to persist the program.
"A complex system that works is invariably found to have evolved from a simple system that worked."
I understand that from a certain theoretical perspective it is just the same thing to echo a string literal in a REPL, but from a software development perspective it is completely different.
> The standalone program is easier to compose in your shell, which in some contexts matters a lot, but I don't see that it does here.
I kind of see where you are coming from. You are assuming the program is only ever used by yourself. I understand this is just a different culture and hadn't even thought about that perspective.
For example Rust is another language which have a reputation for being hard to learn, due to novel and complex concepts like the borrow checker. But in my experience you see none of this "talking down" to the audience. They just explain how the stuff works with practical examples.
Not at all. Haskell has a REPL, so anything involving output is defined as pure functions, then executed at the REPL. For example, here's Quicksort: http://learnyouahaskell.com/recursion#quick-sort That's from Chapter 5.
> I certainly wouldn't be able to learn anything that way.
Certainly not by only reading the table of contents, sure.
For academics, the purpose of a program is to exhibit some clever abstraction. So a quicksort which can't take any input to sort and can't produce any output is a perfectly fine program - almost the platonic ideal of a program.
But for developers, the purpose of programs is to do something useful. So learning how to read input and produce output is basically the first thing I want to learn when learning a new language or platform. Because then I can start building small toy programs, gradually doing more and more stuff and learning by tackling the challenges along the way. And implementing quicksort comes way down the line of things I need to learn to write a useful application.
This isn't actually true. You need to use a value of the `IO ()` type. Your program would still work if `IO` was not an instance of `Monad`. In a similar way, you also need to use a value of type `String` but you don't care that it implements `Read` or `Ord` or `Semigroup`.
This doesn't entirely invalidate the rest of what you've written - you do need some basic understanding of how to combine IO actions to do any more interesting IO.
But I feel like your presentation is still misleading. "Learn You A Haskell" starts with the REPL, and has you running code in the first tutorial.
As I see it it’s actually not very nice.
With the utmost respect, truly, humbly: I am against this custom that it’s just fine to call well-meaning people for condescending. As it appears to me, claiming condescendence is more of a statement of the claimant, but this is still an unfortunate custom. Who are we really to claim condescension? Really?
The article linked to in the post starts off with quotes from people who directly state that they feel like they would need to be smarter to use Haskell. It’s a common thing. The article addresses that. It is literally the opposite of condescension.
Then there’s the burrito tutorials with pictures. I for one actually think in weird abstract sloppy metaphors and colors. My head really is full of burritos and inaccuracies. I was helped enormously by the burrito concept. Textbooks generally don’t speak in burritos. I work with people to whom the burrito class of analogies is not helpful. They think in a more direct and crystalline way. I wish I were more like them. And I try try to be. And I can. And it makes me better. And I really do think I also bring something to the table by spraying flaming burrito concepts and lateral jumps into the team zeitgeist.
OK fair enough, I can only speak for myself and the way I learn concepts. The way I learn a new language or platform is always to try to write a real program that does something. I am only able to lean concepts when I can see their usefulness. But I should recognize that other people learn in different ways.
Not such a bad life, if you ask me.
The only condescension I see in Haskell-related discussion is condescension towards Haskellers,
fwiw I've heard exactly the same claims by and about lispers.It's probably condescending for haskellers because it shows that monads are not some enshrined primary concept but just a kind of dumb wrapper you can write in any language and you do when it's useful for you.
I get it though. The concepts are so advanced that you have to internalize the concept first before it can even be used.
The other article that demonstrated the practical purpose or path to constructing Monads showed a number of them using nested procedural syntax formatted to look like do-notation. [I can't remember the title/link, if anyone knows please post.] It went through async/await, maybe or list and showed them rearranged with the 'monad'ic parts off to the right where semi-colons might be.
Do-notation just took that and flattened the nested structures. The appreciation that these different computational contexts could all be structured the same way while focusing on what's happening apart from these contexts really demonstrated why it's useful and the power of being able to encapsulate these seemingly different ideas.
I'm sure I'll need to find a part-3 in my journey (perhaps Monad transformers) and so on, but I've at least got a good footing from those.
Neither one of these conditions are remotely indicated by research on the subject, therefore the statement is false.
Also I did try learning Haskell at one point and I found it harder than some other languages, I would say I found it Erlang level hard which I tried at about the same time( about 2009) but I think Erlang has a little more inclusiveness to their community IMHO
What is the standard unit of measurement for quantifying just how difficult a language is to learn? Is there a standard unit of measurement for quantifying the competence of a person learning a programming language?
However it has been shown that there are people who seem more attuned to different learning styles, styles of programming, there are differences in difficulty between first language acquisition and later (dependent on language similarity, language domain), and many other studies regarding programming language learning that it can be said not everyone learns equally well every language, and not every language is equally as learnable.
I think that’s reasonable, but the question is then at what point does a language become sufficiently difficult that it no longer provides a good return on investment? And to what degree of proficiency must one achieve in a language in order to productively use it?
It doesn’t take a long time to learn all of Elm, and you can be productive in it quickly.
It takes a very long time to learn all of Haskell, but it does not take a very long time to be productive with it.
Did you click through to the article? The HN title is (currently) "Learning Haskell is no harder than learning any other programming language". The article's actual title is "You are already smart enough to write Haskell".
You appear to be responding to what a zealous mod wrote not what anyone actually believes.
the article's actual title is wrong in other ways.
1. because someone might not be smart enough to write Haskell (is that different than program in Haskell?)
2. Because someone might be better suited to other language types than Haskell.
language types I prefer - small instruction sets, functional or declarative (that however is my preference) someone might just find object orientation a more natural way of thinking.
That does not follow. "Research does not prove it" means "therefore we don't know it's true", not "therefore it's false".
To your wider point: I think (but cannot prove) that some peoples' brains find FP easier to learn and some peoples' brains find procedural easier. And I think (but can prove even less) that the large majority find procedural easier.
We defaulted to PHP and C# in the past.
We have quite some experience training people who just graduated and even people with backgrounds outside of tech.
Training someone from zero to autonomously writing production code is a lot easier in F# compared to PHP and C#.
We educate people in Elm and Haskell and switch over to F# when they’re ready to try building the first real thing end to end.
It's been over a decade since generics were introduced, but I still encounter code using ArrayList today...
https://www.amazon.com/Get-Programming-Haskell-Will-Kurt/dp/...
a) Inherently simpler and require no prior advanced knowledge of math.
b) They are the default in every curriculum and once your brain is wired that way everything else seems counter-intuitive.
I remember in the first grade of my elementary school we were taught basic algebra and how sets work (union, intersection etc). I understood both concepts but I couldn't relate sets to the outside world. Like why do I even need this. Maybe new programmers feel this way about functional languages and Haskell.
c) That's the way microprocessors actually work. Machine language is just imperative commands and branch instructions.
I had a hard time understanding the Haskell ecosystem, plus the syntax is just way too different and restrictive.
For example the last tutorial I tried to follow recommend using nix package manager to install Haskell. I failled missereably, it just did not work out of the box.
someone should make a haskell fiddle for newcomers
ps: I wouldn't think that syntax would matter that much, was Scala your first FP language ?
1) Haskell is much like math. You look at some concepts and ideas and fail to see what it is about, until 20 years down the road you finally click that "maaan" discrete mathematics was already encompassing 90% of computation it just wrapped it in counting instead of concrete computer instructions. Mathematicians have this culture of going too far, too concise, too fast for most of the population.
2) Mainstream culture imposes a negative toll on this because when you're fed Java or PHP (let's say you learned in the 2000s when they were peak fad) you'll interpret Haskell through them, brain already set on a belief, and it will make it twice harder. And it's really hard to disentangle the weird bliss of being able to manipulate elements through an interface (here it would be the syntax its idioms) from "objective reality". I too was enamored by PHP associative array syntax (so fun compared to C or Java lack thereof) and F5 to see a website change before my eyes. Haskell feels like a punishment compared to this. Not even counting the social aspect of it .. wordpress made people have colleagues and money.
Anyway, keep learning haskell (and FP, and logic, and math).
So this begs the question: where are the complicated systems written in Haskell that could not have been realized in other languages? Or are Haskell programmers only using the language to sleep better at night?
For me, the balance point lies definitely closer to the latter. I'm not writing hugely more complicated programs in Haskell than I did in Python but I feel much better knowing they're not likely to fall into pieces next time I touch them.
If you are developing a business application and no one dies if something goes wrong for a few hours, the answer is "hell no". Productivity takes a hit for no strong upside to the biz.
If you are developing a safety-critical system, the answer is "maybe". This one is more obvious.
The more complex reasoning is that ultimately, almost every software system has to interface with the outside world in some way. Putting your functional programming layer as your principal interface between your internal and external domains is an extremely high-cost endeavor due to the complexity of handling things you can't predict. If your system is safety-critical, and you have extensive control over the external domain (e.g. dedicated, redundant sensor networks), you might be able to justify this added complexity because you can control many more variables than you would be able to otherwise.
An alternative approach that seems ideal to me at this time is to use imperative techniques as the principal architecture, and then use functional within that domain in the specific cases where it can be justified. Good examples of this would be C#/LINQ, or even invocation of F# from C# (e.g. rules engine). Imperative is extremely good at handling side-effects and managing exceptions. Functional is deterministic if you can keep it on rails. Using both where most suitable w/ interop between seems to be the most productive approach.
A quick corollary could be: "If you are exclusively using functional techniques to build your application, you are probably making a mistake".
I fail to see why if you believe this is true of haskell, it would not also be true of C#
That said, Haskell just feels like a different beast. I also don't know how interop would actually work with these different solutions, would feel like unnecessary complexity if anything. Only to end up losing the benefits of Haskell's strong typing.
I've been struggling to justify spending more time & effort on concepts mostly exclusive to Haskell, only to be able to be productive on it. I keep thinking I should brush up my Elixir skills instead.
You appear to be responding to what a zealous mod wrote not what anyone actually believes.
>pure functions and values >pattern matching >sum and product types >how to glue together IO to make side effects
You can do pure functions, pattern matching, and sum and product languages in plenty of other languages that have much more user-friendly syntax, community, and documentation. Also doesn't hurt that they have impure IO so you don't have to jury-rig impure IO using a monad.
> If you’ve already learned another language, you can learn Haskell. And even if you haven’t, learning Haskell is no harder than learning any other programming language.
But most of the programmers are familiar with imperative languages, and can quite easily switch between them (possibly writing non-idiomatic and awkward code, but without having to really learn the language from scratch to do so). While a purely functional language with unfamiliar syntax doesn't let one to do it as easily. So indeed, if you haven't learned other languages, probably it's not harder, and if you have, you still can learn it, but likely it would be harder to learn than other imperative languages if you are already familiar with a few imperative languages.
I think the HN title used to be correct. Perhaps the mods changed it. I can't imagine why. It's a completely different claim from the article!
( UPDATE: I stand corrected, in a reply by tome. )
I'm writing and debugging some functional code - in Clojure. There's a function I've smoke-tested on its own but that's failing me with "real" data. Lacking a decent debugger for Clojure and being too lazy to isolate my problem into a test setup, my tool of choice is the lowly "debug print."
In Clojure, the body of a function is essentially imperative, and I can insert a `(println "label:" value)`. To do the same thing in Haskell, I'd have to restructure my whole damn program.
I understand the rationale for purity in Haskell, but sometimes I see it as badly standing in my way of accomplishing what I want to do.
https://hackage.haskell.org/package/base-4.12.0.0/docs/Debug...
I think my point is still supported to some extent by the fact that this is knowledge from outside of the language that I'd need to know to accomplish. It's something I need someone helpful like yourself to tell me about. Also something that only works because this library deliberately breaks Haskell's rules.
I'm glad it was helpful.
> It's something I need someone helpful like yourself to tell me about.
In general we're happy to help but it does annoy us when people make assumptions about what it's like to program in Haskell without actually trying it. If you'd like help I can suggest Haskell Reddit https://www.reddit.com/r/haskell/ or emailing me personally http://web.jaguarpaw.co.uk/~tom/contact
> something that only works because this library deliberately breaks Haskell's rules.
Only if you have very punitive assumptions about what Haskell's rules are, like those who have never used the language often do. People who actually write Haskell programs have other ideas.
(edited for a word)
You appear to be responding to what a zealous mod wrote not what anyone actually believes.
My point was/is that even if it IS easy to learn, relatively speaking, WHY would you want to learn it when it's obviously a niche language... I mean a REALLY small niche at that...Hence I feel like one of the big issues is that people learn to program in a non declarative/imperative way. When you first pick up python / c / c++ etc, there are plenty of details you don't necessarily understand (who here has absorbed all of the c++ spec?).
At most Haskell is just different and I don't think it requires more mental effort than anything else.
There is a reason loops are easier to read and maintain compared to a fold(map(filter(zip(...)))).
Absolutely not, and I read and maintain loops in both Python and Haskell!
If only it had tail call optimisation, multi-line lambdas and other features that Guido doesn't want. Then it might be even more than bearable.
Long before Haskell became as mainstream as it is now, C++ people have discovered the benefits of STL algorithms over raw loops.
These are not opposite, and I happen to agree with you.
(fold op . map f . filter p . zip) l1 l2
Which I think is very understandable as long as you're familiar with what all of them do. Which you become, very quickly.
I say this as someone who quite likes Haskell, when you have a problem that fits well with Haskell it's really amazing.
The reason why I’ve given up my own time to write some blog posts (apart from it just being an occasional pastime), is that everyone complains that Haskell doesn’t have enough documentation or tutorials.
Damned if we do; damned if we don’t.
* "Haskellers just write tutorials but there's no good practical code."
* "Haskell will never catch on because it's too hard. They think it's easy but that's just because they're geniuses."
* "Haskellers are obnoxious and condescending. The language is easy but they want to pretend it makes them special and clever."
* "The Haskell type checker only checks really simple properties that are easy to prove by hand"
* "Haskell doesn't come with tooling that automate refactorings that are easy to make by hand"
The list of contradictory complaints goes on ...
We got a lot of books detailing various type theories, algorithmic nuances and so on in Haskell, but very little resources to actually help anyone get off the ground with practical web development.
Could you share some examples? It's probably a good idea to promote useful content!
lets be real, software engineering that brings results is i/o. i see the appeal in pure functions and guarding that out but the shit that matters is going to be the nasty mess of stuff that saves business logic that was hacked together over 10 years of rnd, interns and crunches
Read the book domain modelling made functional.
Easy as a design goal has led to many languages like AppleScript that try to ape natural language, or to visual languages, and those consistently seem to be bad choices.
Haskell is probably not as hard as many people who haven't made the jump think it is. But it is hard.
> What do you need to write real Haskell? The core of the language is extremely simple. If you understand how to write and use [four items broken out below] then you already have everything you need to write useful programs that solve real-world problems.
The core is admirably simple, but simple doesn't mean easy. Easy to learn is when you have intuition and existing skill that readily map to the core constructs used in a language.
> pattern matching
There are cases where pattern matching is obvious, but this is not how most people have learned to structure a problem.
People aren't naturally good at breaking problems into parts, that's a skill you have to develop over two or three years, which is my guess from observing myself and peers.
> sum and product types
Explicit types are already hard; you're already excluding many people who have trouble making the leap from Javascript to Java.
But Haskell's type system is heavily generic. And its support for container types is not so great. Making them easy was a major reason Perl, Python and Ruby grew quickly, and landed us a ton of horrible code.
To understand how Haskell makes these container types hard, compare the API for Data.Map to the API for java.util.Map[2]. You can ignore Java's rather pathetic facilities for FP entirely because you don't need them.
The Haskell version has functions to do lookup, union, intersection and various predicates. In Java, they're trivial to implement with a loop.
> pure functions and values
The intuitive way to change a thing is... well, to change a thing. I can express it directly in plain language. To express the pure way, I have to describe it as "construct a new value such that all properties are the same except for the changed properties."
I've seen a meme that shows Common Core multiplication in a pedagogical context, and contrasts it with the traditional long-form multiplication. It completely misses what CC is trying to do, but it's useful in showing what people are comfortable with: they grok procedures much better than abstractions.
Functions are the part of math where most people who were okay with algebra nevertheless started tuning out. First-class functions are weirder still. Haskell also mandates currying, and being strongly typed every function gets a confusing type signature.
All that said, keeping track of state changed by mutation is deceptively hard. It's a case where your intuition routinely leads you to build things you can't maintain. But sticking strictly to the claim in the OP, it's easy to learn to mutate state but hard to do.
> how to glue together IO to make side effects
This shows that monads are core to the language, and they have all the difficulties of generic types as well.
Everyone has jumped on how hard monads are already so I won't belabor the point, just to say that except for concurrency / parallelism, I don't think there are any core language features as hard to understand as monads.
(And I'm not sure exactly what the "core language" is, but I think anything in the Prelude is fair game.)
Haskell does make them dramatically easier with "do" notation, but I think you really do need to grok monads for your learning to progress beyond simple examples.
[1]: https://hackage.haskell.org/package/containers-0.6.2.1/docs/... [2]: https://docs.oracle.com/en/java/javase/12/docs/api/java.base...
> The Haskell version has functions to do lookup, union, intersection and various predicates. In Java, they're trivial to implement with a loop.
I don't get it. I don't see how you do lookup with a loop at all. As for union and intersection, are you sure you write them in Java with a loop? The way that's obvious to me would be quadratic. Is there a better way?
Hard to do... safely. Easy to do wrong, though. But wrong might still work most of the time. [Edit: "Works most of the time" is not praise, nor endorsement of the approach.]
To me, this is the fundamental thing that the FP people have right. Constrain your state space or die.