所以Monad到底是什么?

简单来说,是对某些经常出现在代码中的pattern的推广,这种推广有很多,我们一个个看:

  1. Semigroup
    定义为
    class Semigroup a where
     (<>) :: a -> a -> a
    其中(<>)需要满足结合律,即
    (a <> b) <> c = a <> (b <> c)
    这个结构推广了所有类型封闭且满足结合律的二元运算,例如
    instance Semigroup Int where
     n1 <> n2 = n1 + n2

2.Monoid
定义

class (Semigroup a) => Monoid a where
 mempty :: a

Monoid与对应的数学概念一样,比起Semigroup强在拥有单位元,即mempty,它满足如下法则:

x <> mempty = x
mempty <> x = x

当然,从定义中的type constraint可看出它还需要满足Semigroup的结合律。这个结构推广了那些拥有与之结合结果不变的元素的二元运算,上面那个Semigroup就是Monoid,它的单位元是0 :: Int

3.Functor
定义

class Functor f where
 fmap :: (a -> b) -> f a -> f b

每个Functor需要满足

fmap id = id
fmap $ f . g = fmap f . fmap g

即它将单位映射变为单位映射,并兼容函数的合成。当我们需要将一个函数“抬升”到更高的类型并且保留这个函数对原数值的效果,就可以用Functor,例如

instance Functor [] where
 fmap _ [] = []
 fmap f (x : xs) = f x : fmap f xs

上述代码定义了列表结构所对应的Functor,它把接收到的函数逐点应用到列表中每个元素上,最终结果还是一个列表,故而说Functor将函数抬到更高结构的同时保留原函数的效果。

4.Applicative
定义

class (Functor f) => Applicative f where
 pure :: a -> f a
 (<*>) :: f (a -> b) -> f a -> f b

可见Applicative比起Functor唯一的不同在于它允许你把函数本身“嵌入”到结构中来,看一个例子就明白了:

instance Applicative Maybe where
 pure x = Just x
 Nothing <*> _ = Nothing
 _ <*> Nothing = Nothing
 (Just f) <*> (Just x) = Just $ f x

这里,函数f被嵌进了Maybe中,于是可以根据其类型来产生不同效果。一个更明显的例子是列表,如下

instance Applicative [] where
 pure x = x : []
 fs <*> xs = [f x | f <- fs, x <- xs]


如图所示,它允许你将一列函数运用到一个列表上,并把结果拼起来。所有Applicative需满足

pure id <*> v = v    -- Identity
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)    -- Composition
pure f <*> pure x = pure $ f x    -- Homomorphism
u <*> pure v = pure ($ v) <*> u    -- Interchange

5.Monad
定义

class (Applicative m) => Monad m where
 return :: a -> m a
 (>>=) :: m a -> (a -> m b) -> m b

Monad与Applicative类似,也允许你按照某种结构来应用函数,还是来看一些例子:

instance Monad (Either e) where
 return = pure    -- this should be true for any Monad
 (Left l) >>= _ = Left l
 (Right r) >>= f = f r

这个Monad的作用是,如果被bound(>>=被叫做bind)的变量在Left中,则不应用函数;若在Right中,则正常返回结果。可见Monad很适合成序列的操作,比如:

上图中我们同时“取出”了包裹在两个列表中的元素,对它们进行操作后又放回了列表中,这种同时操作是Monad相比Applicative最大的优势之一。Monad必须满足

return x >>= f = f x    -- Left Identity
x >>= return = x    -- Right Identity
(x >>= y) >>= z = x >>= (\a -> y a >>= z)   -- Associativity

OK, so what hell is a monoid in the category of endofunctors?
大概是这么回事,由于你可以把type看作object,函数看作morphism,(.)id分别为morphism的合成运算与identity,这样会得到一个范畴Hask(虽然这实际上不是个范畴,详见 https://wiki.haskell.org/Haskhttps://math.andrej.com/2016/08/06/hask-is-not-a-category)。于是Monad就by definition是自函子范畴上的幺半群(范畴论意义上的而非抽象代数意义上的)了。。。

积分: 4

图片怎么全没了

积分: 4

我很讨厌那种叽里呱啦一大堆某某东西有什么用能干什么但死活不告诉你 这个东西到底是什么 的教学
今天我在跟着某本教程学所谓的Reader(其实就是函数的Applicative和Monad实例),作者上来先说这个东西能让函数在结构中等待某个输入值,嗯很好理解,然后他开始举例了:

readFnG foo bar = do
          f <- foo
          g <- bar
          return (f,g)

-- pseudo output --

readFnG (+ 1) (* 2) $ 3  =  (4,6)

余不解,这是什么黑魔法么?就算你type能对上并且读一下也确实能看出这函数在干嘛,但问题关键不是到底为什么能这样干么?
是,举例子可以帮助理解加深印象形成直觉blahblah,但这应该是给出定义之后的事情,是在有了一个稳固、精确的出发点后增进理解的一个过程,不然你举再多例子我也只知道某种情形下这个东西能达成什么效果,还得如临深渊如履薄冰战战兢兢生怕哪里猜错了
然后他——仍然没给定义——叫我用>>=替换do把这个函数重写一遍,我终于忍无可忍查了下document,于是

我操你妈,把我生命宝贵的两小时还给我

积分: 4
4 天 后

我在写StateT的Applicative时发现有好几种可能的写法,你既可以先执行 待处理/被映射的那个东西 来得到新状态再执行函数获取最终状态,

也可以反过来(上图改为先(f,s1)<-(stmf s)(x,s2)<-(stmxs s1))。然而你要是打算“同时”执行这两个步骤(见下图),那就倒楣了!

这样虽然可以正常编译(类型当然也对的上),但却不是个Applicative,它不满足law of interchange:

-- pseudocode --
u <*> pure v = state m (f x,su) != state m (f x,sv) = pure ($ v) <*> u

自然,你取sv为最终状态也解决不了这个问题。Monad的情况似乎简单的多,因为你必须先把 待处理的那个东西 传进去,才能执行函数那一步。但其实这么写也能过编译

我们最后没有使用ss而是用了两次初始状态。不过这个“Monad”依照
m1 <*> m2 = m1 >>= (\x1 -> m2 >>= (\x2 -> return (x1 x2)))
引出的“Applicative”所做的实际上是(如果我没算错的话)
\s -> (f x,s)
由于它锁死了最初状态,故而违背了law of identity,因此这不是一个Monad

积分: 4