I have never used a monad like this before, but after coming up with this example, I can see its merits. This will calculate the distance between two cartesian coordinates. It seems to actually be quite useful because it automatically keeps any operations on Xs separate from any operations on Ys.
collapsePair :: (a -> a -> b) -> Pair a -> b
collapsePair f (Pair x y) = f x y
type Coordinates = Pair Float
type Distance = Float
type TriangleSides = Pair Distance
-- Calculate the sides of a triangle given two x/y coordinates
triangleSides :: Coordinates -> Coordinates -> TriangleSides
triangleSides start end = do
-- Pair x1 y1
s <- start
-- Pair x2 y2
e <- end
-- Pair (x2 - x1) (y2 - y1)
Pair (e - s) (e - s)
-- Calculate the cartesian distance
distance :: Coordinates -> Coordinates -> Distance
distance start end = collapsePair distanceFormula (triangleSides start end)
where distanceFormula x y = sqrt (x ^ 2 + y ^ 2)
Edit:
It turns out this example could be done with just the Applicative
instance of Pair
; no Monad
needed:
import Control.Applicative
triangleSides :: Coordinates -> Coordinates -> TriangleSides
triangleSides = liftA2 (flip (-))
But, we could make it depend on Monad
in a contrived way, by adding 1 to X at the end:
triangleSides' :: Coordinates -> Coordinates -> TriangleSides
triangleSides' start end = do
s <- start
e <- end
Pair (e - s + 1) (e - s)
In this case, the last line cannot be converted to some form of pure ...
and therefore must use the Monad instance.
Something fun that can be explored further is that we can easily expand this to include three dimensional coordinates (or more). We can use the default instance of Representable
and derive Applicative
and Monad
instances via the Co
newtype from Data.Functor.Rep.
We can then abstract the distance
calculation into its own typeclass and it will work on coordinates of n-dimensions, as long as we write a Distributive
instance for the coordinate type.
{-# Language DeriveAnyClass #-}
{-# Language DeriveGeneric #-}
{-# Language DeriveTraversable #-}
{-# Language DerivingVia #-}
import Control.Applicative
import Data.Distributive
import Data.Functor.Rep
import GHC.Generics
class (Applicative c, Foldable c) => Coordinates c where
distance :: Floating a => c a -> c a -> a
distance start end = sqrt $ sum $ fmap (^2) $ liftA2 (-) end start
data Triple a = Triple
{ triple1 :: a
, triple2 :: a
, triple3 :: a
}
deriving ( Show, Eq, Ord, Functor, Foldable, Generic1, Representable
, Coordinates )
deriving (Applicative, Monad) via Co Triple
instance Distributive Triple where
distribute f = Triple (triple1 <$> f) (triple2 <$> f) (triple3 <$> f)
> distance (Triple 7 4 3) (Triple 17 6 2)
10.246950765959598
You can verify this answer here.