Usually I reckon type-families
are similarly expressive as compared with typeclasses
/instances -- the difference is awkwardness/ergonomics of the code. In this case I have code working with type-families
to raise a constraint
, but the equivalent typeclass
code won't compile. (* Could not deduce (Eq a) ...
when (Eq a)
is exactly the constraint I'm supplying.) Is this a case typeclasses
just can't express, or am I doing something wrong?
data Set a = NilSet | ConsSet a (Set a) deriving (Eq, Show, Read)
-- fmap over a Set, squishing out duplicates
fmapSet :: (Eq a, Eq b ) => (a -> b) -> Set a -> Set b
fmapSet f NilSet = NilSet
fmapSet f (ConsSet x xs) = uqCons (f x) (fmapSet f xs)
uqCons fx fxs | sElem fx fxs = fxs
| otherwise = ConsSet fx fxs
sElem fx NilSet = False
sElem fx (ConsSet fy fys) = fx == fy || sElem fx fys
I want to call that fmap
via a Functor
-like class, with a constraint that the data-structure is well-formed. Either of these approaches with type-families
work (based on this answer, but preferring a standalone family).
{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
import Data.Kind (Type, Constraint)
type family WFTF (f :: * -> *) a :: Constraint
type instance WFTF Set a = Eq a
class WFTFFunctor f where
wftFfmap :: (WFTF f a, WFTF f b) => (a -> b) -> f a -> f b
instance WFTFFunctor Set where
wftFfmap = fmapSet
type family WFTF2 c_a :: Constraint
type instance WFTF2 (Set a) = Eq a
class WFTF2Functor f where
wftF2fmap :: (WFTF2 (f a), WFTF2 (f b)) => (a -> b) -> f a -> f b
instance WFTF2Functor Set where
wftF2fmap = fmapSet
The equivalent (I think) typeclass
at least compiles providing I don't give an implementation for the method:
class WFT c_a where
instance Eq a => WFT (Set a)
class WFTFunctor f where
wftfmap :: (WFT (f a), WFT (f b)) => (a -> b) -> f a -> f b
instance WFTFunctor Set where wftfmap f xss = undefined -- s/b fmapSet f xss
Inferred :t (\ f (xss :: Set a) -> wftfmap f xss) :: (Eq a, Eq b) => (a -> b) -> Set a -> Set b
-- which is exactly the type of fmapSet
. But if I put that call to fmapSet f xss
in place of undefined
, rejected:
* Could not deduce (Eq a) arising from a use of `fmapSet'
from the context: (WFT (Set a), WFT (Set b))
bound by the type signature for:
wftfmap :: forall a b.
(WFT (Set a), WFT (Set b)) =>
(a -> b) -> Set a -> Set b
at ...
Possible fix:
add (Eq a) to the context of
the type signature for:
wftfmap :: forall a b.
(WFT (Set a), WFT (Set b)) =>
(a -> b) -> Set a -> Set b
WFT (Set a)
implies raises Wanted (Eq a)
, so I shouldn't need to add it. (And if I do via InstanceSignatures
, rejected because it's not as general as the inferred constraint.) [In response to @dfeuer's answer/my comment] True that there's nothing in the instance decl to Satisfy (Eq a)
, but fmapSet
's sig also Wants (Eq a)
so (why) doesn't that ensure the constraint gets satisfied at the call site?
I've tried decorating everything with ScopedTypeVariables/PatternSignatures
to make the constraints more explicit. I've tried switching on ImpredicativeTypes
(GHC 8.10.2). I sometimes get different rejection messages, but nothing that compiles.
If I take away the WFT (Set a)
and (Eq a)
from fmapSet
's signature, I get a similar rejection * Could not deduce (Eq b) ...
. Yes I know that rejection message is a FAQ. In the q's I've looked through, the constraint is indeed unsatisfiable. But then in this case
a) why does the version with implementation undefined
typecheck;
b) isn't the constraint wanted from WFT (Set a)
getting satisfied
by fmapSet
having the (Eq a)
?)
Addit: To explain a bit more about what I'm expecting in terms of Wanted/Satisfied constraints:
There's no signature given for uqCons
, nor for sElem
, which it calls. In sElem
there's a call to (==)
, that raises Wanted (Eq b)
in sElem
's sig, which gets passed as a Wanted in the sig for uqCons
, which gets passed as a Wanted in the sig for fmapSet
, which does have a sig given including (Eq b)
.
Similarly the Set
instance for method wftfmap
raises Wanted (Eq a, Eq b)
; I expect it can use that to Satisfy the Wanted arising from the call to fmapSet
.
WFT (Set a)
impliesEq a
for everya
. In particular, I could write e.g.instance WFT (Set (Int -> Int))
for which this implication is false. For the type families version, I cannot writetype instance WFTF2 (Set (Int -> Int)) = ()
because open type families may not overlap, whereas type class instances may overlap. – user2407038forall a . WFTF2 (Set a) => Dict (Eq a)
is precisely the same asforall a . Eq a => Dict (Eq a)
(you can see this in GHCI, try e.g.:kind! forall a . WFTF2 (Set a) => Dict (Eq a)
). In that case, your Wanted constraint isEq a
and your Have constraint isEq a
- this case is trivial. For the type class version, you have thatWFT (Set a) => Dict (Eq a)
does not compute; your Wanted isEq a
but you have onlyWFT (Set a)
- these are unrelated constraints and your Wanted is not satisfied. – user2407038instance WFT (Set (Int -> Int))
would be an overlapping instance, so if I switch off that extension and-XNoFlexibleInstances
(or run in H2010 mode), will the compiler commit toinstance WFT (Set a)
so that it can uncover the(Eq a)
? It seems not to -- but that may be because GHC is not really H2010-compliant. – AntC