Reputation: 2257
I am working on a webapp using Happstack and I am writing some code to store my types in MongoDB. I felt like shortening my code some by putting the code into a typeclass so that I could use the same code to read and write to the database for different types. Something like this:
class DatabaseType a where
toDoc :: a -> Document
fromDoc :: Document -> a
saveCollection :: Text
getFromDatabase :: (MonadIO m) => Pipe -> Text -> Value -> m a
getFromDatabase pipe field value = ...
...
Now the problem here is the saveCollection
, since it doesn't use any of the type variables GHC won't let it compile, however it is very important to the database functions (like getFromDatabase
) so that they know which collection to save to.
Question is, how to have a value in a typeclass that is not bonded by the type variables.
Upvotes: 2
Views: 356
Reputation: 48621
You have to add the type variable. The easiest way is to use a proxy:
saveCollection :: proxy a -> Text
-- Note the `proxy` is lower case
instance DatabaseType MyDB where
saveCollection _ = "MyDB"
Now to use it, you'll likely do this:
import Data.Proxy
foo = saveCollection (Proxy :: Proxy MyDB)
The reason for using lower case in the method declaration is convenience: you can use any value whose type has the right form instead of Proxy MyDB
, if you happen to have one on hand at the call site.
There are some situations where the standard proxy technique can lead to a problematic loss of sharing. This happens because results of function calls are not generally memoized. In this case, you can used a tagged type. Data.Tagged
defines
newtype Tagged s b = Tagged {unTagged :: b}
Tagged types are much more awkward to work with than proxies, unless you use partial type signatures or explicit type application, two of GHC's most recently added features. If you wanted, though, you could write
saveCollection :: Tagged a Text
Then in the instance,
saveCollection = Tagged "Hi there."
Using it directly would require something like
unTagged (saveCollection :: Tagged MyDB Text)
or, with partial type signatures,
unTagged (saveCollection :: Tagged MyDB _)
or with explicit type application I think something like
unTagged (saveCollection@MyDB)
Thanks to this awkwardness, the tagged
package offers functions for converting between proxy-based and tagged representations.
Upvotes: 5