一个Monad m定义了一个运算(computation):
图中上面一个是monad m a,下面一个是function (a->mb)。
可以大致这么理解,一个monad是包含两面的,它除了在一个世界中作为a以外,还携带了另外一个世界如何从in变化到out的信息。所以,一个monad还叫做action,或者computation。例如,IO monad又称IO action。后面为了不用每次画图,我们这样画一个monad:
a//in->out,或者a//in,或者a//out
而a->m b这样画:
a->b//in->out
Monad有四个基本运算分别是:bind(>>=), then(>>), return, fail。
从数学的角度讲,一个monad只需要两个运算,>>=和return就够了。不过从程序设计角度,为了便利,添加了>>和fail,他们分别是>>=和return的特化型。
bind运算把一个monad m a的pure部分取出来,放到一个monad constructor (a->m b)中,构造器产生一个新的monad m b,借此把a输送给real world。
then运算是特殊的bind,它描述了两个monad的顺序诞生。
return运算是一个特殊的constructor,它接受一个pure world中的a,产生一个monad m a。
fail运算是特殊的return,它接受一个String之后,产生一个monad,同时把这个String输送给real world。
monad三定律:
(1) return a >>= k == k a
(2) m >>= return == m
(3) m >>= (/x -> k x >>= h) == (m >>= k) >>= h
第一个,monad bind到constructor等价于直接apply monad中的pure部分到constructor。
第二个,return保留monad的所有信息不变。
第三个,bind运算满足结合律。
好了,看了这么些难以理解的概念之后,让我们看看几个实际的例子吧。
1. putStrLn :: String -> IO ()
putStrLn函数根据pure world中的一个String构造了一个IO monad IO ()。
2. Just :: a -> Maybe a
Just这个constructor根据a构造一个Maybe monad Maybe a。
试试把一个Maybe monad bind到print看看:
Prelude> (Just 3)>>=print
Couldn't match expected type `Maybe' against inferred type `IO'
Expected type: t -> Maybe b
Inferred type: t -> IO ()
看起来,monad只能bind到能够构造同类型monad的constructor上。
Prelude> let f x | x>=0 = Just (x+1) | x<0 = Nothing
Prelude> :t f
f :: (Ord a, Num a) => a -> Maybe a
Prelude> (Just 3)>>=f
Just 4
Prelude> (Just (-1))>>=f
Nothing
进一步看看:
Prelude> :t f 3
f 3 :: (Ord t, Num t) => Maybe t
Prelude> :t f (-1)
f (-1) :: (Ord a, Num a) => Maybe a
可以看到,real world中不是只有I/O一种action。
3. rollDice = getStdRandom (randomR (1,6)) :: IO Integer
这是一个随机数产生函数。
试试看:
Prelude> :m System.Random
Prelude System.Random> let rollDice = getStdRandom(randomR(1,6))
Prelude System.Random> mapM (/x->rollDice) [1..12]
[3,5,3,6,2,4,5,1,5,6,3,2]
st.monad@gmail.com原创,转贴请注明出处,谢谢!