考虑下面的代码,它返回一个数字的阶乘:
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
答案 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
行动需要做两件事:
n
go
或生成结果要做两件事,我们需要一些组合器来组合它们。这里适当的是*>
:
(*>) :: Applicative f => f a -> f b -> f b
序列动作,丢弃第一个参数的值。
这正是我们所需要的,因为我们不需要使用第一个操作的值(print
操作的类型为IO ()
,因此它不会包含任何有用的结果。)
我们最后还需要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