假设我有一对类型:
[1]
为其编写monad实例的正确方法是什么?我的想法大致是这样:
data Pair a = Pair a a
对吗?如果是这样,那么在对中指定b型中的b类型为半群?
假设我有一对类型:
[1]
为其编写monad实例的正确方法是什么?我的想法大致是这样:
data Pair a = Pair a a
对吗?如果是这样,那么在对中指定b型中的b类型为半群?
实际上,Pair
的唯一正确的monad实例如下。
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实例的原因是因为Pair
是representable functor。
instance Representable Pair where
type Rep Pair = Bool
index (Pair x _) False = x
index (Pair _ y) True = y
tabulate f = Pair (f False) (f True)
结果是,对于每个可表示的函子(>>=)
等效于以下bindRep
函数。
bindRep :: Representable f => f a -> (a -> f b) -> f b
bindRep m f = tabulate (\a -> index (f (index m a)) a)
如果我们将bindRep
函数专门用于Pair
,则会得到以下结果。
bindRep :: Pair a -> (a -> Pair b) -> Pair b
bindRep (Pair x y) f = tabulate (\a -> index (f (index (Pair x y) a)) a)
= Pair (index (f x) False) (index (f y) True)
-- |_________________| |________________|
-- | |
-- (1st elem of f x) (2nd elem of f y)
以下Adelbert Chang的博客文章对此进行了更好的解释。 Reasoning with representable functors。
,Functor和应用实例很容易。 monad实例花了我一段时间。
data Pair a = Pair a a deriving (Eq,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 a b) = Pair (f a) (g b)
instance Monad Pair where
return = pure
(Pair a b) >>= f = let Pair a' _ = f a
Pair _ b' = f b
in Pair a' b'
我采用这种单子定律来解决这个问题。您可以轻松地举一个例子来验证,如果您同时使用Pair a' a''
或Pair b' b''
或Pair a' b'
或Pair a'' b''
都不适用身份法。因此,唯一的解决方案必须是对角线。定义
(m >>= f) = Pair (first . f . first $ m) (second . f . second $ m)
其中first
和second
是显而易见的,您可以证明所有定律。
通过使Pair
取2×2正方形的对角线,可以将Monad
变成join
。 return
除了复制参数外别无选择。这样会将Pair
变成固定长度的ZipList
。
当然,join
的定义会丢弃一些数据。这可能重要也可能不重要。
一个Monad实例可能不会对其包含的数据类型(此处为a
)施加任何约束。它必须将该数据视为完全不透明。我认为您的Pair
类型不接受Monad实例(或Applicative实例),因为无法在不丢失某些数据或以某种方式使用的情况下将一对配对变成一对类型类定义不允许。
我很想知道 Pair a
monad 的实际用途是什么,特别是考虑到 (>>=)
的唯一有效实现实际上丢弃了一半的数据,所以我问了 {{3} }.
事实证明 this question 也是这个问题的一个非常有趣的答案(并且包含在 Daniel Wagner 在当前问题下的评论中,但我没有注意到),所以我将详细说明在这里,因为我从中学到了很多东西。
(a,a)
和 Bool -> a
是一回事所以简短的回答是类型 Pair a
,或者更简单地说 (a,a)
和 Bool -> a
是同构的:将 (a,a)
提供给 fst
/{ {1}} 相当于用 snd
/Bool -> a
(或 True
/False
,这只是一个选择)馈送 False
函数。这两个同构实际上显示在answer I accepted的(已删除)答案中:
True
结果,无论哪种方式 funToPair :: (Bool -> a) -> (a,a)
funToPair f = (f True,f False)
pairToFun :: (a,a) -> Bool -> a
pairToFun (x,y) True = x
pairToFun (x,y) False = y
都是 monad,Bool -> a
/(a,a)
都是 monad。
Pair a
的 (a,a)
会丢弃信息……Monad
也是如此!此时让我感到困扰的是,Bool -> a
/Monad
的 (a,a)
实例(在 Zhiltsoff Igor 中进行了彻底解释)非常明显地丢弃了数据,而Pair a
的 Monad
实例...不是...?
大错特错! Bool -> a
的 Monad
实例(具有泛型 r -> a
,而不仅仅是 r
)一直在丢弃信息!如何?让我们看看众所周知的 Bool
实现:
>>=
现在记住,instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
类型的函数等价于 x -> y
类型的元组,其条目数与 (y,y,...,y)
类型的可能值的数量一样多 (不是 x
).
y
怎么样? k
是一个二元函数,比方说 k
类型,因此它可以使用属于 x -> y -> z
类型的值和属于 {{ 类型的值的笛卡尔积1}}。如果 x
和 y
分别包含 x
和 y
值,那么 m
等价于 n
类型的元组条目为 k
,这是 (z,z,z)
附带的信息。
现在的问题是 m*n
是否利用了所有这些信息。不,它没有! k
正在向 (>>=)
馈送仅 (>>=)
可能的输入:第二个参数 k
是 n
类型的任何值,而第一个参数是 通过 r
对应于 x
的单个值。
如果我们想到数学函数,我们是说,对于固定一元函数f: A → B和二元函数k: B×A → C,{ {1}} 是 t ∈ A → k(f(t),t) ∈ C 的一元函数,即它是 k 对参数化曲线的限制通过方程 x = f(t) 和 y = t。
回到r
,f
的签名特化为
f >>= k
我们可以改写如下,只是为了更明显:
Bool -> a
上面有什么明显的?如果我们用 (>>=)
馈送 (>>=) :: (Bool -> a) -> (a -> Bool -> b) -> Bool -> b
f >>= k = \r -> k (f r) r
,我们将只使用 (>>=) f k r
| r == True = k (f True) True
| r == False = k (f False) False
-- remember this
,从而丢弃另一半数据 f >>= k
。同样,如果我们调用 True
,我们会扔掉任何 f True
。这种丢弃包含在 f False
中的一半信息的方式与通过 (f >>= k) False
aka f True
的 k
占位符丢弃的内容完全相同(改编自 Aadit M Shah's answer ):
_
确实,如果我们定义 (a,a)
和 Pair a
(这些是对 instance Monad Pair where
return = pure
(Pair a b) >>= k = let Pair a' _ = k a
Pair _ b' = k b
in Pair a' b'
的镜像 fst' (Pair a _) = a
和 snd' (Pair _ b) = b
),那么 fst
可以是简单写成如下
snd
在我看来,这与我用 (,)
标记的代码非常相似,带有 (>>=)
镜像 (>>=) :: Pair a -> (a -> Pair b) -> Pair b
p >>= k = (fst' (k (fst' p)),snd' (k (snd' p)))
和 -- remember this
镜像 fst'
。
顺便说一下,对于下面的情况,让我们注意 True
的类型,如果我们被允许为 snd'
编写它:
False
(>>=)
和 (a,a)
是同一类型时,(>>=) :: (a,a) -> (a -> (b,b)) -> (b,b)
怎么样?这是我最后的疑问:因为 Ismor's answer,在特殊情况下,类型 (a,b)
等于类型 a
(和 b
必须是 b
),我们是否得到与上面相同的实例?
不,上面的问题是病态的,缺陷在于“在类型a
等于类型b
”的特殊情况 .这个假设根本不可能,因为类型构造函数要成为 Monoid
,它必须有一个且只有一个自由类型参数:
b
在自由参数 a
中被设置为 Monad
,这意味着 (a,b)
将被允许根据 Monad
的第二个参数而变化,而 b
类型将通过 b
运算符保持不变;(>>=)
在自由参数 a
中被设置为 (>>=)
,这意味着 (a,a)
将被允许根据 Monad
的第二个参数而变化,而......没什么,就是这样,答案的前一部分中都做了解释。