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
andm
are bothNum
, they may not be the sameNum
. How can I specify this? If I can't figure it out withNum
, usingDouble
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 Float
s to Double
s 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
.
精彩评论