Known/estabilished 同源对的 monad 实例的用例

Known/estabilished usecases for the monad instance of an homogeneous pair

有一次我问this question, which was correctly marked as duplicate of

现在我很好奇,一对同类类型的 monad 实例是否有任何已知的用例?

这是它的实例:

data Pair a = Pair a a deriving Show

instance Functor Pair where
  fmap f (Pair a b) = Pair (f a) (f b)

instance Applicative Pair where
  pure a = Pair a a
  Pair f g <*> Pair x y =  Pair (f x) (g y)

instance Monad Pair where
    m >>= f = joinPair (f <$> m)

joinPair :: Pair (Pair a) -> Pair a
joinPair (Pair (Pair x _) (Pair _ y)) = Pair x y

我以前从来没有用过这样的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)

编辑:

原来这个例子可以只用 PairApplicative 实例来完成;不需要 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 实例。


可以进一步探索的有趣之处在于,我们可以轻松地将其扩展为包括三维坐标(或更多)。我们可以使用 Representable 的默认实例,并通过 Data.Functor.Rep.

Co 新类型派生 ApplicativeMonad 实例

然后我们可以将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

你的 Pair a 同构于 Reader Bool a/Bool -> a:

to (Pair f t) = \b -> if b then t else f

from f = Pair (f False) (f True)

因此,the Reader monad 的任何用例也是您的 monad 的潜在用例。这些数据类型的通用术语是 可表示仿函数