【问题标题】:Error trying to call putStrLn in function尝试在函数中调用 putStrLn 时出错
【发布时间】:2010-10-15 15:13:43
【问题描述】:

我正在尝试在 Haskell 函数中调用“打印输出”函数。

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

下面是我的代码和来自编译器 (GHC 6.10) 的错误消息。

我不太明白为什么它将putStr 调用和空数组混为一谈。

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

知道为什么这不起作用吗?

我的代码:

isAFactor :: 整数 -> 整数 -> 布尔 isAFactor x y = x `mod` y == 0 findFactors :: 整数 -> 整数 -> [整数] findFactors 计数器编号 = 让商 = div num 2 在 如果(计数器>商) 然后做 putStrLn ("factorList is : " ++ show quotient) (*** Line 10***) [] else if(isAFactor num 计数器) 然后 [counter] ++ [quotient] ++ findFactors (counter + 1) num 别的 findFactors (counter + 1) 数量

来自 ghc 的错误

测试.hs:10:4: 无法匹配预期类型 `[a] -> [Integer]' 针对推断类型‘IO()’ 在表达式中: putStrLn("factorList is :" ++ show quotient) [] 在表达式中: do putStrLn("factorList is :" ++ show quotient) [] 在表达式中: 如果(计数器>商)那么 do putStrLn("factorList is :" ++ show quotient) [] 别的 if (isAFactor num counter) then [counter] ++ [quotient] ++ findFactors (counter + 1) num 别的 findFactors (counter + 1) 数量

【问题讨论】:

  • 我相信这是因为findFactors是一个纯函数,所以它可以没有副作用(比如IO)。这就是 Haskell 的全部意义所在。不过,我不知道如何调试您的功能。在 C++ 中,我确实一直使用调试日志记录。
  • "do" 并不是你认为的意思。它只是 monad 排序的语法糖;你实际上不能在 Haskell 中“做”任何事情。 :-)

标签: debugging haskell


【解决方案1】:

重要的是要记住 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

正如 cmets 建议的那样:

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

由于 haskell 是一种非常不同的语言,最好尝试以同样不同的方式开发程序。尝试推理您的函数,而不是使用trace 和类似的“不纯”构造。学习利用 haskells 强大的类型系统,并在函数通过类型检查后使用(例如)QuickCheck 对其进行测试。

【讨论】:

  • 值得注意的是,因为 Haskell 是一种惰性语言,所以这些 trace 调用也是惰性发生的。因为没有明确的操作顺序,对trace 的调用可能看起来不正常,甚至根本没有发生。请记住,您正在处理一种惰性语言,在调试时也是如此。
  • 当我尝试使用它时,我得到一个错误说error: [-Wdeprecations, -Werror=deprecations] In the use of ‘trace’ (imported from Protolude, but defined in Protolude.Debug): "'trace' remains in code"我正在使用这个命令来构建:stack build --copy-bins --local-bin-path /usr/local/bin
  • 好的,我不得不从我的 Protolude 导入中隐藏 trace 并改为 import Debug.Trace (trace)
【解决方案2】:

更新说明

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

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

一般来说,因为 Haskell 是一门带有 monadic IO 的纯函数式语言,一旦你进入 IO monad,就没有出路,它具有传染性。即,如果一个函数有一个 do 块,其中包含 IO 操作,它的签名必然包含 IO _ 类型,所有其他调用此函数的函数的签名也是如此,等等。(其他 monad 确实提供“exit " 函数,但 IO monad 没有。)

【讨论】:

  • 它并没有真正解释为 putStrLn 的最后一个参数是吗?这是 (>>=) 的第二个参数。 do块不一定是IO(),do块可以有很多不同的类型,包括[a]!
  • 您在这里有许多误解。我讨厌对认真的帖子投反对票,但这掩盖的比它揭示的要多。
  • @cthulahoops:错误消息“在表达式“putStrLn ("factorList is :" ++ show quotient) []"中似乎表明我的解释是正确的。
  • @Peter:接受批评,改写整个答案以使自己更清楚。请重新考虑您的投票。
【解决方案3】:

Jonas 的帖子很好地涵盖了您的问题,因此我将按照惯用方式重写您的 findFactors 函数。当我第一次学习时,我发现它对我很有帮助。

因此,您希望通过查看从 1 到 n/2 的每个数字来查找给定数字 n 的所有因数,检查它是否是 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 -O2 似乎意识到了这一点并且只计算了一次)。其次,在任何地方都必须处理那个计数器变量有点烦人。第三,它仍然非常必要。

查看问题的另一种方法是获取从1 到n/2 的整数列表,并过滤出那些是n 的因数。这可以直接转化为 Haskell:

findFactors :: Integer -> [Integer]
findFactors num = filter (isAFactor num) [1..(num `div` 2)]

发现它与上述版本具有相同的性能特征可能会令人惊讶。 Haskell 不需要一次为整个列表分配内存,最多可达n/2,它可以根据需要生成每个值。

【讨论】:

    【解决方案4】:

    其他人已经很好地解释了你的代码,所以让我帮你解码错误信息。

    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] -> [Integer]。那是错误消息的“预期类型”部分。

    此时编译器断定无法匹配这两种类型,所以报错。

    "Couldn't match expected type 'Foo' against inferred type 'Bar'" 可能是您在尝试编译 Haskell 时收到的最常见的错误消息,因此它值得尝试阅读并理解它。查看推断的类型并尝试找出引用表达式的哪个部分具有该类型。然后尝试通过查看周围的代码来找出编译器期望其他内容的原因。

    【讨论】:

    • +1,很好地解释了错误信息。我无法弄清楚代码如何报告它所做的错误。直到我才意识到提问者插入了一个换行符来插入“第 10 行”注释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 2019-12-02
    • 1970-01-01
    相关资源
    最近更新 更多