Haskell is an interesting programming language. It's fairly different than most mainstream languages and has all kinds of shiny features not present in other languages.
Some people say that that's enough reason to learn about it. "It will make you better at other languages" they say. But if I were to invest time and learn a new programming langauge, I'd like to be able to do things with it.
In this blog post I'd like to share a bit of my thoughts on why I think Haskell is a useful and practical language to get things done (and learn a lot on the way).
I will survey a few use cases where in my opinion Haskell works very well and provides a lot of value. So that when you consider learning Haskell, you'll have a better idea of what you can do with it.
I hope this won't come out as incoherent rambling that is but a lesser version of State of the Haskell Ecosystem.
But first, a preface about Haskell and functional style.
A Preface About Haskell and Functional Style
Haskell is a functional language. What this means is that it enables and rewards the programmer for using functional style. It is also possible to program in Haskell in fairly imperative style using syntactic sugar called do-notation, but functional style has other benefits.
Functional style focuses on data transformations instead of step-by-step instructions. A program written in functional style will generally take some initial data and pass it through a pipeline of functions that each will return a new and slightly modified data as a result. By the end of the pipeline it will return the result we wanted.
For example, in a game we might have some data that describes the current state of the game: where the characters are, what the map looks like, our HP, etc. In functional style we take that data and pass it through a function along with the user input and other events, and get the next state of the game. In my simple snake clone I have this update function:
update :: MyEvents -> GameState -> GameState update events state | sStatus state == Dead = state | otherwise = collide . updateFood . moveAndEat . changeDir events $ state
update, takes two arguments: the events and the game state,
and will either return the original state if the snake is dead,
or will run the state data through a pipeline of functions that will eventually
return a new state that can be displayed on screen.
Note that in this code we use
. to construct right-to-left a pipeline of functions,
and we use
$ to avoid adding a few parenthesis.
Haskell takes this style of programming a little bit further by also giving
us the ability to know and restrict the kind of effects that can happen
in a function. For example, it gives us some guarantees that the function
will not write the position of the next apple to the console and spoil
the thrill of seeing it for ourselves.
This is something that most (even functional) languages do not do.
So if your goal if to learn functional programming concepts, Haskell has a lot to offer with built-in support for immutability, controlling effects, easy access and transformation of nested immutable data, and more.
Now that we got that out of the way, let's talk about what Haskell has to offer to you as a programmer that wants to build useful software.
A Few Use Cases
But I will try to focus on specific cases where I think Haskell (and the haskell ecosystem) will work really well.
While I think that the nature of functional programming, in which we pass data into a function and get a new result, makes it easier to test, I'd actually like to talk about using Haskell to write tests instead. And not only for Haskell programs, but for testing other systems as well.
I like to use Haskell for writing tests because I find the syntax to be lightweight and declarative, the code easily changed and relocatable, and there are quite a few options of Haskell libraries for writing tests. Also, Haskell has a pretty good compiler and runtime system, so these can be pretty fast as well.
For example, HSpec can be used as a testing harness. It provides a mini language in Haskell for writing tests, automatic argument parsing for various options such as which tests to run or how many tests to run in parallel (and get a significant performance boost without doing anything on our end), and more.
Another useful way to do testing is propety-based testing with libraries such as QuickCheck and Hedgehog. The idea behind property-based testing is that instead of providing specific cases and expected results, we describe how to generate input data randomly and what kind of property we expect the output to hold.
For example, we could test a parser and prettyprinter by:
- Defining how to generate valid data randomly and then
- Check that if we render the data, parse the rendered data, and then render it again, we get the same output as just rendering the original data.
Haskell's ability to describe data in a concise way using algebraic data types and the compiler's ability to check that all cases are covered are especially valuable in this case. We can pretty much take an RFC spec and translate it in a straightforward manner to Haskell and generate tests for it.
Even if you think your current test coverage is good you should probably consider using property-based testing anyway.
You might also be interested in this book about property-based testing.
Asynchronous Programming and Single Machine Concurrency
Haskell has very good support for program concurrency. It provides lightweight threads support baked into GHC's runtime system, various libraries for concurrent programming and very good learning resources.
Asynchronous programming is made easy in Haskell by using libraries such as async and stm. The async library provides an API for executing and chaining asynchronous computations, while avoiding callback hell completely.
Using this API, running multiple asynchronous jobs to go fetch some pages from a url and then write them to a file would look like this:
pages <- mapConcurrently getURL urls let names = map getName pages writeFile "myurls.txt" (unlines names)
It also has good support for handling exceptions and cancelling asynchronous computations. As well as communicating between (green) threads using libraries such as stm.
stm (Software Transactional Memory) is a library providing mutable data structures and operations on these data structures that can be grouped together and performed as a single atomic operation, without the need to manually lock or unlock them.
This is done by having different thread compete on who manages to finish their transaction first. The first transaction that finishes is commited, and all other transactions restart and try again.
This works well in Haskell because Haskell gives us the power to limit the kind of effects we allow in functions, so code that runs inside a transaction cannot do arbitrary IO operations. Otherwise the compiler will not compile our code!
For example usage, say we are writing a chat server and we want to maintain the order
of messages between users, for each user we have a
TBQueue (transactional bounded queue) of messages and when a new messages arrives to the queue a dedicated thread will send that message to the user over the network.
When a user sends a message, we can just run this code:
sendMessageToUsers :: Message -> [User] -> IO () sendMessageToUsers message users = atomically $ for_ users $ \user -> writeTBQueue (msgQueue user) message
And we can be sure that all users will get the messages in the same order!
And if we just want to use the fairly standard
fork api of threads, Haskell has that too.
All of these solutions and much more are covered in the excellent book Parallel and Concurrent Programming in Haskell by Simon Marlow.
Haskell has a lot of libraries for composing functions that process data in a streaming fashion. From the streamly readme:
"Streams can be generated, merged, chained, mapped, zipped, and consumed concurrently – enabling a high level, declarative yet concurrent composition of programs."
Data flow programming seems like a good fit for functional programming to me. And the Haskell ecosystem has many solutions for this problem.
Command-line Programs and Scripting
Together with good streaming and concurrency support, writing command-line programs and scripts in Haskell can actually be fun.
Here are a few useful libraries to use for common cli needs.
For command line arguments parsing, I use optparse-generic and optparse-applicative. I usually reach for optparse-generic for my simple programs because all I have to do is define a few types and I get a nice command line interface automatically. When I need something a bit more complicated, optparse-applicative provides a nice and declarative interface to defining command line arguments parsers.
Running an external process is one function call away:
λ> callProcess "cal"  April 2020 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
For networking, there are mature libraries you can use. Such as http-client and wreq which are http clients, or you can use network for low-level networking interface. There's a new book discussing how to work with sockets and pipes in Haskell to build a web server named Sockets and Pipes.
If you want to write a build system, please take a look at Shake. It is a Haskell library (/EDSL) that can help you write powerful build systems that are fast, featureful and maintainable.
I'll let the Shake website do the talking.
Server-side Web Programming
Not my forte, but as I understand it Haskell has fairly decent facilities in this regard. There are quite a few web frameworks, some are lightweight, others more featureful and opinionated, most of them use the same underlying industrial-strength web server (warp).
There are also plenty of libraries to choose from for a variety of tasks, whether it's talking to a database, working with json, logging, html generation, websockets and more.
As far as I know, if you are using postgres as your database, you should be fine.
- Your First Web Application with Spock
- Your First Web App with WAI and Warp
- Your Second Haskell Web App—A Yesod Workshop
Compilers and Interpreters
Compilers and interpreters are a fairly common use case for Haskell due to its language features and ecosystem. I've given a talk about this in a local meetup and The slides are here.
Long story short, again Haskell's pattern matching and ADTs shine here, but also there are a lot of libraries for building languages, such as:
- Parsing libraries such as megaparsec
- Generic tree traversals such as recursion-schemes or uniplate
- Monad transformers for a cleaner interface to common needs (such as symbol tables with Reader, fresh variables with State, error reporting with Except, etc).
I hope you found this article useful and that it gave you a few ideas on what you could do with Haskell. If you liked this article or there are other similar articles that you know of for other languages, please let me know via email or twitter.
If you wish to learn more about Haskell, consider checking out my haskell-study-plan!