How to produce a polymorphic “unit scalar” for using with Data.VectorSpace
-
14-06-2021 - |
Question
So I'm using Data.VectorSpace and I'm trying to extend on force-layout.
Now I want to produce a polymorphic '1'-scalar. That is, a scalar that, if it were multiplied with a vector, would produce the same vector, regardless of the type (parameter) of that vector. Is it possible? Is there a useful workaround?
Here's a more concrete code example (it continues from the code I've worked with here):
data Particle v = Particle { _pos :: Point v
, _vel :: v
, _force :: v
, _mass :: Scalar v
}
-- .. standalone Show and Eq omitted
initParticle :: AdditiveGroup v => Point v -> Particle v
initParticle p = Particle p zeroV zeroV unitScalar
unitScalar = undefined
-- Should always be true:
testInit :: Point (Double,Double) -> Bool
testInit p = ((_mass (initParticle p)) == 1::Double)
How can I define 'unitScalar' above?
Solution
It is not possible with the context that you define; since you say that v
is an AdditiveGroup
, that's all it can be, and thus no additional properties can be attributed to v
.
You can of course define additional classes that define an unit value:
{-# LANGUAGE FlexibleContexts #-}
module Main (main) where
import Data.VectorSpace
If we want to be mathematically rigorous, we'd probably do the following; however, this has undesirable consequences in Haskell, because there can only be one Monoid defined for a type, so the solution below is probably better.
-- BAD SOLUTION! (Arguably)
import Data.Monoid
instance Monoid Double where
mempty = 1
mappend = (*)
Instead, we define a general MultiplicativeGroup
, which also is the monoid of multiplication.
class MultiplicativeGroup a where
unit :: a
multiply :: a -> a -> a
reciprocal :: a -> a
instance MultiplicativeGroup Double where
unit = 1
multiply = (*)
reciprocal = (1 /)
Now we can implement the working example:
data Particle v =
Particle -- Removed Point for this example since I don't have its definition
{ _vel :: v
, _force :: v
, _mass :: Scalar v
}
-- We need VectorSpace because there needs to be a Scalar type associated with v
-- Also, this context requires you to use FlexibleContexts, which should be
-- harmless
initParticle :: (VectorSpace v, MultiplicativeGroup (Scalar v))
=> Particle v
initParticle = Particle zeroV zeroV unit
-- Works as expected:
main :: IO ()
main = print $ ((_mass (initParticle :: Particle Double)) == (1::Double))
By the way, you can of course replace the MultiplicativeGroup
with Num
and replace unit
with 1
, but that'll give you far more features than what you might have wanted.
PS. You should have a look at the excellent algebra
package, which does this kind of stuff more rigorously for you. Implementing force solvers on top of it wouldn't be too hard.