4 ms·
I work full-time as a Haskell programmer, I stream myself building various Haskell projects once a week at https://twitch.tv/agentultra https://twitch.tv/agentu
by agentultra 3y ago
I work full-time as a Haskell programmer, I stream myself building various Haskell projects once a week at https://twitch.tv/agentultra https://twitch.tv/agentultra, I maintain a couple of Haskell libraries, and I occasionally do other things too.
I've been a professional programmer for a little more than twenty years and have been using Haskell to this degree for about the last 4 or so. A good chunk of my career was dedicated to Python and C. I've done work in C++ and Javascript. Dabbled in Common Lisp in my spare time. Which is to say, I've ran the gamut of functional, imperative, object-oriented, etc.
I got started in OCaml first by taking the Inria course. I later tried Haskell by taking the University of Glasgow course. I often thought of myself as preferring FP but I always worked in languages with escape hatches where I could retreat to familiar territory. It was easy to rationalize this as, "using the right tool for the job." However thanks to some smart people in my life I decided to dip my toes in languages that were FP-first (OCaml) and later FP-only (Haskell).
> Is there a threshold after which Haskell's purely functional paradigm shines the most?
I think you just have to jump in the pool and go for it. There's no threshold. You can write scripts, programs, whole systems in Haskell. There's no point where, "it becomes worth it."
I'm certain at this point that you can write a relational database engine that is comparable in performance and features as any other in the market in Haskell. You can write video games in it. You can write short helper scripts and tools. Word editors, websites, compilers, etc.
The threshold is how willing you are to leave behind what you're already familiar with and learn a new way of programming that will make you feel like a complete beginner again for a while. Are you willing to re-learn programming over again? I don't know why this is surprising to people but if you started with a C-like language and picked up other C-like languages along the way, picking up Haskell isn't going to be as easy: there's nothing familiar you can use in your C-like knowledge that will be useful for a long while.
I've been doing it for a while now and there are benefits to it which is why I still program in Haskell and got a job writing code in Haskell. You likely already have a good deal of the tools needed to reason about how to design programs in a pure FP language: sets and high-school level algebra. You have notions built into the type system in Haskell like equality, relations, and constraints. Once you start jogging your brain on how to think this way you can design most of your program in types. And when you do it this way, there tends to be only one or a couple of ways to implement a program that satisfies those types... it's almost a mechanical process of filling in the holes.
From a practical perspective of a former Python programmer, I don't have to write a good deal of unit tests I would usually start with when building a Python program. Armed with a well-thought out handful of types the compiler will reject any program that isn't correct by construction which allows me to focus more of my efforts on things that are more important to me: is the logic correct, etc. I'm not testing, "are these things equal," "does this throw an expected error," "is this actually a valid function," etc.
Another weird thing that happens when you learn to program in Haskell is that control flow is kind of... represented in data types. Since Haskell is expression-oriented and execution is carried out by evaluation we get neat features like whole program analysis of all code branches. If I add a new constructor to a sum type, I get instance feedback on all the places where I need to now handle that case. This is super useful.
However... it can be a challenge getting to this place where you're comfortable with it. I had a hard time when I started a small Haskell project early on to make a simple web app. I was so used to logging being a single import away that having to learn all these new concepts in Haskell like, "monad transformers," just to add logging to my app seemed like a bunch of busy work for something that was, "solved," in my mind. The key to getting through moments like that is to forget your past experiences and just open your mind to being a beginner again.
It becomes useful later on when you realize that composition is so incredibly useful. You start to miss it when you go back to procedural languages and there are no guarantees and nothing composes.
- xrcyz 3y ago> control flow is kind of... represented in data types This sounds really interesting. Is there a toy example that comes to mind that you could share?
- agentultra 3y agoSure, a trivial one is: `Either Int Char` Example values of this type are: Left 3, Right 'a', Left -99, Right 'X'. When we pattern match on a value of this `Either Int Char` type we have two branches to handle: ``` case myValue of Left someInt -> "It's an integer: " ++ show someInt Right someChar -> "It's a char: " ++ [someChar] ``` You can nest them if you need more branches: `Either Int (Either Char String)` and you can match out the different branches. Although people don't usually write code this way in Haskell: we usually think of better types that model what we're trying to do. This is such a common way of doing things. There's a great function called, `compare` that returns a value of type `Ordering` which can be one of: LT, EQ, GT for each of the cases when comparing things with the usual comparison operators. A lot of Haskell is variations on pattern matching: guards, if-expressions, etc. Where this becomes even more useful is when you start to use types to enforce pre-conditions. I don't know how many times I've seen code in C++ or Python or Javascript where you have to validate the inputs to a function: make sure the list isn't empty otherwise throw an error! In Haskell we can prevent that possibility and eliminate the need for a useless check by writing a type for a non-empty list and use that in our function's signature (note: you can do this in C++ and Javascript too, it's a good idea). Or you can write your type so that it fails in some way if the inputs are not right: use `Either SomeError Int`. Then your callers have to handle the error case and the happy case. There are such things as "exceptions" in Haskell but they're limited (mostly) to things that happen in `IO` returning functions. There are ways to pick apart the seams of the GHC runtime and do nefarious things that would throw exceptions that would break the usual rules (eg: https://hackage.haskell.org/package/bytestring-0.10.8.1/docs/src/Data-ByteString-Internal.html#accursedUnutterablePerformIO https://hackage.haskell.org/package/bytestring-0.10.8.1/docs...). However it's incredibly rare and sticks out like a sore thumb that it forces you to give such code extra scrutiny. Exceptions aren't used for control flow in the same way that they are in Python, say. Handling them though is possible and a whole other topic. Let's just say it's a bit tricky to get used to... though Haskell has some good facilities for properly handling async/threads and exceptions so that resources are cleaned up and handled properly.