tags:

views:

80

answers:

2

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 given 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.

A: 

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.

Dave Hinton
wasn't aware that the grouping of definitions makes a difference. in any case, it solves a problem with `conflicting definitions for difference`, but this isn't the main issue.. still don't know how to define `scale`.
Steve
sorry i edited out that second question, you stackoverflow guys are too fast.. ;)
Steve
+1  A: 

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.

Michał Marczyk
Indeed.. hadn't thought of that issue with `sqrt`. But I can't just put `scale :: a -> a -> a`, because `a` is a vector quantity, while `scale` scales a vector by a _scalar_ quantity.
Steve
Perhaps I need to define a `Scalar` that is somehow bound to the same type as the parameters to `Vector`? Not sure how to do that..
Steve
Ouch, right. I'll edit accordingly in a minute, though I think I might have another idea, so I'll try and include that in the edit.
Michał Marczyk
I think scale:: (Num a) => Vector1D a -> a -> Vector1D a would work but I don't have Haskell handy to check.
stonemetal
Your `Scalable` idea is pretty good, I'll have to give that a try, thanks.
Steve
Hope it's useful to you. Note that functional dependencies are a GHC extension, as are multi-parameter type classes and flexible instances. Use -XMultiParamTypeClasses -XFunctionalDependencies -XFlexibleInstances (or the corresponding pragmas, or -fglasgow-exts) to compile the Scalable code.
Michał Marczyk
Hm, I suddenly realise it wasn't a very useful comment, that previous one -- the GHC would tell you all that... Anyway: happy hacking! :-)
Michał Marczyk
Actually, you could have a typeclass for calculating the distance... Then you could use, say, 2D vectors of Integers with the Manhattan distance, if that was ever useful to you. In fact, make Vector a type class also, requiring Scalable a and Distant a (what do you mean Distant wouldn't be a good name?), with a (Num a) => Vector a instance... typeclass madness FTW! :-) (The most important issues are still ultimately solved with FunDeps in any case.)
Michał Marczyk
Thanks so much, I tried this out after work and functional dependencies do seem to be just the right way to go. I'm going to keep that trick in my back pocket for now as I don't really want to get into language extensions while I'm still learning basic Haskell, but I think it's definitely the right answer.
Steve
You're welcome. :-)
Michał Marczyk