I'm playing around with an idea for an extensible serialization library. I have the following typeclass:
class Monoid m => BuilderS m a where
cstr :: String -> a -> m
The idea is that people can define instances for pairs of different serializers/types, like this:
import qualified Data.Serialize.Builder as B
instance Serialize a => BuilderS B.Builder a where
cstr _ = B.fromByteString . encode
And a usage example:
sfPut :: (BuilderS a b, Monoid a) => WriterT a Identity ()
sfPut = do
tell $ cstr "version" (5 :: Int)
-- ...
return ()
However, it turns out that the type of a
needs to be specialized:
Could not deduce (BuilderS a Int) arising from a use of `cstr'
from the context (BuilderS a b, Monoid a)
bound by the type signature for
sfPut :: (BuilderS a b, Monoid a) => WriterT a Identity ()
i.e, the following type signature works without problems:
sfPut :: WriterT B.Builder Identity ()
Is there an obvious way I'm missing to solve this?
Well, if I use the typeclass you've given, then use GHCi to check types:
So it looks like you need to specify that it's
BuilderS a Int
, notBuilderS a b
. If I haveIt works fine. Note that you need
FlexibleContexts
for this as well, and the type signature on5
is not optional.To further elaborate, the type signature you gave
sfPut
wasBut you had the term
tell $ cstr "version (5 :: Int)
, which has the typeThe type system couldn't unify
a
andInt
, so it gave you an error. Additionally, if you leave off the type signature from5 :: Int
, you'd instead haveBut since
a
doesn't appear inWriterT m Identity ()
, the type system doesn't know which instance ofNum
to use for5
and would give you another error. Specifically, you'd getHowever, if you use a monomorphic literal instead (or a value whose type is not polymorphic), then you can easily do