I wanted to create a type or class, which can be used to handle angles. I started with defining a class, to make it possible to store degrees in Integral, radians in Floating, etc... But I realized, that I will meet complex problems if I continue it so. For example handling the addition of two angles, like a Radian Double and a Degree Int.

To keep it simple, easy to use and develop, I decided to create one type only, and write the functions around it.

module Data.Angle where

import Data.Fixed -- mod'

data Angle a = Radians a deriving (Eq, Show)

-- Creating Angle from a value

-- | Create an Angle with the given degrees
angleFromDegrees :: (Integral d, Floating r) => d -> Angle r
angleFromDegrees x = Radians $(realToFrac x) * pi/180 -- | Create an Angle with the given turns angleFromTurns :: (Real t, Floating r) => t -> Angle r angleFromTurns x = Radians$ (realToFrac x) * pi*2

-- | Create an Angle with the given turns
angleFromRadians :: (Floating r) => r -> Angle r

-- Get the value from Angle

-- | Get degrees from an Angle
angleValueDegrees :: (Floating r, RealFrac r, Integral d) => Angle r -> d
angleValueDegrees (Radians x) = round $x / pi * 180.0 -- | Get radians from an Angle angleValueRadians :: (Floating r) => Angle r -> r angleValueRadians (Radians x) = x -- | Get turns from Angle angleValueTurns :: (Floating r) => Angle r -> r angleValueTurns (Radians x) = x / (pi*2) -- Basic functions -- | Adding two angles addAngle :: (Floating a) => Angle a -> Angle a -> Angle a addAngle (Radians r1) (Radians r2) = Radians$ r1 + r2

-- | Normalize Angle: transforming back to (0-2pi)
normAngle :: (Floating a, Real a) => Angle a -> Angle a
normAngle (Radians r) = Radians $mod' r (pi*2) -- | Add two angles and normalize the result addAngleNorm :: (Floating a, Real a) => Angle a -> Angle a -> Angle a addAngleNorm a b = normAngle$ addAngle a b

-- | Distance between two angles
distAngle :: (Floating a, Real a) => Angle a -> Angle a -> Angle a
distAngle (Radians r1) (Radians r2) = Radians $if (a' < b') then a' else b' where a' = mod' (r1-r2) (pi*2) b' = mod' (r2-r1) (pi*2) -- | Flip Angle flipAngle :: (Floating a) => Angle a -> Angle a flipAngle (Radians r) = Radians (-r) -- | Flip Angle and normalize the result flipAngleNorm :: (Floating a, Real a) => Angle a -> Angle a flipAngleNorm = normAngle . flipAngle -- | Add degrees to Angle addAngleDegrees :: (Floating r, Integral d) => Angle r -> d -> Angle r addAngleDegrees ang deg = addAngle ang$ angleFromDegrees deg

addAngleRadians :: (Floating r) => Angle r -> r -> Angle r
addAngleRadians (Radians r1) r2 = Radians $r1 + r2 -- | Add turns to Angle addAngleTurns :: (Floating r, Real t) => Angle r -> t -> Angle r addAngleTurns ang turn = addAngle ang$ angleFromTurns turn

-- Trigonometric functions

-- | Sine of the angle
sinAngle :: (Floating a) => Angle a -> a
sinAngle (Radians r) = sin r

-- | Cosine of the angle
cosAngle :: (Floating a) => Angle a -> a
cosAngle (Radians r) = cos r

-- | Tangent of the angle
tanAngle :: (Floating a) => Angle a -> a
tanAngle (Radians r) = tan r

-- | Cotangent of the angle
cotAngle :: (Floating a) => Angle a -> a
cotAngle (Radians r) = 1 / (tan r)

-- Inverse trigonometric functions

-- | Create angle from inverse sine
asinAngle :: (Floating a) => a -> Angle a
asinAngle x = Radians $asin x -- | Create angle from inverse cosine acosAngle :: (Floating a) => a -> Angle a acosAngle x = Radians$ acos x

-- | Create angle from inverse tangent
atanAngle :: (Floating a) => a -> Angle a
atanAngle x = Radians $atan x -- | Create angle from inverse cotangent acotAngle :: (Floating a) => a -> Angle a acotAngle x = Radians$ (pi/2) - (atan x)


The finished library is available here:

I would use newtype instead of data to define Angle, like this:

newtype Angle a = Radians { angleValueRadians :: a }


This will automatically create the angleValueRadians function to get the angle value, and will be a bit more efficient.

Here is a good answer explaining the differences between data, newtype, and type: https://stackoverflow.com/a/21081227/1525759

You could use function composition to define many of your functions instead of pattern matching (pattern matching isn't bad, I just prefer this style):

sinAngle :: (Floating a) => Angle a -> a


Also, if you made Angle an instance of Applicative, you could define addAngle like this:

addAngle :: (Floating a) => Angle a -> Angle a -> Angle a
addAngle r1 r2 = (+) <$> r1 <*> r2 -- or even this, because f <$> x <*> y is the same as liftA2 f x y:


Here's how you could do that:

instance Functor Angle where

instance Applicative Angle where