Reputation: 1094
My code does not compile:
{-# LANGUAGE EmptyDataDecls, GADTs, RankNTypes #-}
import Data.Ratio
data Ellipsoid
data Halfplane
data PointSet a where
Halfplane :: RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Halfplane
Ellipsoid :: RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Ellipsoid
type TestFunc = RealFrac a => (a -> a -> a -> Bool)
ellipsoid :: PointSet Ellipsoid -> TestFunc
ellipsoid (Ellipsoid a b c f r) = f' where f' z y x = ((x/a)^2 + (y/b)^2 + (z/c)^2) `f` r
halfplane :: PointSet Halfplane -> TestFunc
halfplane (Halfplane a b c f t) = f' where f' z y x = (a*x + b*y + c*z) `f` t
The error I get is:
Could not deduce (a1 ~ a)
[...]
Expected type: a -> a -> a -> Bool
Actual type: a1 -> a1 -> a1 -> Bool
In the expression: f'
[...]
for both functions ellipsoid
and halfplane
.
I don't understand and am looking for an answer, why a
cannot be equated with a1
, both being RealFrac
, or even better: why two different types (a ~ a1
) are deduced?
Upvotes: 4
Views: 264
Reputation: 6778
Your use of GADT
with implicit forall
is causing you grief. This is a good time to mention the "existential quantification with typeclass" anti-pattern
Since you've included the constraint RealFrac a
in the definition of PointSet
you're implicitly using forall
like this:
data PointSet a where
Halfplane :: forall a. RealFrac a => a -> a -> a -> (a -> a -> Bool) -> a -> PointSet HalfPlane
Ellipsoid :: forall a. RealFrac a => ...
The same applies to TestFunc
:
type TestFunc = forall a. RealFrac a => a -> a -> a -> Bool
This is why GHC coerced you into adding the RankNTypes
extension.
Because of forall
the a
in the constructors for PointSet
cannot possibly unify with the a
in TestFunc
since the a
in PointSet
is some specific instance of RealFrac
, but TestFunc
is a function that is required to work for any a
.
This difference between the specific type a
and the universally quantified forall a. a
causes GHC to deduce two different types a
and a1
.
The solution? Scrap all this existential nonsense. Apply the typeclass constraints where and when they're needed:
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
data Shape = Halfplane | Ellipsoid -- promoted via DataKinds
data PointSet (s :: Shape) a where
Halfplane :: a -> a -> a -> (a -> a -> Bool) -> a -> PointSet Halfplane a
Ellipsoid :: ...
type TestFunc a = a -> a -> a -> Bool
ellipsoid :: RealFrac a => PointSet Ellipsoid a -> TestFunc a
ellipsoid (Ellipsoid a b c f r) = f' where f' = ...
Now PointSet
takes 2 parameters: a phantom type s :: Shape
which has kind Shape
(kinds are the types of types) and a
which is the same as your original example, except as an explicit argument to PointSet
it is no longer implicitly existentially quantified.
To address your last point, the a
and a1
in the error message are not "both being RealFrac
. RealFrac
is not a type, it is a typeclass. a
and a1
are two potentially distinct types that both happen to be instances of RealFrac
.
That said, unless you are making use of the more expressive type of PointSet
there is a much simpler solution.
data PointSet a
= Halfplane a a a (a -> a -> Bool) a
| Ellipsoid a a a (a -> a -> Bool) a
testFunc :: RealFrac a => PointSet a -> TestFunc a
testFunc (Ellipsoid a b c f r) = f' where f' = ...
testFunc (Halfplane a b c f t) = f' where f' = ...
Upvotes: 6