【发布时间】:2012-04-08 13:58:22
【问题描述】:
foo:: Int -> Int -> Int
foo z x = if (z < 100)
then z * foo (z+(x*z)) z
else z
每次从自身调用时如何打印输出(整数 z)?你能有返回 IO 和 Int 的函数吗?您需要辅助功能吗?
【问题讨论】:
foo:: Int -> Int -> Int
foo z x = if (z < 100)
then z * foo (z+(x*z)) z
else z
每次从自身调用时如何打印输出(整数 z)?你能有返回 IO 和 Int 的函数吗?您需要辅助功能吗?
【问题讨论】:
任何执行 I/O 的函数都必须在 IO monad 中返回其结果:
foo :: Int -> Int -> IO Int
foo z x = print z >> if z < 100 then fmap (z*) (foo (z + x * z) z) else return z
注意if 表达式的两个分支现在也必须在IO 中。
这与返回“IO 和 Int”不同。 IO Int 是表示 I/O 操作的值的类型,当执行该操作时,将产生 Int 作为其结果(可能在执行一些 I/O 之后)。所以上面对foo 的定义接受了一个Int 和一个Int 并返回一个I/O 操作,该操作最终将导致Int。
【讨论】:
为简单起见,您可以使用trace。但是,它不适合真正的生产代码,因为它破坏了引用透明度。
trace 需要一个 String 来打印并返回一个值。
import Debug.Trace
foo:: Int -> Int -> Int
foo z x = trace ("z = " ++ show z) $ if (z < 100)
then z * foo (z+(x*z)) z
else z
*Main> foo 1 2
z = 1
z = 3
z = 6
z = 24
z = 168
72576
【讨论】:
为了完整起见,我将回答这个问题:
你能有返回 IO 和 Int 的函数吗?
...字面意思。答案是“是的!”……它有时甚至很有用。这可能不是您作为初学者想要做的事情,但如果是的话,这里有一个示例。
foo :: Int -> Int -> (IO (), Int)
foo z x = if z < 100 then (print z >> io, z * rec) else (return (), z) where
(io, rec) = foo (z+x*z) z
例如,您可以通过设置打印递归调用
main = fst $ foo 13 7
或者你可以通过设置打印答案
main = print . snd $ foo 13 7
或其他六种东西。当然,IO () 类型有点难以检查;你可以考虑写这样的东西:
foo' :: Int -> Int -> Writer [Int] Int
foo' z x = if z < 100
then tell [z] >> fmap (z*) (foo' (z+x*z) z)
else return z
使用它与上面的非常相似,但添加了一个额外的runWriter;例如,您可以编写以下两种中的任何一种:
main = print . snd . runWriter $ foo' 13 7 -- to print a list of the calling values
main = print . fst . runWriter $ foo' 13 7 -- to print the result
此方法的优点是您可以返回一个调用值列表,而不是打印该列表的 IO 操作,因此您可以以许多更有趣的方式处理调用。
【讨论】:
Writer 之类的东西。
Writer 与列表一起使用而不是DList 对性能的影响?
基于@is7s 的回答,使用Debug.Trace 的一个有用的习惯用法是这样做:
import Debug.Trace
foo:: Int -> Int -> Int
foo z x | trace ("z = " ++ show z) False = undefined
foo z x = if (z < 100)
then z * foo (z+(x*z)) z
else z
在这里,我们在评估为False 的守卫中引入了foo 和trace 的定义,因此它将始终落入原始定义。这样,我们就不会干扰我们的函数,并且可以通过注释掉该行来打开或关闭跟踪。
【讨论】: