With InstanceSignatures
, I can give a signature for a method within an instance decl.
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.
I can see from this answer that the type part of the sig can't be more specific; but why couldn't you add extra constraints? After all you can put constraints on the instance decl that will make the instance more specific than "the class declaration, instantiated with the instance type".
Addit: (In response to the first couple of comments.) To explain "you can put constraints on the instance decl ... instance more specific": the OVERLAPPABLE
instance head here alleges it provides addNat
for all types a, b, c
. But it doesn't: it only provides if a
is of the form SNat a'
, c
is of the form SNat c'
, etc. I'm asking why I can't similarly restrict the types at the method level?
For example [adapted from Hughes 1999] (more realistically this could be a BST/Rose tree, etc):
data AscList a = AscList [a] -- ascending list
instance Functor AscList where -- constructor class
fmap f (AscList xs) = AscList $ sort $ fmap f xs
-- :: Ord b => (a -> b) -> AscList a -> AscList b -- inferred
Without an instance signature GHC complains no instance for (Ord b)
. With the signature GHC complains the instantiated sig from the class is more polymorphic than
the one given.
This (excellent) answer explains the machinery in the instance dictionary. I can see the entry in the dictionary for the method has a type set from the number of constraints for the method (if any) in the class decl. There's no room to put an extra dictionary/parameter for the method.
That seems merely that the implementation didn't foresee a need. Is there a deeper/more theory-based reason against?
(BTW I'm not ever-so convinced by Hughes' approach: that would want both Ord a => WFT(AscList a)
and Ord b => WFT(AscList b)
. But there's no need to require the incoming (AscList a)
is ascending. Other constructor classes for AscList
perhaps don't need an Ord
constraint anywhere.)
AscList
's argument beOrd
. – bipllF
is a functor iff it providesfmap
for all typesa
andb
. So it's all-or-nothing, eitherF
is a functor or it is not (fmap
does not work in at least one case). This would be different if we had a functor class likeclass Functor' f a b where fmap' :: (a->b)->f a->f b
wherea,b
are indices of the class, hence can be constrained in instances. In the libraries there are some variants like this but they are not frequently used in practice. – chiFunctor
instance violates the lawfmap id == id
. (id (AscList [3,2,1]) == AscList [3,2,1]
, butfmap id (AscList [3,2,1]) == AscList [1,2,3]
.) (Though you can get away with it if you ensure that anAscList
value can only be created with a smart constructor prevents an unsorted list from being created in the first place.) – chepnerWFT (AscList a)
as proxy saying the incoming list is sorted. Then the law still holds. – AntCfmap (f.g) == fmap f . fmap g
. Iffmap g
reorders the list,f
is applied to different elements than iff.g
were used directly. – chepner