haskell:尝试在函数中调用putStrLn时出错

时间:2009-03-26 04:48:13

标签: debugging haskell

我正在尝试在haskell函数中调用'print out'函数。

(一个简单的调试消息)。

以下是我的编译器代码和错误消息(ghc 6.10)。

我不太明白为什么它会把puttr调用和空数组混为一谈。

空数组是该特定情况的返回值(打印输出消息实际上只是一个存根)。

知道为什么这不起作用?

由于

我的代码:

isAFactor :: Integer -> Integer -> Bool 
isAFactor x y = x `mod` y == 0

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
            then do
                putStrLn ("factorList is  : " ++ show quotient)  (*** Line 10***)
                []
        else if(isAFactor num counter)
            then [counter] ++ [quotient] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

来自ghc的错误

    test.hs:10:4:
    Couldn't match expected type `[a] -> [Integer]'
           against inferred type `IO ()'
    In the expression:
        putStrLn ("factorList is  : " ++ show quotient) []
    In the expression:
        do putStrLn ("factorList is  : " ++ show quotient) []
    In the expression:
        if (counter > quotient) then
            do putStrLn ("factorList is  : " ++ show quotient) []
        else
            if (isAFactor num counter) then
                  [counter] ++ [quotient] ++ findFactors (counter + 1) num
            else
                findFactors (counter + 1) num

4 个答案:

答案 0 :(得分:20)

重要的是要记住Haskell是一种pure函数式语言。这意味着不允许函数产生任何副作用,包括将调试消息打印到屏幕上。

然而,可以打破这种纯度,它可以用于调试。看看模块Debug.Trace。在那里你会找到一个函数trace :: String -> a -> a。您可以在代码中使用它,如下所示:

import Debug.Trace

isAFactor :: Integer -> Integer -> Bool 
isAFactor x y = x `mod` y == 0

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
                then trace ("factorList is: " ++ show quotient) [] 
        else if(isAFactor num counter)
            then [counter] ++ [quotient] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

如评论所示:

Haskell也是一种lazy语言。在实际需要结果之前不评估表达式。在延迟设置中使用跟踪功能可能会有点混乱,因为跟踪消息打印到屏幕时(如果它完全打印)并不总是很容易理解。

由于haskell是一种非常不同的语言,因此最好以同样不同的方式尝试开发程序。尝试推断您的功能,而不是使用trace和类似的“不纯”结构。学习利用haskells强大的类型系统并使用(例如)QuickCheck来测试你的函数,一旦它通过了类型检查器。

答案 1 :(得分:9)

Jonas的帖子很好地涵盖了你的问题,所以我会给你一个惯用的重写你的findFactors函数。我第一次学习时发现它对我有帮助。

因此,您希望通过查看n1中的每个数字来查找给定数字n/2的所有因素,并检查它是否为{{1}的因子并建立一个列表。

您的版本(只需稍加修改即可开始使用):

n

进行了一些格式更改,使其更具可读性:

findFactors :: Integer -> Integer -> [Integer]
findFactors counter num = 
    let quotient = div num 2
    in
        if(counter >  quotient)
            then []
        else if(isAFactor num counter)
            then [counter] ++ findFactors (counter + 1) num
        else
            findFactors (counter + 1) num

这很好,但在某些方面它并不理想。首先,它会在每次调用findFactors :: Integer -> Integer -> [Integer] findFactors counter num | counter > div num 2 = [] | otherwise = if num `isAFactor` counter then counter:findFactors (counter+1) num else findFactors (counter + 1) num 时重新计算商数,即findFactors个分区(尽管n/2似乎意识到这一点并且只计算一次)。其次,到处处理这个计数器变量有点烦人。第三,它仍然非常必要。

另一种查看问题的方法是从ghc -O21获取整数列表,并过滤那些n/2因子的整数。这直接转换为Haskell:

n

发现它具有与上述版本相同的性能特征可能会令人惊讶。 Haskell不需要立即为整个列表分配内存findFactors :: Integer -> [Integer] findFactors num = filter (isAFactor num) [1..(num `div` 2)] ,它可以根据需要生成每个值。

答案 2 :(得分:8)

其他人在解释您的代码方面做得很好,所以让我帮您解码错误信息。

Couldn't match expected type `[a] -> [Integer]'
       against inferred type `IO ()'
In the expression:
    putStrLn ("factorList is  : " ++ show quotient) []

我错过了所有其他“在表达式”中的部分;他们只是展示了越来越多的封闭背景。

Haskell中的所有内容都是一个表达式,因此所有内容都有一个类型。这包括像“putStrLn”这样的东西。如果你在GHCi中输入“:t putStrLn”,你会看到它回复:

putStrLn :: String -> IO ()

这意味着putStrLn是一个接受字符串并返回“IO动作”的函数,在这种情况下是将消息放在屏幕上的动作。在你的代码中,你给了“putStrLn”一个字符串,因此编译器推断表达式“putStrLn( stuff )”的类型为“IO()”。这是编译器错误消息的“推断类型”部分。

与此同时,编译器也在外部进行另一个方向的类型推断。除此之外,它注意到这个“putStrLn( stuff )”表达式似乎应用于空列表,其类型为“[a]”(即某个列表,我们不知道是什么)。此外,整个表达式的结果应该是“[Integer]”类型。因此,表达式“putStrLn( stuff )”应该是将“[]”变成整数列表的函数,其类型写为“[a] - > [整数]”。这是错误消息的“预期类型”部分。

此时编译器得出结论,它无法匹配这两种类型,因此报告了错误。

“无法匹配预期类型' Foo '与推断类型' Bar '”可能是您在尝试编译Haskell时获得的最常见错误消息,因此其值得尝试阅读并理解它。查看推断类型并尝试找出引用表达式的哪个部分具有该类型。然后通过查看周围的代码,尝试找出编译器为什么期望别的东西。

答案 3 :(得分:3)

更新了说明

问题是Haskell中的IO是monadic,以do开头的块是用于将monadic表达式(有时称为语句)与monadic运算符组合在一起的语法糖。在这种情况下,有问题的monad是IO monad,可以从对putStrLn的调用中推断出来。 []块的第二行中的do实际上是而不是整个do块的值,而是被解释为putStrLn的最后一个参数;并不是说它接受第二个参数,但是编译器甚至没有达到解决这个问题的程度,因为它会在你引用的类型错误之前终止。要使该行成为命令,您必须在其前面放置另一个monadic函数(例如return [])。不过,我不是说这可以帮助你解决问题。

类型错误源于IO monadic表达式始终具有类型IO _;在您的情况下,do块也具有此类型,这显然与您在签名中指定的类型[Integer]不兼容。

一般来说,因为Haskell是一个带有monadic IO的纯函数式语言,所以一旦你进入IO monad,就没有办法了,它是有意义的。也就是说,如果一个函数有一个带有IO操作的do块,它的签名必然包含IO _类型,所有其他函数的签名也会包含这个函数,等等。(其他monad)确实提供“退出”功能,但IO monad没有。)