What I Wish I Knew When Learning Haskell
dev.stephendiehl.com
dev.stephendiehl.com
It's a pity I don't use Haskell at work and yet learning Haskell was the single most bang for buck exercise I have ever done. "Parallel and Concurrent Programming in Haskell" is the best resource I have ever read on parallel and concurrent programming concepts.
Every programmer should learn this language even if they never plan/get to use it.
Edit: Indeed, the PDF that is linked to in [1] looks like a scan of the original. [2]
[1] https://chrisdone.com/posts/dijkstra-haskell-java/
[2] http://www.cs.utexas.edu/users/EWD/OtherDocs/To%20the%20Budg...
Very Dijkstra to start by insulting the intended audience. Wonder if it had the intended effect?
But the truth is that I still don't feel comfortable writing anything more than toy apps in Haskell. I'm not really sure how to unit test things when I'm passing complex monad transformers around. I feel like I'm just guessing at best practices for arranging, say, a website of moderate complexity.
There's tons of resources for learning the language itself, but very few on actually applying it successfully to anything other than a single purpose command line app.
If you don't mind, what were the other 1 or 2 things?
Code Complete 1st edition
Maybe Domain driven design as well
Re code architecture and arranging code - It's not that different from what you'd do in any other language and it's more of a domain question (a webapp?, a game?) then a language one. If and when you change your mind though to move things around/re-architecture your code that is when Haskell really shines. Compiler assisted refactoring is one of the superpowers. It's like having google maps in a foreign city, it guides you to whatever your destination is.
I feel learning Haskell was worthless. It did not teach me anything about decomposing problems into function units, compositional sequences, pure functions / immutable data structures, lazy evaluation or asynchronous programming that I have found useful in modeling solutions to software problems.
Guarantees from the compiler and use of the type system itself for type designs like phantom types and type class patterns did not help reduce behavioral bugs and the slower development cycle of waiting for compilation (instead of just using occasional unit test runs to check simple safety conditions in lighter weight dynamically typed languages) was a very significant cost.
It was no easier, and often much harder, to enable extensibility into systems designed in Haskell because the constraints of the extensible needs of the system were only ever learned from changing business circumstances after the fact and could not be reflected in anticipated up-front design, which is perhaps the number one need of business software yet is an area where functional languages are uniquely poorly suited even with veteran, highly experienced engineers on the team like we had.
In the end, following simple patterns of composition and function purity while avoiding cookie-cutter design patterns and avoiding use of object / class constructs is all you need. This is easy to achieve in many imperative languages while leaving much greater flexibility to choose mutation, treat safety as a resource to be traded off instead of enforced in all cases, etc.
“Module oriented programming” in Python or C, letting lightweight unit tests replace heavy reliance on type system patterns, just using simple structs / namedtuples / dataclasses that posses zero function logic, and creating imperative APIs around functional-inspired core implementations is strictly better.
Really, strong adherence to statically typed functional languages with lazy evaluation is a religious trap. Learning that it’s not all it’s cracked up to be early in your career is critical for success.
Would you mind sharing what business domain you were working in? I’ve seen static FP being used/popular in finance but not much elsewhere.
Also, do you have a favourite language now?
My favorite two languages are Python and C. There are certain things I like about Rust, Go and Julia.
I dislike Java and C++ (just personal preference).
I dislike Scala a ton. Really disergonomic and overly complicated.
I like Haskell a lot. Very clean, fun to write, it just doesn’t confer any serious advantages over other language choices and does suffer from difficult-to-extend designs in business use cases, but probably very good for academic work, pure software or research projects. It’s very nice language, I just wish it wasn’t hyped as a transformational way of thinking or as a panacea for all the normal annoying problems with business software.
If you disagree, post a rebuttal.
The fact that classes of bugs prevented by compilers are not very important and are just as adequately caught with lightweight unit tests is more empirical but critical nonetheless.
These are real problems with the arguments in favor of strict functional programming. But instead of engaging with them, they are disingenuously called “language wars” as if they are less legitimate just by using this phrase to describe them.
I'm sorry, but it's an opinion-based discussion. No hard facts were presented here. While I see the reasoning behind such opinion, it directly contradicts opinion of pure FP proponents and somewhat contradicts my own experience.
> the business use case of that software suddenly demands incrementally mutating some part or introducing subtle violations of the constraints the system was designed to enforce
Sure, sometimes it's very convenient to add quick hacks, but there is definitely a tradeoff from maintainability perspective. I wouldn't like to work on a codebase full of mutable state and constraints broken in unprincipled ways. On the other hand, I never used Haskell and religiously functional Scala, so maybe I underestimate the scale of the problem. For me Scala with immutable collections, ADT, lenses, IO, typealiases and typeclasses as extension mechanism served extremely well. Could you please provide few concrete examples of type system abuse?
Did you already know most of it from Scala?
I thought I had some idea about controlling state and writing composable systems before, but I see now how rudimentary and flawed it was. Maybe you just got it quicker.
Can you clarify if you mean that the type system gets in the way here, or the same would apply to even dynamically typed functional languages?
I'm trying to understand if you think a static type system is the hindrance here, or the language being functional in paradigm.
This is quite different than object oriented or even compiled imperative languages where type system constructs contain data and function units that represent internalized constraints.
In functional programming you set up types and their constraints such that the business logic you require is a derivable consequence of the system and any other outcome is as close to provably impossible as can be. The less of this you choose to enforce, the more “imperativy” or “objecty” your use of that functional language is.
In imperative or OO languages you choose to make atomic units that have internal structure, but whose behavior across the interaction of the units is not itself a derivable consequence of any more abstract set of rules. Not even interfaces or templates represent this - they govern how components can communicate but do not limit what components can do.
You use the program’s structures to carry out the required business logic, but you cannot truly prove you haven’t allowed for some different logic or edge case instead.
In this sense, safety is a resource rather than a requirement, and if you learn that a previously unsafe operation is now safe, because the real world circumstances changed around you, you are free to have your program units do the previously-unsafe -now-safe thing without needing to remove expensive abstractions that had been set up to cause the previous notion of safety to be a consequence of your program.
If I understand you correctly now, a functional language with no real type system (and thus no such compiler enforced constraints) like Elixir would be imperative in your book and therefore more suitable to facilitate constantly changing business requirements.
Thanks for taking the time to detail your thoughts for me!
Within a few hours I rebuilt it from scratch.
That's when I realized the power of functional programming. There's no way I have been able to recover that quickly if I had used an imperative programming language.
Since that experience I've tried to follow a functional programming style whenever I can. I've realized that every variable is another state which adds to the complexity of the code.
Time is money.
How much time is wasted on debugging? Functional programming forces you to deal with your bugs at a much earlier point of development.
It also forces you to avoid some bad practices just to meet a deadline.
And since you're re-using so many component, you spend much less time with boiler plate code.
https://www.snoyman.com/blog/2019/11/boring-haskell-manifest...
If you need to temporarily bypass purity there's unsafePerformIO and friends (or they can occasionally be used permanently if you do a lot of analysis I believe).
It's a pretty practical language compared to its reputation.
They are lamenting that implementing a trivial sorting function in Haskell is difficult.
quicksort [] = []
quicksort (x:xs) =
let smallerSorted = quicksort [a | a <- xs, a <= x]
biggerSorted = quicksort [a | a <- xs, a > x]
in smallerSorted ++ [x] ++ biggerSortededit: also, how is the pivot element selected there?
Obviously picking first element as pivot makes for a very poor Quicksort. Though I presume a better approach, say median of first, mid and last elements, would add but an extra line?
It is only linear when you force it to evaluate, which ideally happens only once (amortized) in your program when the result is consumed. As long as you are careful, you don't get the bad quadratic performance that you'd get from something like eagerly appending a character to a string in a loop in Java. There are either gotchas, though, including relying on the compiler to do things in constant space that naively look linear, and also knowing when the compiler won't help you and you have to structure your computation manually.
My biggest problem with Haskell is how GC and lazy-evaluation makes it very difficult to reason about what the hardware is actually doing at a given point. I know there ways to inspect and control it, but I've found myself preferring languages that have simpler mental models.
Quicksort isn't really hard, you just do it in IO and it looks about like what you'd see in any imperative language. (or you can use ST, but then you have to know about ST).
For non-debug, you may be overestimating how useful it is to output from pure functions in production.
In general when you are in a situation where you need to change the Monad you're under, there's techniques and libraries for that (one way you define it once, give it a name, etc.).
The biggest pain tends to be going from pure to monadic in the first place, which last I used Haskell there wasn't much for but to just do it. There may be more tooling now, haven't seen.
As soon as you need to control the order of execution, memory usage, IO, or any combination of those, you are fighting the language and into territory of things you can technically do and out of the realm of things the language makes easy.
I'd say that it's one take on that paradigm, the Way of Monads is not the only way to do purely functional, Clean uses uniqueness typing instead. Though it otherwise has rather similar properties to Haskell.
Monad itself is never a silver bullet. Type systems and Composability are the true power of FP IMO.
This is no reason to avoid arguably the most popular (and state-of-the-art) function language and implementation out there.
Space leaks I'd like to read more about but writings about them are a bit hard to come by. Recommendations?
The language I recommend as a first step to most people is Elixir (or Erlang). It's fairly pure, data is immutable, relies on recursion at the lowest level etc. Good code in Elixir is structure in a similar to good code in a lot of other functional languages (and so teaches good habits), but being dynamically typed is avoids that immediate pain of learning about how to keep the compiler happy.
I read a lot of code and didn’t get bogged down reading explanations of what a monad is. I also avoided trying to understand too well what the evaluation semantics of the language were.
When I read code, I focused on looking at the type signatures and understanding what they meant, then I would stare at the code and try to work out in my head why those definitions would get those types.
I did this at a time when there were lots of “I wrote a program in Haskell. Let me explain how it works” blog posts on this site.
I defined some data structures and solved a bunch of project euler problems as practice
My experience is that people get scarred of so much new terms which get introduced in Haskell and that could feel overwhelming.
When beginning with Haskell, I would advice to just write code and try to intuitively understand bits, but not get down into unwrapping things or theory much. Stay high level and figure out how things interact as you would do in black box model. Don't open the box, but poke it and see what result you will get (in other words; just write code and do trial and error).
When you get comfortable with black-box learning then open the box and look for the details.
If you're interested in static functional programming, learn Elm and then F#/OCaml.
If you're interested in dynamically typed then learn Clojure or possibly Racket.
That's it. Spending time learning Haskell as your first (or even second) fp language is a terrible idea and has done more to slow FP adoption than anything.
You can learn Elm literally in a weekend it's so small and well organized (go to their site and follow the tutorial). The concepts you pick up will double the speed you learn any other static FP.
F# on .net core is cross platform and can run via a vs code plugin. Plus you have a batteries included full class library of practical needs via dot net.
Clojure is very productive, great concepts as well. A bit worse tooling / debuggingqgen you have to drop down to deal with java, but also had batteries included java libs.
Racket is probably the most isolated of the above. A scheme dialect and a bit closer to historical scheme and Lisp.
Don't learn Haskell it's a horrible use of time as a first language. You get 80% of the value in the above languages in somewhere between 1/10 to 1/4th the effort (depending on which lang you choose), and have learned a more practical language, and will actually learn that lang plus Haskell faster than if you had started in Haskell.
There is plenty of real-world Haskell code in production systems by industrial users.
The research is on practical applications.
SML by Dan Grossman is a great resource because it explains the concepts very well. Anything that you learn after will be easier, does not matter if you go for Haskell or F# later.
https://www.youtube.com/watch?v=a1fkhDjCHB8&list=PL-eVNDa9MN...
I'm 6 chapters into Haskell Programming from First Principles [0], and I highly recommend it.
Picking the point where I gave up, giving me a list of 7 vscode plugins, without any guidance as to which are more mature, doesnt feel like a thing anyone would wish to know before they start.
We've come a really long way in just the past 3 years with IDE stuff. I suggest you give it another try if you're still interested in diving into Haskell.
To me at least, “things I wish I knew” implies it’s some sort of companion to a real language guide/tutorial/book, not a replacement for one.
Take it from someone who spent 10 years on and off learning Haskell: It's a guide to navigating the boobytrapped minefield of the Haskell ecosystem, from the difficulties of installing it for the first time, to the confusing type theory.
Those aren't 7 plugins to choose from. Those are 7 plugins you should use all 7 of because there is no unified coherent Haskell IDE or plugin, but there are a dozen other broken ones that look good in the catalog.
Much the same with libraries. You need a guide to tell you which ones are usable and which are abandoned experiments, since both live side by side in the main repos.
It is under active development and is the closest you can get to a "unified coherent Haskell plugin" - it comes with all the features you would get from installing the 7 plugins individually.
Properly setting up HIE was a bit of a mission:
https://github.com/hmemcpy/haskell-hie-devcontainer
This should be the #1 recommendation to anyone looking to get started IMO, it cleanly sets everything up for you and extracts it into an isolated development container.
https://news.ycombinator.com/item?id=5546679 (2013)
https://news.ycombinator.com/item?id=6868303 (2013)
List all the programs useful for something other than programming a computer written in haskell:
Xmonad window manager Git annexe Pandoc
What else, let's get the full list. Exclude anything that we can't directly see or use.
I say, yes! Learn Haskell! Just don't expect to write any useful programs because they're pretty rare for people to have written when they've doesn't time learning Haskell.
It is great fun. Do it!
Contrast with the number of useful applications you can actually install and run that are for some purpose that is not programming.
In fact there are more Haskell textbooks you can read than haskell programs you can install (excluding programs for programming - because that's some pyramid scheme vibe there).
This tells us something, I didn't even suggest what it tells us. What it suggests to you is what you thought of.
And fwiw imho pandoc is extremely useful! And Haskell is great fun and worth your time to learn!
What have you written in haskell that we can use?
My three commercial Haskell projects are detailed here[0].
I would expect `isNotJust (Just foo)` to equal `False`, not `True`, and vice versa `isNotJust Nothing` to equal `True`.
The point has some validity (that you should try to encode data into the types if you can), but the example makes no sense in my opinion.
The improved way below where the Just a is unwrapped with a case allows the compiler to see whether or not x is valid.
> Is there anything wrong with the definitions and below
> and why is this not caught in the type system?
And
> ???
on the line you flagged as a bug.
Every single piece of hackneyed received wisdom and FUD in here can be easily countered if you talk to somebody with actual production Haskell experience. Speaking for myself:
- I run a startup that has production applications making money for 4 years now written entirely in Haskell with 6 devs doing nothing but Haskell (as well as engineers working in JS and our own language, Pact, yes, written in Haskell).
- When we need more bandwidth, we work with an all-Haskell consultancy that itself has no trouble finding work and is very successful in their own right.
- Before that, I built a group at a major bank writing Haskell code and getting it out in production, and outperforming Java apps.
- The tired "eww static types aren't for real business" is opinions masquerading as "facts". If you are a half-decent engineer in ANY language you can make your code refactorable for changing requirements. Whining that types makes that harder just shows your own limitations. Any decent programmer working in a strongly-typed system leverages types to make code _more_ refactorable in _less_ time with _fewer_ bugs. But hey these are just our anecdata too. Here's what I'm not doing: spreading FUD about Python or Clojure or JS, I'm too busy loving what I do.
- From a hiring perspective, Haskell programmers as a group offer an immense strategic advantage, and it's mainly _because_ they had to learn Haskell on the weekend and it wasn't handed to them. That shows passion and grit. I've built two teams from scratch now in totally different circumstances and it is simply breezy to find a wide variety of experience levels to craft a team from; they are by far the best teams I have ever worked with, with zero duds. The community is strong and excellent and helps each other out as far as job hunting goes.
Don't blame Haskell if it didn't stick, don't hate on Haskell if you find it intimidating or pointless. If anything, you should be grateful that there is a language that is actually different enough to attract a different kind of programmer, lord knows it's why I'm here: after having jobs in Java, C++, Perl, Ruby, JS, Visual Basic, Hypercard, you name it, it was nice to see that there's a different way to do things. It's really fun, there's always more to learn (not true of every language btw), and finally, it kicks serious ass on the performance side (as GC/runtime languages go).