如果我们有一个环境和值m a, 还有一个函数a -> m b,
怎样才能调用这个函数呢?
我们需要monad, 它是对Applicative Functor的补充
monad
1 | class Monad m where |
感觉class定义里应该有Applicative m, 但是没有,
这是因为早期haskell并不认为Applicative Functor适合Haskell
但是现在证明适合, 并且所有的Monad都是Applicative Functor, 虽然class定义里没有说1
2
3
4
5instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
原来Monad提供了类似F#的从左向右调用的功能, 就是>>=
一个Monad应用实例
1 | type Birds = Int |
当每一步都有可能失败的时候,好的做法是允许每一步的调用返回Maybe类型
反例, 如果不使用Monad代码可能像这样1
2
3
4
5
6
7
8routine :: Maybe Pole
routine = case landLeft 1 (0, 0) of
Nothing -> Nothing
Just pole1 -> case landRight 4 pole1 of
Nothing -> Nothing
Just pole2 -> case landLeft 2 pole2 of
Nothing -> Nothing
Just pole3 -> landLeft 1 pole3
Monad展示了错误处理的境界
do
1 | Just 3 >>= (\x -> Just (show x ++ "!")) |
加换行以求清晰一些1
2
3
4foo :: Maybe String
foo = Just 3 >>= (\x ->
Just "!" >>= (\y ->
Just (show x ++ y)))
干脆发明一个do语法1
2
3
4
5foo :: Maybe String
foo = do
x <- Just 3
y <- Just "!"
Just (show x ++ y)
前例也可以使用do语法改写为:1
2
3
4
5
6
7routine :: Maybe Pole
routine = do
start <- return (0, 0)
first <- landLeft 2 start
Nothing
second <- landRight 2 first
landLeft 1 second
明显不如原版可读性好
do语法里可以使用pattern match1
2
3
4justH :: Maybe Char
justH = do
(x:xs) <- Just "hello"
return x
When pattern matching fails in a do expression, the fail function (part of the Monad type class) enables it to
result in a failure in the context of the current monad, instead of making the program crash.
List Monad
1 | instance Monad [] where |
In fact, list comprehensions are just syntactic sugar for using lists as monads.
MonadPlus and the guard Function
1 | ghci> [ x | x <- [1..50], '7' `elem` show x ] |
The MonadPlus type class is for monads that can also act as monoids.
mzero is synonymous with mempty from the Monoid type class, and mplus corresponds to mappend.
1 | instance MonadPlus [] where |
Monad Laws
Left Identity1
return x >>= f 即 f x
Right Identity1
m >>= return 即 m
Associativity1
(m >>= f) >>= g 即 m >>= (\x -> f x >>= g)
the mtl package
ghc-pkg list
Writer Monad
1 | newtype Writer w a = Writer { runWriter :: (a, w) } |
附加数据, 往往做日志有用
Difference List
保证效率连接List(即总是在List头部++而不是尾部++)的一个类型1
2
3
4newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
instance Monoid (DiffList a) where
mempty = DiffList (\xs -> [] ++ xs)
(DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))
结果DiffList就是一个function composition, 相当于把++的顺序倒了过来
这样原来慢的变成快的,原来快的变成慢的
Function as Monad(Reader)
1 | instance Monad ((->) r) where |
函数的环境是,还缺一个值,我们需要对这个值调用这个函数来取得返回值
1 | addStuff :: Int -> Int |
函数的Monad也叫Reader, 所有的函数都从同一个数据源取值
当我们有许多操作针对同样的值, 就可以考虑使用Reader
State
把状态封装在Monad的环境里1
2
3
4
5newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return x = State $ \s -> (x, s)
(State h) >>= f = State $ \s -> let (a, newState) = h s (State g) = f a in g newState
return x返回一个一元函数,该函数接受一个状态返回一个State Monad,该Monad的值是x,环境是这个状态
>>= 里This lambda will be our new stateful computation.
()基本上当void或erlang的ok来用
type class MonadState
Maybe的Nothing只告诉我们失败了, 却没有失败的原因
Control.Monad.Error1
2
3
4
5instance (Error e) => Monad (Either e) where
return x = Right x
Right x >>= f = f x
Left err >>= f = Left err
fail msg = Left (strMsg msg)
其中e必须符合Error这个type class
liftM
有了liftM,我们可以不必实现Functor type class, liftM类似fmap, 就像return和pure是一样的1
2liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f m = m >>= (\x -> return (f x))
函数ap类似<*>1
2
3
4
5ap :: (Monad m) => m (a -> b) -> m a -> m b
ap mf m = do
f <- mf
x <- m
return (f x)
1 | liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c |
这是一个方便的函数
liftM2至liftM5做的事情相同, 只不过类型要求是Monad
可以直接定义Applicative Functor的pure为return, <*>为ap
join
1 | join :: (Monad m) => m (m a) -> m a |
1 | ghci> join [[1,2,3],[4,5,6]] |
m >>= f 即 join (fmap f m)
filterM
1 | filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a] |
filterM过滤后不仅返回Bool, 还带一个环境(可能包含原因等信息)
例子:1
2
3
4
5
6
7
8keepSmall :: Int -> Writer [String] Bool
keepSmall x
| x < 4 = do
tell ["Keeping " ++ show x]
return True
| otherwise = do
tell [show x ++ " is too large, throwing it away"]
return False
1 | ghci> fst $ runWriter $ filterM keepSmall [9,1,5,2,10,3] |
1 | powerset :: [a] -> [[a]] |
1 | ghci> powerset [1,2,3] |
foldM
1 | foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a |
Composing Monadic Functions
1 | ghci> let f = (+1) . (*100) |
这是Monad与非Monad的类比,其实操作都很像,换几个符号而已
1 | ghci> let f = foldr (.) id [(+1),(*100),(+1)] |
Rational类似与Float不同, 不会丢失精度