“do”表示法中的Haskell递归

时间:2015-05-27 00:35:00

标签: haskell recursion

我正在阅读本教程http://learnyouahaskell.com/a-fistful-of-monads并偶然发现了这个定义:

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  

我有一个关于函数in3的问题(它在棋盘上获取一对坐标(Int,Int)并产生一个字段列表[(Int,Int)]可以从该字段到达3个移动一个骑士)。
是否有可能(如果是这样 - 如何做)将该功能重新制作为inNMoves :: (Num a) => KnightPos -> a -> [KnightPos]
以便它也可以作为参数的移动次数,而不是绑定到3次跳跃?

3 个答案:

答案 0 :(得分:9)

由于本练习是关于List Monad ,所以尽量不要考虑你对列表的了解,但要限制自己的monad结构。那就是

move :: Monad m => Pos -> m Pos

也就是说,move需要一个Pos,并在某些monadic context Pos中为您提供约m项内容。 (在列表的情况下,“上下文”是“任意多重性+排序”。但是尽量不要考虑它。)

另外,我们不要在这里讨论do,这只是使用(>>=)的语法糖。出于本说明的目的,您需要知道如何使用(>>=)转换为表达式。

(>>=)有签名m a -> (a -> m b) -> m b。我们需要的实例是m Pos -> (Pos -> m Pos) -> m Pos。您看到我们已将ab实例化为Pos。您还可以在此处识别中间部分(Pos -> m Pos)move的签名。因此,使用(>>=)并将move作为第二个参数,我们可以创建一个m Pos -> m Pos类型的函数。

moveM :: Monad m => m Pos -> m Pos
moveM mp = mp >>= move

monad内同胚的组成

很明显,m Pos -> m Pos可以按照您的意愿顺序执行,因为它是从类型到自身的函数(我认为可以称为 monad endomorphism ,因为type是monad)。

让我们写一个做两个动作的函数。

move2M :: Monad m => m Pos -> m Pos
move2M mp = moveM (moveM (mp))

或者采用无点样式(仅考虑转换,而不是转换后的对象):

move2M :: Monad m => m Pos -> m Pos
move2M = moveM . moveM

对于一般情况(由整数n参数化的移动数),我们只需要通过函数链运算符moveM连接一些.个。因此,如果n为3,我们需要moveM . moveM . moveM。以下是以编程方式执行此操作的方法:

nmoveM :: Monad m => Int -> m Pos -> m Pos
nmoveM n = foldr1 (.) (replicate n moveM)  -- n "moveM"s connected by (.)

这里出现一个问题:移动0次的结果是什么?对于foldr1&lt; = 0的值,n未定义。将nmoveM 0定义为“无所事事”非常有意义。换句话说,身份函数id

nmoveM :: Monad m => Int -> m Pos -> m Pos
nmoveM n = foldr (.) id (replicate n moveM)

在这里,我们使用foldr而不是foldr1,这需要额外的“基本情况”,id

现在我们基本上拥有了我们想要的东西,但是类型签名不适合100%:我们有m Pos -> m Pos,但我们需要Pos -> m Pos。这意味着,给定Pos,我们首先必须将其嵌入上下文m,然后执行nMoveM。这个嵌入运算符(我认为它可以称为初始代数)是return(类型为Monad m => a -> m a

nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = nmoveM n . return

让我们马上写下所有这些,这样你就可以充分欣赏它的光辉。

nmoves :: Monad m => Int -> Pos -> m Pos
nmoves n = foldr (.) id (replicate n move) . return

箭头的组成

使用(>>=)实际上通常有点不干净,因为它非常不对称:需要m aa -> m b。换句话说,它对转换的对象有点过于关注,而只关注我们案例的转换。这使得组合转换变得不必要地困难。这就是我们必须应对. return的原因:这是从Posm Pos的初始转换,因此我们可以自由组合任意数量的m Pos -> m Pos

使用(>>=)会产生以下模式:

ma >>= f_1 >>= f_2 >>= ... >>= f_n

其中ma是monad,而f_ia -> m b类型的“箭头”(通常a = b)。

有一个更好的变体(>=>),它在序列中组合了两个a -> m b类型箭头,并返回另一个箭头。

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)

在这里,我们并没有不必要地关注转换的对象,而只关注转换及其组合。

现在让我们同意move实际上就是这样的箭头(Pos -> m Pos)。所以

move >=> move >=> move >=> move >=> move

是仍为Pos -> m Pos类型的有效表达式。使用(>=>)时,monad的可组合性质变得更加明显。

我们可以使用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)

将此与我们之前在monad内同态世界中nmoves的定义进行比较:我们现在将箭头与{{1}组合,而不是与(.)和基本案例id结合的函数。 }和基本案例(>=>)。好处是我们不必将给定的return注入Pos

更有意义的取决于您的情况,但m Pos通常比(>=>)更清晰。

答案 1 :(得分:4)

使用直接递归:

inNMoves :: KnightPos -> Int -> [KnightPos]
inNMoves start 0 = return start
inNMoves start n = do
  first <- moveKnight start
  inNMoves first (n - 1)

但正如评论中所述:您可以使用内置函数。例如:

inNMoves start n = (foldl (>>=) . return) start (replicate n moveKnight)

甚至完全无点:

inNMoves = (. flip replicate moveKnight) . foldl (>>=) . return

答案 2 :(得分: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
      ...
    ]

现在in3可以写成

in3 xs = moves !! 3
  where moves = iterate (concatMap moveKnight) xs