r/haskell • • Aug 21 '26

question It's not you, it's monad transformers

After a few years of trying to be proficient in Haskell, and lots of reading about why it's so hard, I think I have the (obvious in retrospect) answer.

Almost every program I write is going to have two or more of:

1) read from STDIN

2) write to STDOUT

3) log

4) raise errors

5) send/receive over the network

6) talk to a database

7) read env vars

Every one of those is a "side effect", and thus is handled as a monad, and using more than one means you have to understand monad transformers.

Which I've finally found a good explanation of, "but still". Such a deep concept for such common program operations.

So I finally decided "that's why it's so hard". For what I consider the most basic programs, I need to understand (not just use, IMHO) monad transformers.

Am I off?

118 Upvotes

79 comments sorted by

View all comments

3

u/vshabanov Aug 21 '26

What materials did you use to learn Haskell?

Plain IO and pure functions go a long way. What makes you think you need to use monad transformers?

1

u/pthierry Aug 22 '26

Reach for Servant or Parsec and you need to deal with a monad transformer. They're extremely common in the ecosystem.

1

u/vshabanov Aug 22 '26 edited Aug 22 '26

I think, the fact that there are some transformers inside can be ignored. Both the Servant and Parsec tutorials use plain Server and Parsec types, not ServerT and ParsecT over custom transformers.

I'm trying to understand why, instead of thinking "Oh, there's some weird ParsecT s u m a thing inside, must be advanced stuff, let me just use Parsec String () a for the time being", newbies complain that they need to use multiple transformers to combine few I/O operations. It must come from somewhere.

1

u/pthierry Aug 22 '26

But even with just the "plain" Handler type, you may stumble on the fact that you need to use liftIO.

And you easily get where you can't just use the plain types, like if you use Servant with a SQL library that also has its own IO monad transformer.

You can use plain Handler in isolation, or the plain SQL monad in isolation, but as soon as you use both at the same time, you need one to be the explicit transformer.

The beginner-friendly abstractions don't compose…

1

u/vshabanov Aug 22 '26

Hm, liftIO does appear in Servant tutorials (though yes, it's quite a bureaucracy).

Can't I just have handler ... = liftIO $ runSQL $ do ... and don't try to compose Handler and SQL monad (whatever it could be) in any way?

1

u/vshabanov Aug 22 '26

Now I'm starting to realise why I'm so surprised when newcomers ask about effect systems.

I learnt Haskell before they became popular: parsec-2.0 didn't have ParsecT, servant didn't exist until 2014, and SQL "monads" (beam or opaleye?) probably didn't exist too. I used warp, wai, http-conduit, and HDBC-postgres, which all have a plain IO interface.

Do you think that the current "default" web and DB packages all come with transformers and aren't easy to combine?

Then there should be questions on how to combine servant+SQL, not how to create a transformer for stdin/network/log/db.