{-# LANGUAGE TypeFamilies, GeneralizedNewtypeDeriving #-} -- |Define a lightweight wrapper, 'Angle', for floating point numeric -- types that comes with a 'Num' instance that wraps around at -- 0 and 2π, and properly handles angle differencing. module AngleNum (Angle, angle, fromAngle, toDegrees) where import Data.Function import Data.VectorSpace wrapAngle :: (Floating a, Ord a) => a -> a wrapAngle theta | theta < 0 = theta + 2 * pi | theta > 2*pi = theta - 2 * pi | otherwise = theta angleDiff :: (Floating a, Ord a) => a -> a -> a angleDiff x y = wrapAngle (x' + pi - y') - pi where x' = if x < 0 then x + 2*pi else x y' = if y < 0 then y + 2*pi else y -- |Representation of an angle in radians. newtype Angle a = Angle { fromAngle :: a } deriving (Eq, Ord, Show, Fractional, Floating) -- |Produce an 'Angle' value within the range [0,2pi]. angle :: (Floating a, Ord a) => a -> Angle a angle = Angle . wrapAngle -- |Convert an 'Angle' into a floating point number of degrees. toDegrees :: Floating a => Angle a -> a toDegrees = (* (180 / pi)) . fromAngle instance (Floating a, Ord a) => Num (Angle a) where Angle x + Angle y = Angle . wrapAngle $ x + y (*) = ((Angle . wrapAngle) .) . (*) `on` fromAngle Angle x - Angle y = Angle $ angleDiff x y negate (Angle x) = Angle (negate x) abs (Angle x) = Angle (abs x) signum (Angle x) = Angle (signum x) fromInteger = Angle . wrapAngle . fromInteger instance (Floating a, Ord a, AdditiveGroup a) => AdditiveGroup (Angle a) where zeroV = Angle 0 (^+^) = ((Angle . wrapAngle) . ) . (^+^) `on` fromAngle negateV = negate instance (Floating a, Ord a, AdditiveGroup a) => VectorSpace (Angle a) where type Scalar (Angle a) = a s *^ Angle x = Angle . wrapAngle $ s * x