Haskell中的诊断输出

时间:2013-02-10 09:50:36

标签: haskell

我学习了命令式语言的编程,主要是C ++和C语言,所以功能方法对我来说很新鲜。

当我以前编写函数/方法时,我通常采用“增量”方法(可能大多数人都这样做):编写一小部分代码,然后检查到目前为止结果是否符合预期(通常只是用printf或std :: cout将它们打印到stdout,改进算法,增强算法,然后检查到目前为止的结果是否符合预期(通常只需用printf或std :: cout将它们打印到stdout),精炼......我很少把整个方法写成一个整体。

这种“增量”方法的关键是具有诊断输出的能力(在上面的例子中是printf或std :: cout)。但是在Haskell中(据我现在所理解的),如果我想 - 比如说 - 使用'putStrLn'向stdout写一些东西,我必须改变我的函数的签名,因为'putStrLn'只返回一个IO Monad包含我想要打印的信息,但是在调用'putStrLn'时不打印它,对吧?因此,每次我想使用'putStrLn'进行诊断输出时,我都必须更改当前函数的签名以及我所有其他函数调用它的方式等...

有没有一种便宜又简单的方法将函数的'局部变量'的值打印到标准输出?

或者仅仅是因为我要求这个标志我不理解Haskell编程的基本部分?

6 个答案:

答案 0 :(得分:13)

没有好办法做你想做的事。你可以接近Debug.Trace,但我不建议在学习时因为Haskell的非标准评估顺序。 Haskell不能像C和C ++这样的语言顺序设置“变量”的值。因为它是惰性的,所以Haskell表达式按照依赖于使用的顺序进行评估,因此增量值的东西并不真正起作用。

Haskell是一种面向表达式的语言。使用它对您有利:

  1. 写短函数。通过这种方式更容易看出每个函数的作用。大多数函数应该是每个方程一行,并且真正的“一个衬里”应该是常见的。
  2. 使用REPL。您应该不断尝试使用GHCi中的代码
  3. 使用类型系统。 Haskell的类型系统比大多数命令式语言中的类型系统更有用。类型以机器检查方式记录文档意图。如果不了解类型,就不能指望理解代码。编写代码时,一旦获得了正确的类型,就可以完成大部分工作。
  4. 结合以上建议。您可以使用:t获取GHCi中表达式的类型。

答案 1 :(得分:8)

这很奇怪,因为我发现你习惯使用的语言中没有Read Eval Print Loop(REPL),我一直很沮丧,因为我一直在测试代码的工作量。 REPL是我增量代码开发的基础;您可以使用它来测试代码,而无需添加一堆打印语句。

  • 让GHCi与您的编辑同时开放。
  • 编写较小的单用途功能。这看起来很古怪,但函数应用程序是Haskell中的基本工作单元,并且没有你在命令式语言中获得的那种开销。
  • 每次编写函数时,请在GHCi中执行:r并使用各种输入进行测试。
  • Haskell非常密集,因此在屏幕上制作单独的功能要比你习惯的要短得多。

偶尔你会陷入漫长的monadic计算或其他事情。 GHCi允许您设置断点 - 优先使用这些断点来向代码中添加print语句,因为您可以在不编辑代码的情况下搞砸并进行更多调查,最重要的是,您不需要在类型签名上添加Show约束。 / p>

完成后,您可以手动无意中内联辅助函数并使用ghc -O2进行编译。

(根据我的经验,使用手动添加的打印语句或Debug.Trace模块是完全痛苦的。)

摘要:尽可能避免在测试时编辑代码。使用GHCi很多。

答案 2 :(得分:1)

可以使用Debug.Trace模块的trace函数快速将不纯的调试输出添加到纯函数中。它是一个函数,它返回第二个参数,并在强制第二个参数/返回值时打印第一个参数。

我认为暂时使用它进行调试是完全可以接受的,只要它不会在任何最终提交或其他永久代码中结束。此外,打印消息的顺序与评估顺序相匹配,这对调试也很有用,但并不总是输出的首选顺序。

如果您需要经常使用它,它也可能表示您需要将代码分解为更小的函数,这样可以通过仅指定参数和查看返回值来更轻松地检查其行为。

答案 3 :(得分:1)

首先,您可以通过将函数加载到ghci并在那里使用它们来调试函数。

然后,您可以使用Debug.Trace中的trace在计算表达式时打印字符串。但请注意,由于Haskell使用延迟评估,因此在大多数情况下,表达式将在不同于您期望的时间进行评估。另请参阅WikibooksHaskell wiki。 (内部trace使用不安全的调用,即使在纯代码中也允许它打印输出。通常你不应该使用它们,但在这种特殊情况下它是可以的。)

答案 4 :(得分:0)

如何构建与您描述的内容类似的内容,有一个相当简短的示例here。如果我正确地阅读它,作者创建了一个简单的monad,允许你在计算过程中打印出来,可以这么说。

答案 5 :(得分:0)

逼近:

  1. GHCi调试是打印局部变量而不会使代码混乱的方法。

  2. 如果您返回跟踪值并与结果配对,WriterT monad变换器(严格或惰性)可以序列化您的日志。

  3. {-# LANGUAGE PackageImports #-}
    -- import qualified "transformers" Control.Monad.Trans.Writer.Strict as W
    import qualified "transformers" Control.Monad.Trans.Writer.Lazy as W
    
    compute:: Int -> Int -> (Int, Int)
    compute x y = (result, local)
      where
        local = 2 * x
        result = local + y
    
    test :: (Monad m) => W.WriterT String m Int
    test = do
      let (r1, local1) = compute 5 3
      W.tell $ "local1= " ++ show local1 ++ "\n"
    
      let (r2, local2) = compute 2 2
      W.tell $ "local2= " ++ show local2 ++ "\n"
    
      return $ r1 + r2
    
    main = do
      (r, logs) <- W.runWriterT test
      putStrLn logs
      putStrLn $ "result= " ++ show r
    

    输出:

    local1= 10
    local2= 4
    
    result= ...