With the following types
newtype Kms =
Kms Double
newtype Hours =
Hours Double
newtype KmsPerHour =
KmsPerHour Double
I would like to have the following
foo :: KmsPerHour -> Hours -> Kms
foo kmsPerHour hours = kmsPerHour * hours
Is this possible? In a perfect world the solution would support (/) as well and for several different units (m, m/s, m/s/s for example)
That way I can more easily make sure that all the units I use match up and that the calculations is correct. The way I do it now (but with several more types of units in different combinations) is
foo :: KmsPerHour -> Hours -> Kms
foo (KmsPerHour kmsPerHour) (Hours hours) = Kms $ kmsPerHour * hours
I checked this Can you overload + in haskell? and https://hackage.haskell.org/package/alg-0.2.10.0/docs/src/Algebra.html#%2B but those are just a->a->a
Update
My try looks like this. I really like how it very tightly secures the types and it supports nested types and I do not need to define every type combination but I do not know if this is a good way to go - especially since the type family option seems elegant.
Ofc, the functions below can be changed to operators
class Unit a where
unwrap :: a -> Double
unitMap :: (Double -> Double) -> a -> a
instance Unit Kms where
unwrap (Kms x) = x
unitMap f (Kms x) = Kms $ f x
newtype Per a b =
Per a
deriving (Eq, Show)
instance (Unit a, Unit b) => Unit (Per a b) where
unwrap (Per x) = unwrap x
unitMap f (Per x) = Per $ unitMap f x
multWithUnits :: (Unit a, Unit b) => Per a b -> b -> a
multWithUnits (Per x) z =
let zVal :: Double
zVal = unwrap z
in unitMap (* zVal) x
divWithUnits :: (Unit a, Unit b) => a -> b -> Per a b
divWithUnits x y =
let yVal = unwrap y
in Per (unitMap (/ yVal) x)
multUnitWith :: (Unit a, Unit b) => Double -> Per a b -> Per a b
multUnitWith factor = convert (* factor)
divUnitWith :: (Unit a, Unit b) => Double -> Per a b -> Per a b
divUnitWith factor = convert (/ factor)
toKmsPerHour :: Kms -> Hours -> Per Kms Hours
toKmsPerHour kms h = km `divWithUnits` h
distance :: Per Kms Hours -> Hours -> Kms
distance speed time = speed `multWithUnits` time
I ommited implementations of Hours, and the instances of Num, Ord and other things to not bloat the post.
addKms :: Kms -> Kms -> Kms
addKms k1 k2 = k1 + k2
Thoughts?