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:
> :t tell $ cstr "version" (5 :: Int)
tell $ cstr "version" (5 :: Int) :: (MonadWriter s m, BuilderS s Int) => m ()
So it looks like you need to specify that it's BuilderS a Int
, not BuilderS a b
. If I have
sfPut :: BuilderS a Int => WriterT a Identity ()
sfPut = tell $ cstr "version" (5 :: Int)
It works fine. Note that you need FlexibleContexts
for this as well, and the type signature on 5
is not optional.
To further elaborate, the type signature you gave sfPut
was
BuilderS m a => WriterT m Identity ()
But you had the term tell $ cstr "version (5 :: Int)
, which has the type
BuilderS m Int => WriterT m Identity ()
The type system couldn't unify a
and Int
, so it gave you an error. Additionally, if you leave off the type signature from 5 :: Int
, you'd instead have
tell $ cstr "version" 5 :: (Num a, BuilderS m a) => WriterT m Identity ()
But since a
doesn't appear in WriterT m Identity ()
, the type system doesn't know which instance of Num
to use for 5
and would give you another error. Specifically, you'd get
> let sfPut = tell $ cstr "version" 5
Could not deduce (BuilderS s a0)
arising from the ambiguity check for ‘sfPut’
from the context (BuilderS s a, MonadWriter s m, Num a)
bound by the inferred type for ‘sfPut’:
(BuilderS s a, MonadWriter s m, Num a) => m ()
at <interactive>:20:5-35
The type variable ‘a0’ is ambiguous
When checking that ‘sfPut’
has the inferred type ‘forall (m :: * -> *) s a.
(BuilderS s a, MonadWriter s m, Num a) =>
m ()’
Probable cause: the inferred type is ambiguous
However, if you use a monomorphic literal instead (or a value whose type is not polymorphic), then you can easily do
> let sfPut = tell $ cstr "version" "I'm not polymorphic"
> :t sfPut
sfPut :: (BuilderS s [Char], MonadWriter s m) => m ()