My Thoughts on Haskell in 2020

박준규 @curry@hackers.pub
This article has been translated to English with the author's permission using ChatGPT.
- Author: Marco Sampellegrini
- Link: https://marcosampellegrini.com/thoughts-on-haskell-2020
My Thoughts on Haskell in 2020
In October 2019, I gave a talk titled "Stick to Simple Haskell." I put a lot of effort into preparing and writing that presentation. After the talk, quite a few people approached me to say they really enjoyed it. They weren't the usual suspects—those who would never give up on type-level programming. Rather, they were ordinary software developers like me and many others who simply wished their day-to-day development work could be a little less difficult.
I believe some of the ideas I discussed in that presentation deserve to be spread more widely. Or at least they need to be discussed more. Movements like the Boring Haskell Manifesto are a breath of fresh air. We need more of those.
This article is my small contribution.
30 Years Old
In 2020, Haskell celebrates its 30th anniversary. From the beginning, Haskell's language designers hoped it would be used for:
- Teaching functional programming
- Innovation and advancement in programming language research
- Building real applications and large-scale systems
Setting education aside for a moment, let's focus on the other two purposes.
Programming language researchers love Haskell because GHC can be easily extended through Extensions. GHC is the perfect playground for testing new ideas. These days, it's quite common to open a file and see a wall of extension listings:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
These extensions fundamentally change the language and type system. As a result, you end up dealing with something much more complex yet powerful. You move increasingly closer to dependent types. You can encode more information in your types, giving you more guarantees at compile time. We usually call these "fancy types."
Nevertheless, "the Haskell language itself" hasn't changed much since 1998. To put it very roughly and inaccurately, if you run GHC without enabling any extensions, you get something called Haskell98. In this case, you're working with simple data types, type classes, and a lazy, pure functional core.
Building Applications with Haskell
Programming language researchers go all-in on fancy types. But I'm not a PL researcher. I'm a software developer. As mentioned earlier, one of the Haskell designers' goals was to have Haskell used in industry for developing real applications. So is Haskell armed with fancy types suitable for real service development?
Looks Good on Paper
Real service development is completely different from writing papers.
Research papers, once published, don't need maintenance. The techniques and tricks shown in code samples in papers might look great on PDF, but their impact on an actual codebase is an entirely different matter. Researchers might rely on experimental features or unproven extensions. Slow compilation times aren't much of an issue either.
Inclusivity
Service development is teamwork.
I want to work with people from diverse backgrounds. I don't want to work in a team where a PhD is a prerequisite for handling Haskell. I want a codebase that's accessible to everyone. Most business logic isn't rocket science. There's no reason to make applications more complex than necessary.
From a more practical perspective, for companies investing in Haskell, lowering the barrier to entry opens up a larger talent pool.
Marginal Benefits
Fancy types provide more guarantees and allow you to write safer, more accurate code. So why give that up?
I believe the benefits aren't that dramatic. Moving from a conventional programming language to Haskell is already a big jump. Having a compiler help you write correct code is already a significant gain. Sure, using fancy types might make you a bit more precise. You might have x% more confidence. But is that complexity worth it?
From a realistic perspective, we need to recognize that the type system isn't everything. Types are incredibly powerful tools, but they're not the only tools we have. Having types doesn't mean you don't need tests.
Simple Haskell
I'm not suggesting we ignore everything that's happened since 1998.
Haskell98 is a good foundation. But it can be improved. The core idea is to accept the type system as it is and focus on ergonomics. Not making the type system itself more complex, but making the language easier to work with.
This ultimately comes down to leveraging generics and appropriately enabling user-friendly extensions.
Generics aren't in Haskell98. They're excellent for reducing boilerplate code. For example, with generic-lens, you can automatically derive JSON instances or lenses.
Some extensions don't cause harm and actually make Haskell much more ergonomic:
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
Application Architecture
Most applications can be written as:
app :: Env -> IO ()
Where Env
represents a dependency injection container:
data Env = Env
{ usersCache :: TVar [User]
, postgresConnection :: PG.Connection
, log :: Severity -> Text -> IO ()
, fetchUser :: UserId -> IO (Maybe User)
, storeFile :: Filename -> ByteString -> IO (Either Text ())
}
My criteria for determining what should be included in Env
are:
- Is the resource shared across the application? (e.g., Postgres connection)
- Do we need to provide different implementations? (e.g., mock implementations for testing)
If the type gets too large, it can be further broken down:
data UserService
= UserService
{ fetchUser :: UserId -> IO (Maybe User)
, updateUser :: User -> IO (Either UserServiceError ())
, deleteUser :: UserId -> IO (Either UserServiceError ())
}
data Env = Env
{ userService :: UserService
, ...
}
As the application grows, you can refactor using the ReaderT
design pattern.
Haskell in 2020
Despite trying to satisfy two different groups—programming language researchers and software developers—Haskell has been successful. Thirty years later, both groups are still using Haskell. But their needs couldn't be more different.
As software developers, we need to understand which Haskell features are useful for writing applications. More importantly, we need to know which features are harmful or ineffective. PL researchers and software developers share the same tools, but that doesn't mean we should blindly embrace fancy types.
As Haskell developers, we need to realize that not every problem needs to be a paper topic. The choice is:
- Spend 4 hours on a quest to find a beautiful solution at the type level,
- Or invest just 10 minutes to solve it in a boring but reliable way, and perhaps write some tests too?
It's okay to sacrifice a bit of type safety for inclusivity. Tests can sufficiently verify that software works correctly. This is the essence of Simple Haskell. Perhaps "Boring Haskell" is an even more appropriate term.
Writing simple and boring Haskell is enjoyable. There's a liberating feeling that comes with concrete code. Abstractions are good, but boring Haskell is better.
The next time a beginner asks you:
- Don't recommend Servant. Scotty works perfectly fine. Type-safe routing can come later.
- Don't recommend any effect system. Big abstractions can come later.
- Don't disparage Elm or Go. You won't achieve anything.
- Don't recommend Nix. Stack runs well enough, regardless of how many hours you spent getting
nix-build
to work the way you wanted. - Keep talking to them even if they don't have a PhD.
In 2020, let's come down from the ivory tower and write boring Haskell.
In an unexpected turn of events, just hours after this post was published, Matt Parsons released an excellent article advocating for "Junior Code." If you liked this post, I highly recommend reading it. If you didn't like this post, then it's even more essential reading.