The Hard Part of Learning Haskell
2025-01-25
I got inspired by a blog post The Hard Part of Learning a Language by Hillel Wayne. Now, obviously, one of my favourite programming languages is Haskell and I wanted to answer the questions raised on the blog post to my best effort. I’ve been using Haskell for personal projects and programming tasks for a bit over two years now, which probably doesn’t make me the most qualified person to do this, but I’ll give it a go. Rather than giving complete answers on all but the simplest cases, I’ll try to lead you to other resources. If you have any suggestions, please send them by email and I’ll have them added (accredited, of course).
How do you install Haskell?
- Preferred install method is via GHCup. Supported platforms are Windows, Linux, macOS, FreeBSD and WSL2. In addition to GHC (Glasgow Haskell compiler, the most common Haskell compiler) GHCup can install Stack and Cabal, which are programs for building and managing packages and programs and HLS (Haskell language server).
- It’s advised to not install GHC via your Linux distribution’s package manager since the repositories may have an outdated version of the compiler.
How are you supposed to write Haskell?
You may use your preferred text editor/IDE (remember to install HLS):
- VSCode has a really nice Haskell plugin
- NeoVim users should check out haskell-tools.nvim
- For Vim there’s coc.vim
- Emacs has lsp-haskell plugin
- IntelliJ people can use IntelliJ-Haskell
How do I do $COMMON_OPERATION in Haskell?
Some code examples to get you going.
- Read a file
- Haskell has multiple types for representing strings. You should read more about them from Monday Morning Haskell, since they have a few gotchas.
- Here are examples of reading file contents for each type:
Text
from text package (you most likely want to use this one)
import qualified Data.Text.IO as T
main = do
content <- T.readFile "/path/to/file"
T.putStr content
ByteString
from bytestring package
import qualified Data.ByteString as BS
main = do
content <- BS.readFile "/path/to/file"
BS.putStr content
String
from Prelude (this has worse performance than others)
main = do
content <- readFile "/path/to/file"
putStr content
- Print to console
print
to print a value of any type that implements theShow
typeclassputStrLn
andputStr
for printing string values.*Ln
adds a newline at the end of the string. Note thatByteString
s andText
have their own versions of these functions.
- Parse JSON
- Aeson is pretty much the standard for handling JSON (de)serialization
- There’s a great tutorial for Aeson on William Yaoh’s blog
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (encode, decode)
import GHC.Generics
data Format = Format
{ name :: String
, fileExtension :: String
} deriving (Show, Generic)
instance FromJSON Format
instance ToJSON Format
main = do
let (Just f) =
decode "{\"name\":\"JSON\",\"fileExtension\":\".json\"}"
print (f :: Format)
print $ encode f
- Datetime handling
time
library- Check out William Yaoh’s excellent cheatsheet
import Data.Time
import Data.Time.Format.ISO8601
main = do
now <- getCurrentTime
print now
past <- iso8601ParseM "2025-01-25T14:15:00.000Z" :: IO UTCTime
print past
print $ diffUTCTime past now
- Common datastructures (that are not part of the Prelude)
vector
for efficient resizeable listscontainers
has maps, sequences and sets, tree, and graphunordered-containers
has more efficient versions of the above if your use case doesn’t require ordering
- HTTP requests
- Standard solution seems to be
http-client
andhttp-client-tls
(for HTTPS requests) - A higher level interface that’s based on the above libraries is
wreq
- Standard solution seems to be
- Web server
What are the language quirks that will cost me an hour to discover?
- Non-total functions in Prelude, meaning you can call them with invalid arguments and they will throw an error
- lazy evaluation (
foldl
vsfoldl'
)
What are the things that are just different enough from what I’m used to that will confuse me?
- Immutability. Many other languages let you mutate variables, but Haskell is pure and immutable.
- Lazy evaluation. This can cause memory leak issues if you’re not careful.
- unary minus vs binary minus (you need to wrap the negative number in parentheses)
What parts of the core language should I avoid at all costs?
String
, although it’s sometimes required. Avoid where possible.- See relevant section from WIWINWLH
How to get help?
There’s active Haskell communities in the following mediums: Discord, IRC/Matrix, Reddit, Discourse
What resources to use for learning Haskell?
- Learn you a Haskell for Greater Good (LYAH)
- Haskell from First Principles (sometimes referred to as “the Haskell book”)
- What I Wish I Knew When Learning Haskell
- Effective Haskell
- Haskell wiki
How to debug?
Debug.Trace
-
“Is this one of those communities which think debuggers are for n00bs and you should write a lot of print statements?”
- I think people understand the usefulness of debuggers and have less judgemental views on using them as the before statement. But yes, print statements (and
Debug.Trace
) are the go-to way of debugging Haskell.
- I think people understand the usefulness of debuggers and have less judgemental views on using them as the before statement. But yes, print statements (and
- GHCi debugger exists, but it’s interface probably isn’t as fancy as you’ve used to.
Testing
- Is unit testing part of the core library, or do I need to install a third party framework?
- Property based testing
- QuickCheck is the canonical property based testing library, which has inspired other languages as well.
How do I build? How do I package? How do I manage my environment?
- Stack, Cabal - choose one and stick with it
- Managing your environment should be done with GHCup as mentioned earlier
- What are all the command flags I should be using that aren’t default for various reasons?
- Kowainik’s style guide has a good default set of GHC options
How am I supposed to “properly” organize my project files?
This is explained well by Steph Diehl.
So… the language community
What innocuous-seeming topics always turn into a flamewar? Is my question even safe to ask?
- People can be very opinionated on formatting styles of Haskell source code
- Using
cabal
vsstack
for package management - I’d expect the discussion to remain civil and constructive regardless of the topic!
Now they’re explaining something way above my head. Is this necessary to get, or are they just cursed with knowledge?
You’ll see a lot of math and category theory jargon (learning them is usually not essential, but might be interesting).
What opinions do they have on other programming languages? Are they going to look down on me for writing a lot of Python?
You won’t get looked down. The community is small and even slightest interest is usually very welcome.
Do they think I’m subhuman scum for using Windows?
No, although the experience on (native) Windows may be a bit rough, some libraries may not work etc. Generally Linux is more widely used in the community. WSL might work just fine, but I don’t have any Haskell development experience on Windows.
What are all these in-jokes supposed to be?
- Monads are burritos (originates from a couple of blog posts)
- Just a monoid in the category of endofunctors. What’s so hard about it? (origin explained + a monoid tutorial)
- Monad tutorials are everywhere
- Unofficial Haskell slogan “Avoid success at all costs”
Conclusion
My goal for writing this post was to get a better understanding of the Haskell ecosystem and community from a beginner’s perspective. I consider myself an intermediate Haskeller, but I’m continuously learning. I hope you found this post helpful!