如何在Haskell递归调用中打印迭代?

时间:2018-02-17 20:29:24

标签: haskell recursion

考虑下面的代码,它返回一个数字的阶乘:

factorial n = go n 1
  where
    go n ret | n > 1 = go (n-1) (ret * n)
             | otherwise = ret

我该如何打印' n'在" go n ret"的每一次递归调用中?由于n每次递减,我想看看我是否可以在每次递减时打印它(如5 4 3 2 1)。 这就是我试图这样做的方式(这是完全错误的),但我不能想到不同的方式:

factorial n = go n 1
  where
    go n ret | n > 1 = go (n-1) (ret * n) print n
             | otherwise = ret

3 个答案:

答案 0 :(得分:3)

这里的问题是你试图在纯函数(print)中做一些IO(factorial):你不能在Haskell中这样做。

一种解决方法是同样使用模块Debug.Trace

import Debug.Trace

factorial n = go n 1
  where
  go n ret | ("n value is: " ++ show n) `trace` n > 1 = go (n-1) (ret * n)
           | otherwise = ret

你会得到:

*Main Debug.Trace> factorial 5
n value is: 5
n value is: 4
n value is: 3
n value is: 2
n value is: 1
120

如图书馆所述,有一些警告:

  

跟踪功能仅应用于调试或用于   监控执行。该功能不是引用透明的:   它的类型表明它是一个纯函数,但它有侧面   输出跟踪消息的效果。

正如丹尼尔指出的那样,如果我们想要在评估的每个步骤中强制执行乘法,我们必须按如下方式重写:

factorial n = go n 1
  where
  go n ret | ("n value is: " ++ show n) `trace` n > 1 = go (n-1) $! (ret * n)
           | otherwise = ret

答案 1 :(得分:3)

类型

要进行打印,您需要将值提升到I / O Applicative中;类型将从

更改
factorial :: (Num a, Ord a) => a -> a

factorial :: (Num a, Ord a, Show a) => a -> IO a

go操作的作用

go行动需要做两件事:

  1. 打印号码n
  2. 以递归方式调用go或生成结果
  3. 如何顺序执行两个I / O操作

    要做两件事,我们需要一些组合器来组合它们。这里适当的是*>

      

    (*>) :: Applicative f => f a -> f b -> f b

         

    序列动作,丢弃第一个参数的值。

    这正是我们所需要的,因为我们不需要使用第一个操作的值(print操作的类型为IO (),因此它不会包含任何有用的结果。)

    将值提升为I / O

    我们最后还需要pure

      

    pure :: Applicative f => a -> f a

         

    提升价值。

    将结果提升到I / O Applicative。

    代码

    factorial n = go n 1
      where
        go n acc =
          print n *>
          if n > 1
            then let !acc' = acc * n
                 in  go (n-1) acc'
            else pure acc
    

    ! forces绑定中的acc'即时发生乘法(感谢Daniel Wagner指出需要这样做)。

    在GHCi中进行测试:

    λ> factorial 5 >>= print
    5
    4
    3
    2
    1
    120
    

答案 2 :(得分:1)

您可以使用(>>)IO行动进行排序,并使用return将纯计算转换为IO行动。所以:

factorial n = go n 1 where
    go n ret | n > 1 = print n >> go (n-1) (ret*n)
             | otherwise = return ret

当然,factorial现在是一个产生IO动作的函数;它的呼叫者可能需要修理以适应它。

另外,请注意:由于懒惰,在之后所有打印都没有进行实际的乘法操作!这可能不是你想要的。对于你在这样的微基准测试中所做的各种测试,它不会有多大关系,但是对于每次迭代进行更多计算的大型函数,你可能会开始注意到。您可以使用$!或类似方法解决此问题,以使IO操作的计算取决于此迭代工作的计算。所以:

factorial n = go n 1 where
    go n ret | n > 1 = print n >> (go (n-1) $! ret*n)
             | otherwise = return ret