问题
I have a multiparam typeclass which supplies a function that makes sense to have its arguments swapped:
class Swappable a b where
swappable :: a -> b -> Bool
So if a
and b
form Swappable a b
, then b
and a
should form Swappable b a
. Writing a swapped instance for each normal instance would be a chore, so I naively wrote
instance Swappable a b => Swappable b a where
swappable b a = swappable a b
Which doesn't compile with the following error:
• Illegal instance declaration for ‘Swappable b a’
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use FlexibleInstances if you want to disable this.)
• In the instance declaration for ‘Swappable b a’
|
12 | instance Swappable a b => Swappable b a where
| ^^^^^^^^^^^^^
Now, I'm not really averse to turning on FlexibleInstances
, but I don't understand why I need it in the first place. All type variables there appear once, and are all distinct. So why do I get this error?
回答1:
All instance types must be of the form (T a1 ... an)
Means that your instance must be of the form
instance Swappable (T a1 .. an) (U b1 .. bn) where ...
where T
and U
are type constructors. Without that extension, you can not have only single variables a
and b
without a constructor on top.
FlexibleInstances
is harmless, anyway, and should arguably turned on by default. Maybe a future revision of the Haskell Report will include it.
I would be far more concerned by the overlapping, instead. instance Swappable b a => Swappable a b
will overlap with any other instance. It will also require undecidable instances. I'm not sure about what you are trying to achieve is a good idea.
回答2:
In this case, you can write the following:
{-# LANGAUGE UndecidableSuperClasses #-} -- 2
class Swappable b a => Swappable a b where -- 1
swappable :: a -> b -> Bool
swappable = flip swappable -- 3
(1) makes it so that, if you know that a
and b
are Swappable
, then you also know that b
and a
are Swappable
. That is,
swappable' :: Swappable a b => b -> a -> Bool
swappable' = swappable
compiles, even though the argument order seems "wrong", because the Swappable a b
constraint itself implies Swappable b a
, and it is that implementation that the swappable
call chooses.
(2) makes GHC stop complaining about the recursive superclass on Swappable
. This is a recent feature, introduced in GHC 8.0.1. Note that we don't need FlexibleInstances
(though it's harmless) or UndecidableInstances
(which is mostly OK) or OverlappingInstances
(which sets the klaxons blaring). UndecidableSuperClasses
probably falls in between the first two on the "danger" scale.
(3) is the default implementation of swappable
. You can make that whatever you like. Usage of this class proceeds as follows:
instance Swappable Bool Int where
swappable _ 0 = False
swappable True n = n > 0
swappable False n = n < 0
instance Swappable Int Bool where -- program does not compile if this is missing
instance Eq a => Swappable a a where swappable = (==)
The Swappable Int Bool
class has, as superclass, Swappable Bool Int
, so its swappable
can be defined in terms of that superclass. Swappable Bool Int
contains the logic. If one of the instances were missing, then the other instance's superclass constraint would be unfulfilled, so the program wouldn't compile. In the Swappable a a
case, the instance is its own "superinstance". (It does require FlexibleInstances
, but the reason is orthogonal to our purposes.)
These all work:
> swappable 5 5 -- defaulted to Integer, mind you
True
> swappable True (5 :: Int)
True
> swappable (5 :: Int) False
False
> swappable' (0 :: Int) True
False
来源:https://stackoverflow.com/questions/51221528/haskell-flip-arguments-of-a-typeclass-with-two-parameters