Haskell 中 Monad 和 Functor 类型类的 Monad 和 Functor 定律

Smo*_*Ken 2 monads haskell functional-programming functor category-theory

我是一个有点经验的函数式程序员,但我一直对声明 \xe2\x80\x9cA monad is just a monoid in the Category of endofunctors\xe2\x80\x9d 以及 Haskell 中 monads/functors 的实际实现感到困惑其他函数式语言。

\n

根据 Haskell Wiki,\n https://wiki.haskell.org/Functor

\n
class Functor f where\n    fmap :: (a -> b) -> f a -> f b\n    (<$) :: a -> f b -> f a\n
Run Code Online (Sandbox Code Playgroud)\n

函子定律

\n

函子必须保留恒等态射

\n
fmap id = id\n
Run Code Online (Sandbox Code Playgroud)\n

函子保留态射的组成

\n
fmap (f . g)  ==  fmap f . fmap g\n
Run Code Online (Sandbox Code Playgroud)\n

我很明白这一点,所以不需要进一步解释;然而,当我们使用 monad 运算符时>>=,现在我们有了 Monad 实现和 Monad 法则

\n

https://wiki.haskell.org/Monad_laws

\n

在这里,我\xe2\x80\x99m并不质疑每个实现和规则。我\xe2\x80\x99m想知道的是,如果我们说\xe2\x80\x9cevery Monad是一个endofunctor,因为monad只是endofunctors\xe2\x80\x9d类别中的一个幺半群,我希望一个monad实例满足函子定律,但实际上并非如此。

\n

或者,虽然 monad 是一个内函子,但该实例有 2 个态射,分别是>>=fmap。这两个态射有独立的定律吗?

\n

我知道函子定律来自范畴论,并且单子满足幺半群定律。但同样,如果一个单子是一个函子,我不明白为什么它不满足函子定律。

\n

例如,

\n
fmap id = id\n
Run Code Online (Sandbox Code Playgroud)\n

如果我更改此代码将不起作用

\n
fmap (f . g)  ==  fmap f . fmap g\n
Run Code Online (Sandbox Code Playgroud)\n

Bar*_*ski 9

首先,单子有两个等效的定义。第一个直接对应于“endofunctors 的幺半群”:

类 Functor m => Monad m 其中
  返回::a->ma
  加入 :: m (ma) -> ma

在这里,我们明确假设m是一个内函子。内函子的幺半群“张量积”由它们的组成给出。从到join的“方块”的箭头也是如此;它在我们的幺半群中定义了“乘法”。是从恒等函子到 的箭头;它定义了幺半群单位。存在与左/右单位和结合性相关的三个幺半群定律。mmreturnmjoinreturn

MonadHaskell 中使用的定义使用 bind而>>=不是join。使用bind定义的monad自动成为一个endofunctor,fmap定义为:

liftM :: Monad m => (a -> b) -> ma -> mb
liftM f ma = ma >>= 返回 。F

第一个定义中的(相对简单的)幺半群定律转化为以下定律:

返回 a >>= k = ka
ma >>= 返回 = ma
ma >>= (\x -> kx >>= h) = (ma >>= k) >>= h

函子法则liftM遵循这些法则。例如,您可以使用以下步骤推导组合定律:

(升力M f . 升力M g)ma
= liftM f (ma >>= (返回 . g))
= (ma >>= (return .g)) >>= return .g F
= ma >>= (\x -> ((return (gx)) >>= return .f))
= ma >>= (\x -> return (f (gx)))
= liftM (f . g) ma


lef*_*out 8

出于理论目的,通常最好忘记>>=,我们不需要它。这些类型类的数学风格定义如下所示:

\n
class Functor f where\n  fmap :: (a -> b) -> f a -> f b\nclass Functor f => Applicative f where -- aka Monoidal\n  pure :: a -> f a\n  liftA2 :: (a -> b -> c) -> f a -> f b -> f c  -- \xe2\x80\xa0\nclass Applicative m => Monad m where\n  join :: m (m a) -> m a\n
Run Code Online (Sandbox Code Playgroud)\n

(然后您可以继续定义x>>=f = join (fmap f x),但这只是为了方便。)

\n

由此应该清楚的是,任何讨论都与[1, 2, 3] >>= id单子是否是函子的问题无关。单子作为函子的含义就在类头中:Monad是 的子类Functor。因此,任何 monad 都fmap可用,这fmap仍然是满足函子定律所需的,无论您现在也有那些更高级的方法可用。

\n
\n

\xe2\x80\xa0或是否更数学化是有争议的。原则上我们应该讨论带有签名的函数,但严格来说,它仅适用于关联张量积。Haskell 元组近似于此,但仅在尴尬的“与”同构的情况下。柯里化函数的好处是隐藏了这种关联性,但付出的代价是我们需要处理指数对象,这一切都变得更加混乱。无论如何,我总是发现比 更容易理解。liftA2<*>(a\xe2\x8a\x97b -> c) -> f a\xe2\x8a\x97f b -> f c((a,b),c)(a,(b,c))<*>liftA2<*>

\n