Home

Freeing GADTs

Our usual Free monad requires a bona fide Functor. But sometimes, we deal with types that are a bit funnier but would otherwise love to get the usual Free monad goodness. Let’s take a silly example, with a GADT for describing requests/responses against a service.

import Control.Monad.Free

type UserId = Int
type User = String

data Req a where
  GetUsers :: Req [User]
  AddUser :: User -> Req UserId
  GetUser :: UserId -> Req (Maybe User)

I would like to be able to chain a bunch of such “abstract requests” in a do block, and only later decide to map those operations to actual network operations or random data generation for testing.

We can introduce a “helper” type which will be a functor, and allow us to achieve our goal.

data ReqF r = forall t. ReqF (Req t) (t -> r)
instance Functor ReqF where
  fmap f (ReqF req k) = ReqF req (f . k)

type ReqM = Free ReqF

getUsers :: ReqM [User]
getUsers = liftF (Req GetUsers id)

addUser :: User -> ReqM UserId
addUser u = liftF $ Req (AddUser u) id

getUser :: UserId -> ReqM User
getUser uid = liftF $ Req (GetUser uid) id

ReqF pairs up a request and “what we would do if we had the result”, all strongly typed. ReqF’s Functor instance piles up more work in the continuation. The ReqM monad as defined above therefore lets us shove a bunch requests in a do block as intended, without them even having a concrete meaning yet.

sane :: ReqM Bool
sane = do
    uid1 <- addUser "xyz"
    xs <- getUsers
    u1 <- getUser uid1
    return $ ("xyz" `elem` xs) && (u1 == "xyz")

Posted: