Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Debugging 如何";“调试”;哈斯克尔和printfs?_Debugging_Haskell_Trace_Printf Debugging - Fatal编程技术网

Debugging 如何";“调试”;哈斯克尔和printfs?

Debugging 如何";“调试”;哈斯克尔和printfs?,debugging,haskell,trace,printf-debugging,Debugging,Haskell,Trace,Printf Debugging,来自Ocaml社区,我正在努力学习一点Haskell。转换进行得很顺利,但我对调试有点困惑。我过去常常在我的ocaml代码中放入(很多)“printf”,以检查一些中间值,或者作为一个标志来查看计算到底在哪里失败 由于printf是一个IO操作,我是否必须提升IOmonad中的所有haskell代码才能进行这种调试?或者有没有更好的方法来做这件事(如果可以避免的话,我真的不想手工做) 我还找到了跟踪功能: 这似乎正是我想要的,但我不明白它的类型:任何地方都没有IO! 有人能给我解释一下跟踪函数的

来自Ocaml社区,我正在努力学习一点Haskell。转换进行得很顺利,但我对调试有点困惑。我过去常常在我的ocaml代码中放入(很多)“printf”,以检查一些中间值,或者作为一个标志来查看计算到底在哪里失败

由于printf是一个IO操作,我是否必须提升IOmonad中的所有haskell代码才能进行这种调试?或者有没有更好的方法来做这件事(如果可以避免的话,我真的不想手工做)

我还找到了跟踪功能: 这似乎正是我想要的,但我不明白它的类型:任何地方都没有IO!
有人能给我解释一下跟踪函数的行为吗?

好吧,因为整个Haskell都是建立在惰性求值原则的基础上的(因此计算顺序实际上是不确定的),所以使用printf的意义不大


如果REPL+inspect结果值对于您的调试来说确实不够,那么将所有内容包装到IO中是唯一的选择(但这不是Haskell编程的正确方法)。

trace
只会变得不纯。
IO
monad的要点是保持纯度(没有类型系统未注意到的IO)并定义语句的执行顺序,否则通过延迟求值实际上无法定义

然而,您可以自行承担风险,将一些
IO a->a
拼凑在一起,即执行不纯净的IO。这是一种黑客行为,当然“遭受”了懒惰的评估,但这正是trace为了调试而做的事情

尽管如此,您还是应该使用其他方法进行调试:

  • 减少调试中间值的需要

    • 编写小的、可重用的、清晰的、通用的函数,其正确性是显而易见的
    • 将正确的片段组合成更大的正确片段
    • 以交互方式编写或试用作品
  • 使用断点等(基于编译器的调试)

  • 使用通用单子。如果你的代码是一元代码,那么就独立于具体的一元代码来编写它。使用
    类型M=…
    而不是普通的
    IO…
    。之后,您可以通过transformers轻松地组合monad,并在上面放置调试monad。即使对单子的需求消失了,您也可以为纯值插入
    Identity a


  • trace
    是最容易使用的调试方法。它不在
    IO
    中,正是因为您指出的原因:不需要在
    IO
    monad中提升代码。它是这样实现的

    trace :: String -> a -> a
    trace string expr = unsafePerformIO $ do
        putTraceMsg string
        return expr
    

    因此,在幕后有IO,但
    unsafePerformIO
    被用来逃避它。这是一个可能会破坏引用透明性的函数,你可以从它的类型
    ioa->a
    以及它的名称中猜出来。

    对于它的价值,这里实际上有两种“调试”存在争议:

    • 将中间值(例如特定子表达式在每次调用时具有的值)记录到递归函数中
    • 检查表达式求值的运行时行为
    在严格的命令式语言中,这些通常是一致的。在Haskell,他们通常不会:

    • 记录中间值可以更改运行时行为,例如通过强制计算否则将被丢弃的术语
    • 由于惰性和共享子表达式,实际的计算过程可能与表达式的外观结构有很大的不同
    如果您只想保留中间值的日志,有很多方法可以做到这一点——例如,与其将所有内容都提升到
    IO
    ,一个简单的
    编写器
    monad就足够了,这相当于让函数返回其实际结果的2元组和累加器值(通常是某种列表)

    通常也不需要将所有内容都放在monad中,只需要将需要写入“log”值的函数——例如,您可以只考虑可能需要进行日志记录的子表达式,保持主逻辑的纯净,然后,通过使用
    fmap
    s和诸如此类的常规方式组合纯函数和日志计算,重新组装整个计算。请记住,
    Writer
    对于monad来说是一种令人遗憾的借口:无法从日志中读取数据,只能对其进行写入,每个计算在逻辑上都独立于其上下文,这使得处理事情更容易

    但在某些情况下,这甚至有些过分——对于许多纯函数来说,只需将子表达式移动到顶层并在REPL中进行尝试就可以了


    但是,如果您想实际检查纯代码的运行时行为——例如,为了找出子表达式发散的原因——通常无法从其他纯代码中进行检查——事实上,这本质上就是纯代码的定义。因此,在这种情况下,您别无选择,只能使用纯语言“外部”存在的工具:不纯函数,如
    unsafePerformPrintfDebugging
    --errr,我的意思是
    trace
    --或修改后的运行时环境,如GHCi调试器。

    trace
    也会过度评估其打印参数,在这个过程中失去了很多懒惰的好处。

    如果您可以等到程序完成后再研究输出,那么堆叠是实现记录器的经典方法。我使用它来返回来自不纯HDBC代码的结果集

    应该注意的是,
    trace
    仅用于调试,如果您将其用于“真实”逻辑,则社区会避开它。那么,请不要这样做:-)(即仅跟踪您认为有信心强制执行的内容),因为您的两个声明都是错误的。按需调用时,计算顺序为静态所有