InstanceSigs admit unintuitive generalization
I was asked to open an issue here on my confusion about InstanceSigs. While @rae assumes it is about documentation, I would actually prefer a change of the implementation in the spirit of #11674 (closed).
Motivation
Here is an example from #11674 (closed) that I (and perhaps also @Icelandjack) find confusing.
{-# LANGUAGE InstanceSigs #-}
data I a = MkI a
instance Functor I where
fmap :: (a -> b) -> f a -> f b
fmap = undefined
This example compiles as expected because according to the documentation
The type signature in the instance declaration must be more polymorphic than (or the same as) the one in the class declaration, instantiated with the instance type.
Let me quote @rae's explanation of a similar example verbatim with the identifiers and types adjusted to this example:
The instantiated type of
fmap
is(Functor I) => (a -> b) -> I a -> I b
. The given type offmap
(a -> b) -> f a -> f b
is more general than this instantiated type. (That is, if we havefmap :: (Functor I) => (a -> b) -> I a -> I b
andfmapI :: (a -> b) -> f a -> f b
, thenfmap = fmapI
type-checks.)
This matches @simonpj's explanation in #11674 (closed) where he points out that the definition is equivalent to
{-# LANGUAGE InstanceSigs #-}
data I a = MkI a
instance Functor I where
fmap :: (a -> b) -> I a -> I b -- this line was added by me not SPJ
fmap = fmapI
where
fmapI :: (a -> b) -> f a -> f b
fmapI = undefined
Both explanations make it obvious that InstanceSigs
actually do not specify the instantiated type of a class method, which I think is the usual intuition when using the extension. Rather InstanceSigs
appear to specify the type of an implicit helper variable (fmapI
in both examples above). This is unintuitive to me.
When I was teaching I sometimes observed that students are already confused by omitted constraints in class definitions, e.g. fmap :: (a -> b) -> f a -> f b
instead of the actual signature fmap :: Functor f => (a -> b) -> f a -> f b
. Writing more polymorphic instance signatures exacerbate the disconnect between the written signature and the actual (but implicit) signature.
While I believe that InstanceSigs
are very useful for documenting code and facilitating learning Haskell, I seriously doubt that 'more polymorphic' instance signatures are in any way more suitable to serve those purposes than 'exactly the same' instance signatures. Quite the contrary: I like InstanceSigs
because they save me the effort of looking up the method signature in the class definition. This becomes potentially misleading if one is allowed to generalize the written instance signature as the implementation admits it.
My rare encounter with more polymorphic instance signatures led to a brief confusion.
I consider myself a rather faithful reader of GHC's documentation of its language extensions. This helped me to resolve my confusion quickly. Alas not everyone is a precautious bookworm and using GHC's documentation is still not as inviting as it should be, e.g. googling InstanceSigs
gives this instead of this as the first result.
Anyway I believe this is not an issue of documentation simply because the current behaviour is unintuitive. A well documented unintuitive behaviour is still problematic in my opinion.
Proposal
I would prefer it if such 'more polymorphic' signatures were not allowed.
I am aware that such a change could break code. Hence I am not seriously proposing it. Instead my goal is to document my worries here.