在Haskell中进行简单调试

gpfsuwkq  于 2023-03-08  发布在  其他
关注(0)|答案(6)|浏览(205)

我是Haskell的新手。以前我用Python和Java编程。当我调试一些代码时,我有一个习惯,就是在代码中间乱放一些print语句。然而,在Haskell中这样做会改变语义,我将不得不把我的函数签名改为那些带有IO的东西。Haskellers如何处理这个问题?我可能遗漏了一些明显的东西。请给予启发。

mbzjlibv

mbzjlibv1#

其他答案将official doco和Haskell wiki联系起来,但如果你已经找到了这个答案,让我们假设你出于某种原因拒绝了这些答案。wikibook也有一个使用斐波那契的示例,我发现它更容易理解。这是一个故意的基本示例,希望能有所帮助。
假设我们从这个非常简单的函数开始,出于重要的业务原因,它将“bob”添加到字符串,然后反转它。

bobreverse x = reverse ("bob" ++ x)

GHCI中的输出:

> bobreverse "jill"
"llijbob"

我们看不出这怎么可能出错,但确实有问题,所以我们添加了debug。

import Debug.Trace

bobreverse x = trace ("DEBUG: bobreverse" ++ show x) (reverse ("bob" ++ x))

输出:

> bobreverse "jill"
"DEBUG: bobreverse "jill"
llijbob"

我们使用show只是为了确保x在输出之前被正确地转换为字符串,我们还添加了一些括号以确保参数被正确地分组。
总之,trace函数是一个装饰器,它打印第一个参数并返回第二个参数。它看起来像一个纯函数,所以你不需要在函数中引入IO或其他签名来使用它。它通过欺骗来实现这一点,如果你好奇的话,可以在上面的链接文档中进一步解释。

exdqitrt

exdqitrt2#

Read this。可以使用Debug.Trace.trace代替print语句。

ny6fqffe

ny6fqffe3#

我能够创建一个双重人格IO/ST monad类型类,它将在一元计算类型为IO时打印调试语句,在类型为ST时打印调试语句。Haskell -- dual personality IO / ST monad?.
当然Debug.Trace更多的是一把瑞士军刀,特别是当用一个有用的特殊情况 Package 时,

trace2 :: Show a => [Char] -> a -> a
trace2 name x = trace (name ++ ": " ++ show x) x

可以像(trace2 "first arg" 3) + 4一样使用

编辑

如果需要源位置,您可以将其设置得更好

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Syntax as TH
import Debug.Trace

withLocation :: Q Exp -> Q Exp
withLocation f = do
    let error = locationString =<< location
    appE f error
    where
        locationString :: Loc -> Q Exp
        locationString loc = do
            litE $ stringL $ formatLoc loc

formatLoc :: Loc -> String
formatLoc loc = let file = loc_filename loc
                    (line, col) = loc_start loc
                in concat [file, ":", show line, ":", show col]

trace3' (loc :: String) msg x =
    trace2 ('[' : loc ++ "] " ++ msg) x
trace3 = withLocation [| trace3' |]

然后,在一个单独的文件中[与上面的定义不同],您可以写入

{-# LANGUAGE TemplateHaskell #-}
tr3 x = $trace3 "hello" x

然后测试一下

> tr3 4
[MyFile.hs:2:9] hello: 4
xa9qqrwz

xa9qqrwz4#

您可以使用Debug.Trace来实现这一点。

xoefb8l8

xoefb8l85#

我真的很喜欢唐斯关于它的短博客:https://donsbot.wordpress.com/2007/11/14/no-more-exceptions-debugging-haskell-code-with-ghci/
简而言之:使用ghci,例如一个代码名为HsColour.hs的程序

$ ghci HsColour.hs
    *Main> :set -fbreak-on-exception
    *Main> :set args "source.hs"

现在,在跟踪打开的情况下运行程序,GHCi将在调用error时停止程序:

*Main> :trace main
    Stopped at (exception thrown)

好的,很好。我们遇到了一个例外......让我们稍微后退一点,看看我们现在在程序中使用(我知道这很奇怪)“:back”命令进行时间回溯:

[(exception thrown)] *Main> :back
    Logged breakpoint at Language/Haskell/HsColour/Classify.hs:(19,0)-(31,46)
    _result :: [String]

这告诉我们,在遇到error之前,我们在第19行的Language/Haskell/HsColour/Classify.hs文件中。现在我们的状态很好。让我们看看具体位置:

[-1: Language/Haskell/HsColour/Classify.hs:(19,0)-(31,46)] *Main> :list
    18  chunk :: String -> [String]
        vv
    19  chunk []    = head []
    20  chunk ('\r':s) = chunk s -- get rid of DOS newline stuff
    21  chunk ('\n':s) = "\n": chunk s
                                       ^^
f87krz0w

f87krz0w6#

这可能是对这个问题的一个非正统的回答,但是,让我简单地说:

  • 问:如何在Haskell中进行典型的“打印”风格的代码调试?
  • 答:不要这样做。不要在Haskell中做“打印”式的调试。不要在任何编程语言中这样做!学习如何不这样做!

我有一个习惯,就是在代码中间乱放print语句。
这是一个非常不好的习惯,很多编程学生在学习如何进行心理代码演练时养成了这个习惯(当然我自己也养成了这个习惯)。这个习惯留在学生身上,而且很难把它弄出来。为了确保代码的正确性并使您能够了解正在发生的事情。
不应该在你的代码中乱放print语句。在任何编程语言中。你应该写测试,而不是乱放print语句。在Haskell的例子中,你应该学习写doctest,或者unit tests with hspec。使用测试来帮助你理解什么可行,什么不可行,写测试来确保你的函数的正确性。
要理解Haskell代码,请使用GHCI。使用交互式解释器来玩函数。加载您正在使用的模块,并使用GHCI交互式地“检查它”。
当然,在某些情况下,调试**是必须的。所有其他优秀的答案会给予你一些关于如何在Haskell中进行调试的见解。但是,在Haskell中调试是一个苛刻的情妇,根据我个人的经验,最好避免。除非你真的,真的需要这样做。

相关问题