API constraints a'la carte in Haskell & PureScript
Tags: purescript, haskell
Published: 2019-05-08 21:41:31

---
title: API constraints a'la carte in Haskell & PureScript
published: true
description: A demonstration of conceptually lightweight constraints in Haskell & PureScript
tags: #purescript #haskell
---

The example in this post is 100% compatible in PureScript as well, with the exception of the effect monad which is called `Effect` in PureScript. Replacing `IO` with `Effect` is all you have to do, as well as use the libraries available there.

I think everyone who's in and around Haskell & PureScript hear some variant of  "Every program you make always ends up having a bunch of `IO ()` in it all the time anyway." and they're not necessarily wrong. The complaint is that you have this neat type system that should constrain your effects but all you get is `IO` or `Effect`, which just plain permits everything.

It's not untrue, but it's not exactly the full story either. Consider the following function:

```haskell
downloadFile' :: Link -> IO (Response ByteString)
downloadFile' (Link link) =
  Http.get link `Exception.catch`
  (\(HttpExceptionRequest req (StatusCodeException resp bytestring)) -> do
     case resp ^. responseStatus . statusCode of
       404 -> putStrLn $ "ERROR: File not found (404) for " <> link
       code ->
         putStrLn $ "ERROR: Unknown error with code " <> show code <> " for " <>
         link
     pure $ fmap (const (LBS.fromStrict bytestring)) resp)
```

We can of course see that we're trying to download a file. But there's a lot going on that doesn't really have anything to do with downloading files in here. In total, the effects we are dealing with:

- We're using HTTP functions: `Http.get`
- We're dealing with exceptions: `Exception.catch`
- We're printing to the terminal: `putStrLn`

How can we be clearer in our types about what we're doing in a lightweight way?

Well, let's make some *constraints*:

```haskell
class MonadTerminalIO m where
  putStrLnM :: String -> m ()

instance MonadTerminalIO IO where
  putStrLnM = putStrLn

class MonadHttp m where
  httpGetM :: String -> m (Response LBS.ByteString)

instance MonadHttp IO where
  httpGetM = Http.get
```

`Exception.catch` already has an associated type class/constraint, so we don't need to make that one.

We can now transform our function to the following:

```haskell
downloadFile ::
     (MonadHttp m, MonadTerminalIO m, Exception.MonadCatch m)
  => Link
  -> m (Response ByteString)
downloadFile (Link link) =
  httpGetM link `Exception.catch`
  (\(HttpExceptionRequest req (StatusCodeException resp bytestring)) -> do
     case resp ^. responseStatus . statusCode of
       404 -> putStrLnM $ "ERROR: File not found (404) for " <> link
       code ->
         putStrLnM $ "ERROR: Unknown error with code " <> show code <> " for " <>
         link
     pure $ fmap (const (LBS.fromStrict bytestring)) resp)
```

We're now being a lot clearer with what we're doing in our type signature and all it took was a couple of type classes and a *generic* return monad type.

Because we now return `m (Response ByteString)` and have a set of constraints on `m` instead, we've guaranteed that there can't be any random `IO` or other effects in our function, because those are in fact not valid for any `m` the type system can imagine. When we try to add something that can talk to the network, for example, it would have to have an associated constraint called `MonadNetwork`, for example, and we would have to add it to our constraints to make those functions available in that scope.

If we find ourselves wanting better type signatures, they could be just a few small constraints / type classes away. It's a very effective way to limit the capabilities of a function and be very clear about what's happening inside of it.

This is all possible because of type classes which serve as constraints on generic type variables, as well as higher-kinded types that allow us to talk generically about types wrapping types and together they form something I really like about Haskell & PureScript.