monadic函数中的尾递归

时间:2013-09-27 13:09:23

标签: haskell monads tail-recursion

我想在尾递归函数中转换下面的genEdges函数。

genEdges :: Int -> Node -> IO [Edge]
genEdges n origin | n == 0 = return []
                  | otherwise =  do
                      edge <- genRandEdge origin
                      edges <- genEdges (n-1) (snd edge)
                      return $ edge : edges

我的意思是,最后一行应该是

return $ edge : genEdges (n-1) (snd edge)

虽然我知道edgegenEdges (n-1) (snd edge)的类型不同,因此这个示例行不正确。

这可能吗?如果是这样,该功能应该如何?如果没有,为什么?

3 个答案:

答案 0 :(得分:2)

这是不可能的。你的尾巴真的是

(genRange origin) >>= (\edge -> .....)

因此它不是递归的。

-----------编辑更新的问题-----------------

一般情况下,如果您有类似

的内容
do
  a <- foo
  bar

你无法使尾部递归出来。这是因为这变得卑鄙于

foo >>= (\a -> bar)

so(&gt;&gt; =)(或(&gt;&gt;))是尾调用。

答案 1 :(得分:1)

修改

看看Ingo的回答他是对的,你可能必须将生成器传递给函数然后你可以使用纯函数来获取下一个随机数并将它生成的gen传递给下一个递归函数调用。

结束编辑

你可以创建一个具有累加器的辅助函数,就像这样。我没有编译器,所以我无法保证它完全正确。

genEdges :: Int -> Node -> IO [Edge]
genEdges n o = genEdgesTR n o []
    where genEdgesRec n origin acc
          | n == 0  = return acc
          | otherwise   = do
            edge <- get RandEdge orgin
            genEdgesRec (n-1) (snd edge) (edge : acc)

这可能是用foldM或其他东西写的,但这取决于你要弄明白。

答案 2 :(得分:1)

如果你最终使用IO或其他monad / monad变换器,例如RandT(其他选项正在传递生成器和/或预生成的无限列表),这里有一个使用状态monad的示例变换器,以帮助origin的线程:

import Control.Monad.State

data Node = Node
type Edge = ((), Node)

genRandEdge :: Node -> IO Edge
genRandEdge = undefined

genEdges :: Int -> Node -> IO [Edge]
genEdges n = evalStateT $ replicateM n $ do
        origin <- get
        edge <- liftIO $ genRandEdge origin
        put $ snd edge
        return edge

为了帮助您更好地genRandEdge以避免IO,我们需要更多上下文,因此请随意提出另一个问题,了解如何改进您的生成方法。