
Hi Konrad,
I am a Haskell newbie working on my first serious test case, and I would like some feedback from the experts to make sure I am not doing anything stupid ;-)
Well, I may not exactly qualify, but I can give you a few suggestions, nonetheless...
data Floating a => Vector a = Vector !a !a !a deriving (Eq, Show)
Here. Do not use 'data C x =>'. It's essentially a useless part of the language that really should have gone away. What you want is just: | data Vector a = Vector !a !a !a deriving (Eq, Show) The Floating constraint can come in on the functions. (I say this feature is useless because it only does one thing: disallows you to construct Vectors whose elements aren't instances of Floating. However, the useful thing that it might do -- namely, allow you to *use* the fact that the elements are instances of Floating -- it doesn't.)
I would like to keep the numerical representation as general as possible, in particular to postpone decisions about precision (Float, Double, eventually arbitrary precision arithmetic) as much as possible. Therefore the "Floating a" constraint. Which leads to my first question: doing it the way I did, I find myself having to add a "Floating a" constraint to almost every function specification with a vector argument. I would prefer to specify once and for all that vector elements need to be "Floating", everywhere. Is there a way of doing that?
Not really.
-- Periodic universe data Floating a => OrthorhombicUniverse a = OrthorhombicUniverse a a a
Again, get rid of the 'Floating a =>' part.
instance Floating a => Universe (OrthorhombicUniverse a) where distanceVector (OrthorhombicUniverse a b c) (Vector x1 y1 z1) (Vector x2 y2 z2) = Vector (fmod (x2-x1) a) (fmod (y2-y1) b) (fmod (z2-z1) c) where fmod x y = x - y*truncate (x/y)
There are actually two problems. First, you want to write fmod as: | fmod x y = x - y * fromInteger (truncate (x/y)) otherwise the type is wrong. Secondly, since you're using truncate, you need to be in RealFrac, not in Floating. Changing the signature to: | instance RealFrac a => ... fixes this problem. However, once we fix this, we can see the real problem. Your Universe class has a method, distanceVector, of type: | distanceVector :: Universe u, Floating a => u -> Vector a -> Vector a -> Vector a And here's the problem. When 'u' is 'OrthorhombicUniverse x', it doesn't know that this 'x' is supposed to be the same as the 'a'. One way to fix this is to parameterize the Universe data type on the element, something like: class Universe u where distanceVector :: (RealFrac a, Floating a) => u a -> (Vector a) -> (Vector a) -> (Vector a) distance :: (RealFrac a, Floating a) => u a -> (Vector a) -> (Vector a) -> a distance u v1 v2 = norm (distanceVector u v1 v2) -- Infinite universe data InfiniteUniverse a = InfiniteUniverse instance Universe InfiniteUniverse where distanceVector u v1 v2 = v2 <-> v1 -- Periodic universe data OrthorhombicUniverse a = OrthorhombicUniverse a a a instance Universe OrthorhombicUniverse where distanceVector (OrthorhombicUniverse a b c) (Vector x1 y1 z1) (Vector x2 y2 z2) = Vector (fmod (x2-x1) a) (fmod (y2-y1) b) (fmod (z2-z1) c) where fmod x y = x - y*fromInteger (truncate (x/y)) This is beginning to get a little ugly, but I think that's largely because you're using type classes in too much of an OO style (this is what a lot of my first programs looked like and I think for reasons similar to this, I moved away from that). However, not knowing more about the domain of discourse, I don't know what to suggest. Perhaps using the module system would serve you better. Instead of having a universe class, simply put each universe in a separate module and provide the same functions in each. Then you just import them selectively, or qualified. HTH. - Hal