Error binding type variables in instance of typecl

2019-08-05 19:25发布

问题:

I have a class "Shape" which should have "area" defined on all instances. area returns "Area b" (a data type) that contains a number (b belongs to Num typeclass) signifying the area of that Shape.

Haskell has problem binding that b to (x*y) where x and y are of type 'a' and 'a' is also of typeclass Num. How do I solve this ?? [If i replace (x*y) by 0, it works but doesn't work even with (0::Int)]

Code :

data Unit = Unit | Meter | CentiMeter               deriving Show

data Area a = Area a Unit                           deriving Show

class Shape a where
      area :: (Num b) => a -> Area b

data Rectangle side = Rectangle side side Unit  deriving Show

instance (Num a) => Shape (Rectangle a) where
     area (Rectangle x y unit) = Area (x*y) unit

Error :

[1 of 1] Compiling Main             ( y.hs, interpreted )

y.hs:11:46:
    Could not deduce (a ~ b)
    from the context (Num a)
      bound by the instance declaration at y.hs:10:10-39
    or from (Num b)
      bound by the type signature for
                 area :: Num b => Rectangle a -> Area b
      at y.hs:11:10-52
      `a' is a rigid type variable bound by
          the instance declaration at y.hs:10:15
      `b' is a rigid type variable bound by
          the type signature for area :: Num b => Rectangle a -> Area b
          at y.hs:11:10
    In the second argument of `(*)', namely `y'
    In the first argument of `Area', namely `(x * y)'
    In the expression: Area (x * y) unit
Failed, modules loaded: none.

回答1:

The problem here is area's type signature:

area :: (Num b) => a -> Area b

What it says is "give me an a, and I'll give you an Area b for any b you want; you can pick". So, for instance, I could give area an Integer and expect back an Area Double. Clearly, this isn't what you want!

In this case, the error arises because you use x*y, which has type a, when b is expected — you have to give a value that works for any numeric type b, but you're giving a value that only works for one (a).

If you changed area's type to a -> Area Integer, or such, then it would work. However, I have a feeling you want the instance to be able to specify what the type of the area is. For this, you'll need to use a language extension called type families:

{-# LANGUAGE TypeFamilies #-}

class (Num (AreaComponent a)) => Shape a where
    type AreaComponent a
    area :: a -> Area (AreaComponent a)

instance (Num a) => Shape (Rectangle a) where
    type AreaComponent (Rectangle a) = a
    area (Rectangle x y unit) = Area (x*y) unit

This says that for every type a that's an instance of Shape, there is an associated type AreaComponent a, representing the type of each component of its area. That type is required to be an instance of Num by the definition of Shape.

Another thing you could do, if all your shapes take a numeric type parameter, is to make the instances be for the type constructors of each shape, rather than the full shape types themselves:

class Shape sh where
    area :: (Num a) => sh a -> Area a

instance Shape Rectangle where
    area (Rectangle x y unit) = Area (x*y) unit


回答2:

The problem is that

class Shape a where
      area :: (Num b) => a -> Area b

promises to be able to deliver any Num type that the caller might want, but your implementation just provides some Num type the callee provides.

The only way to produce numbers of any desired type is to use fromInteger (or literals, wher fromInteger is implicit).

To be able to deliver some Num type determined by the type of thing whose area is to be computed, you can use multi parameter type classes and functional dependencies or type families, e.g.

{-# LANGUAGE TypeFamilies #-}

class Shape a where
    type NumType a :: *
    area :: a -> Area (NumType a)

instance (Num side) => Shape (Rectangle side) where
    type NumType (Rectangle side) = side
    area (Rectangle w h u) = Area (w*h) u