Haskell ';do';符号
我在阅读本教程时偶然发现了这个定义:Haskell ';do';符号,haskell,recursion,Haskell,Recursion,我在阅读本教程时偶然发现了这个定义: type KnightPos = (Int,Int) 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)
type KnightPos = (Int,Int)
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')
in3 :: KnightPos -> [KnightPos]
in3 start = do
first <- moveKnight start
second <- moveKnight first
moveKnight second
type KnightPos=(Int,Int)
moveKnight::KnightPos->[KnightPos]
moveKnight(c,r)=do
(c',r')[KnightPos]
in3开始=do
首先是一个->[KnightPos]
这样它也将移动次数作为一个参数,而不是仅仅绑定到3次跳跃?注意,
concatMap moveKnight
具有类型[Knight]>[Knight]
,并将返回从输入位置可到达的位置
知道这一点,您可以使用:
iterate (concatMap moveKnight)
生成一个无限的位置集合列表,其中下一组位置是通过使骑士从上一组位置移动而获得的
例如:
iterate (concatMap moveKnight) [(1,2)]
= [ [(1,2)], -- the initial list
[(3,1),(3,3),(2,4)], -- after one iteration
[(5,2),(1,2),(4,3),(2,3), ... -- after two iterations
...
]
inNMoves start n = (foldl (>>=) . return) start (replicate n moveKnight)
现在,in3
可以写成
in3 xs = moves !! 3
where moves = iterate (concatMap moveKnight) xs
使用直接递归:
inNMoves :: KnightPos -> Int -> [KnightPos]
inNMoves start 0 = return start
inNMoves start n = do
first <- moveKnight start
inNMoves first (n - 1)
甚至完全没有意义:
inNMoves = (. flip replicate moveKnight) . foldl (>>=) . return
因为这个练习是关于列表单子的,所以不要去想你所知道的关于列表的内容,而是把你自己限制在单子的结构上。那就是
move :: Monad m => Pos -> m Pos
也就是说,move
获取一个Pos
并返回一些关于Pos
的东西,这些东西在一些一元上下文m
中。(在列表的情况下,“上下文”是“任意多样性+排序”。但不要去想它)
另外,我们不要在这里谈论do
,它只是使用(>>=)
的语法糖分。出于本说明的目的,您需要知道如何使用(>>=)
转换为表达式
(>>=)
具有签名ma->(a->mb)->mb
。我们需要的实例是m Pos->(Pos->m Pos)->m Pos
。你看,我们在这里把a
和b
都实例化为Pos
。您还可以在此处识别中间部分(Pos->m Pos)
是move
的签名。因此,使用(>>=)
并将其作为第二个参数move
,我们可以生成类型为m Pos->m Pos
的函数
moveM :: Monad m => m Pos -> m Pos
moveM mp = mp >>= move
单子自同态的合成
很明显,m Pos->m Pos
可以按顺序执行,因为它是从类型到自身的函数(我认为这可以称为monad自同态,因为类型是monad)
让我们写一个函数,它有两个动作
move2M :: Monad m => m Pos -> m Pos
move2M mp = moveM (moveM (mp))
或者采用无点样式(只考虑转换,而不考虑转换的对象):
对于一般情况(由整数n
参数化的移动数),我们只需要一些由函数链接操作符
连接的moveM
数。所以如果n是3,我们需要moveM。莫维姆。moveM
。以下是如何以编程方式执行此操作:
nmoveM :: Monad m => Int -> m Pos -> m Pos
nmoveM n = foldr1 (.) (replicate n moveM) -- n "moveM"s connected by (.)
这里出现了一个问题:移动0次的结果是什么foldr1
对于n
=)的值是未定义的。实际上经常有点不干净,因为它是如此不对称:它需要ma
和a->mb
。换句话说,它有点过于关注转换的对象,而只关注我们案例中的转换。这使得编写转换变得不必要的困难。这就是为什么我们必须加上。return
:它是从Pos
到m Pos
的初始转换,因此我们可以自由组合任意数量的m Pos->m Pos
moveM :: Monad m => m Pos -> m Pos
moveM mp = mp >>= move
使用(>>=)
会产生以下模式:
ma >>= f_1 >>= f_2 >>= ... >>= f_n
其中,ma
是单数事物,fi
是a->mb
类型的“箭头”(通常a=b)
还有一个更好的变体,(>=>)
,它将两个a->mb
类型的箭头按顺序组合在一起,并返回另一个箭头
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
在这里,我们不必关心变换对象,而只关心变换及其组合
现在让我们同意move
实际上就是这样一个箭头(Pos->m Pos
)。所以
仍然是类型为Pos->m Pos
的有效表达式。当使用(>=>)
时,单子的可组合性变得更加明显
我们可以使用(>=>)
重新编写nmoves
,如下所示:
nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = foldr1 (>=>) (replicate n move) -- n "move"s connected by >=>
同样,我们使用了foldr1
,我们要问的是“是什么让0次连续移动”?它必须是同一类型,Pos->m Pos
,答案是return
nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = foldr (>=>) return (replicate n move)
这与我们先前在单子自同态世界中对nmoves
的定义相比较:我们现在将箭头与(..
和基格id
组合,而不是与(>=>)
和基格组合返回
。好处是我们不必将给定的Pos
注入m Pos
moveM :: Monad m => m Pos -> m Pos
moveM mp = mp >>= move
什么更有意义取决于你的情况,但通常情况下,
(>=>)
比(>>=)
干净得多。这是一个很好的解释,但我认为给出单子解释更合适。
move >=> move >=> move >=> move >=> move
nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = foldr1 (>=>) (replicate n move) -- n "move"s connected by >=>
nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = foldr (>=>) return (replicate n move)