开发者

How to match rigid types in a type class instance?

I thought I would try modeling some numerical integration on vector quantities of different dimensionality, and figured that type classes were the way to go. I needed something to define the difference between two values and to scale it by a multiplier (to get the derivative), as well as being able to take the distance function.

So far I have:

class Integratable a where
    difference :: a -> a -> a
    scale :: Num b => a -> b -> a
    distance :: Num b => a -> a -> b

data Num a => Vector a = Vector1D a | Vector2D a a

instance Num a => Integratable (Vector a) where
    difference (Vector1D x1) (Vector1D x2) = Vector1D (x1 - x2)
    scale (Vector1D x) m = Vector1D (x * m)
    distance (Vector1D x1) (Vector1D x2) = x1 - x2
    difference (Vector2D x1 y1) (Vector2D x2 y2) = Vector2D (x1 - x2) (y1 - y2)
    scale (Vector2D x y) m = Vector2D (x * m) (y * m)
    distance (Vector2D x1 y1) (Vector2D x2 y2) = sqrt((x1-x2)*(x1-x2)
                                                      + (y1-y2)*(y1-y2))

Unfortunately there are a couple of problems here that I haven't figured out how to resolve. Firstly, the scale function gives errors. GHC can't tell that m and x are compatible since the rigid type restriction Num is give开发者_如何学编程n in the instance in one case, and in the Vector type in the other case... Is there a way to specify that x and m are the same type?

(I realize in fact that even if x and m are both Num, they may not be the same Num. How can I specify this? If I can't figure it out with Num, using Double would be fine, but I'd rather keep it general.)

There's a similar problem with distance. Attempting to specify that the return type is Num fails, since it can't tell in the instance definition that a is going to contain values that are compatible with b.


EDIT: It seems to me now that the article on functional dependencies from the HaskellWiki provides the key information in the best form that I can find, so I'd suggest reading that instead of my answer here. I'm not removing the rest of the content, though, as it makes clear (I hope) why FDs are useful here.


Apart from the grouping of definitions issue which Dave pointed out...

(I realize in fact that even if x and m are both Num, they may not be the same Num. How can I specify this? If I can't figure it out with Num, using Double would be fine, but I'd rather keep it general.)

This is the main problem, actually. You can't multiply an Integer by a Float, say. In effect, you need the x and the m in scale to be of the same type.

Also, a similar issue arises with distance, with the additional complication that sqrt needs a Floating argument. So I guess you'd need to mention that somewhere too. (Most likely on the instance, I guess).

EDIT: OK, since sqrt only works on Floating values, you could roll a typeclass for those to upcast Floats to Doubles when needed.

Another idea involves having a typeclass Scalable:

data Vector a = Vector1D a | Vector2D a a deriving (Show)                       

class Scalable a b | a -> b where
  scale :: a -> b -> a

instance (Num a) => Scalable (Vector a) a where
  scale (Vector1D x)   m = (Vector1D (x * m))
  scale (Vector2D x y) m = (Vector2D (x * m) (y * m))

This uses a so-called functional dependency in the definition of Scalable. In fact, trying to remember the syntax for that, I found this link... So I guess you should disregard my inferior attempt at being helpful and read the quality info there. ;-)

I think you should be able to use this to solve your original problem.


To fix the second error, I think you need to reorder your definitions in the instance declaration. First have the two equations for difference, then the equations for scale, then both for distance.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜