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]
= liftF (Req GetUsers id)
getUsers
addUser :: User -> ReqM UserId
= liftF $ Req (AddUser u) id
addUser u
getUser :: UserId -> ReqM User
= liftF $ Req (GetUser uid) id getUser uid
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
= do
sane <- addUser "xyz"
uid1 <- getUsers
xs <- getUser uid1
u1 return $ ("xyz" `elem` xs) && (u1 == "xyz")
Posted: