我以前从未使用过这样的 monad,但是在提出这个示例之后,我可以看到它的优点。这将计算两个笛卡尔坐标之间的距离。它实际上似乎非常有用,因为它会自动将 Xs 上的任何操作与 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)
编辑:
事实证明,这个例子可以只用Applicative 的Pair 实例来完成;不需要Monad:
import Control.Applicative
triangleSides :: Coordinates -> Coordinates -> TriangleSides
triangleSides = liftA2 (flip (-))
但是,我们可以通过在 X 末尾添加 1 以人为的方式使其依赖于 Monad:
triangleSides' :: Coordinates -> Coordinates -> TriangleSides
triangleSides' start end = do
s <- start
e <- end
Pair (e - s + 1) (e - s)
在这种情况下,最后一行不能转换为某种形式的pure ...,因此必须使用 Monad 实例。
可以进一步探索的有趣之处在于,我们可以轻松地将其扩展为包括 3 维坐标(或更多)。我们可以使用Representable 的默认实例并通过Data.Functor.Rep 中的Co 新类型派生Applicative 和Monad 实例。
然后我们可以将 distance 计算抽象到它自己的类型类中,它可以在 n 维坐标上工作,只要我们为坐标类型编写一个 Distributive 实例。
{-# 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
你可以验证这个答案here。