Search code examples
haskellservant

Looking for a better way to write a function that takes a type constructor as argument


I have a Haskell Servant Application. I want to read from a file and populate the database with the contents of the file. What I have is this

userList :: IO [User]
productList :: IO [Product]

data User = User { age :: Int, fname :: String, lname :: String }
data Product = Product { title :: String, description :: String }
data Item = UserI User | ProductI Product

listUsers :: Handler [Entity User]
listProducts :: Handler [Entity Product]

hydrateUserDB :: Handler [Entity User]
hydrateUserDB = do
    items <- liftIO userList
    let list = fmap User items
    traverse_ createUser list
    listUsers

hydrateProductDB :: Handler [Entity Product]
hydrateProductDB = do
    items <- liftIO productList
    let list = fmap Product items
    traverse_ createProduct list
    listProducts

Now I would like one function that can take either User or Product and yield the similar result as above. Something like:

hydrateDB :: Handler [Entity a]
hydrateDB =
    \alist con createItem listItems -> do
    items <- liftIO alist
    let list = fmap con items
    traverse_ createItem list
    listItems

Solution

  • This is perhaps a good use for typeclasses. Put the things that vary from one version to the other in a class. The design could probably be improved, but this is the first step:

    class DBItem a where
        itemList :: IO [a]
        createItem :: a -> Handler ()
        listItems :: Handler [Entity a]
    
    instance DBItems User where
        itemList = userList
        createItem = ...
        listItems = listUsers
    
    instance DBItems Product where
        itemList = productList
        ...
    
    hydrateDB :: (DBItem a) => Handler [Entity a]
    hydrateDB = do
        items <- liftIO itemList
        traverse_ createItem items
        listItems
    

    (I made a few changes to make the types make sense, but you get the idea)