Type design question
Hal Daume
t-hald@microsoft.com
Fri, 25 Jul 2003 08:31:26 -0700
Hi Konrad,
> I am a Haskell newbie working on my first serious test case,=20
> and I would like=20
> some feedback from the experts to make sure I am not doing=20
> anything stupid=20
> ;-)
Well, I may not exactly qualify, but I can give you a few suggestions,
nonetheless...
> data Floating a =3D> Vector a =3D Vector !a !a !a
> deriving (Eq, Show)
Here. Do not use 'data C x =3D>'. It's essentially a useless part of =
the
language that really should have gone away. What you want is just:
| data Vector a =3D 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=20
> as possible, in=20
> particular to postpone decisions about precision (Float,=20
> Double, eventually=20
> arbitrary precision arithmetic) as much as possible.=20
> Therefore the "Floating=20
> a" constraint. Which leads to my first question: doing it the=20
> way I did, I=20
> find myself having to add a "Floating a" constraint to almost=20
> every function=20
> specification with a vector argument. I would prefer to=20
> specify once and for=20
> all that vector elements need to be "Floating", everywhere.=20
> Is there a way of=20
> doing that?
Not really.
> -- Periodic universe
> data Floating a =3D> OrthorhombicUniverse a =3D OrthorhombicUniverse a =
a a
Again, get rid of the 'Floating a =3D>' part.
> instance Floating a =3D> Universe (OrthorhombicUniverse a) where
> distanceVector (OrthorhombicUniverse a b c)
> (Vector x1 y1 z1) (Vector x2 y2 z2)
> =3D Vector (fmod (x2-x1) a) (fmod (y2-y1) b) (fmod (z2-z1) c)
> where fmod x y =3D x - y*truncate (x/y)
There are actually two problems. First, you want to write fmod as:
| fmod x y =3D 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 =3D> ...
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 =3D> 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) =3D> u a -> (Vector a) ->
(Vector a) -> (Vector a)
distance :: (RealFrac a, Floating a) =3D> u a -> (Vector a) -> (Vector
a) -> a
distance u v1 v2 =3D norm (distanceVector u v1 v2)
-- Infinite universe
data InfiniteUniverse a =3D InfiniteUniverse
instance Universe InfiniteUniverse where
distanceVector u v1 v2 =3D v2 <-> v1
-- Periodic universe
data OrthorhombicUniverse a =3D OrthorhombicUniverse a a a
instance Universe OrthorhombicUniverse where
distanceVector (OrthorhombicUniverse a b c)
(Vector x1 y1 z1) (Vector x2 y2 z2)
=3D Vector (fmod (x2-x1) a) (fmod (y2-y1) b) (fmod (z2-z1) c)
where fmod x y =3D 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