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.