假设有一个类似国际象棋的棋盘,上面有一个棋子-马
我们想了解马是否能用三步到达一个特定的位置
创建一个类型别名来表示棋盘上马的位置:
type KnightPos = (Int, Int)
假设马从6,2出发,它能三步到达6,1吗?(友情提示:马走日)
下面的函数取马的位置为参数,返回所有下一步可以到达的位置
moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = do
(c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1),
(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)]
guard (c' `elem` [1..8] && r' `elem` [1..8])
return (c',r')
-- guard确保新的位置仍旧在棋盘上,如果不是的话,guard会返回空列表
-- return(c',r')就不会被执行了
红圈代表可以走的点
或者不用monad,用filter
moveKnight' :: KnightPos -> [KnightPos]
moveKnight' (c,r) = filter onBoard
[(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1),
(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)]
where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]
有了下一个位置,就可以用 >>= 喂给moveKnight
下面的函数取位置为参数,返回三步可以到达的所有位置
in3 :: KnightPos -> [KnightPos]
in3 start = do
first <- moveKnight start
second <- moveKnight first
moveKnight second
如果不用do
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
现在,创建一个函数,该函数取两个位置并告诉我们你是否能从其中一个位置三步到达另一个位置
canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` In3 start
Monad定律
左单位元
-- return x >>= f 和 f x 一样
ghci> return 3 >>= (\x -> Just (x+100))
Just 103
ghci> (\x -> Just (x+100)) 3
Just 103
对于列表Monad,return把某个东西放在单元素列表里
列表的>>=实现遍历列表的所有元素,对它们应用那个函数
右单位元
m >>= return 跟 m 没有差别
xs >>= f = concat (map f xs)
当我们把[1,2,3,4]喂给return时
首先return被映射到[1,2,3,4]上
得到[[1],[2],[3],[4]]
然后结果被拼接起来,得到原始列表
结合律
-- (m >>= f) >>= g 应该和 m >>= (\x -> f x >>= g) 一样
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = (\x -> f (g x))
如果g的类型是(a -> b),f的类型是(b -> c),我们就得到了类型为 a -> c 的另一个函数
如果函数的类型是 a -> m b,我们不能把它的结果传给 b -> c 因为它接收普通b
(<=<) :: (Monad m) => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = (\x -> g x >>= f)
ghci> let f x = [x,-x]
ghci> let g x = [x*3,x*2]
ghci> let h = f <=< g
ghci> h 3
[9,-9,6,-6]
相当于就是 f <=< (g <=< h) 应该和 (f <=< g) <=< h 相同
Haskell趣学指南