3

我学习了命令式语言的编程,主要是 C++ 和 C,所以函数式方法对我来说是非常新的。

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

这种“增量”方法的基本要素是具有诊断输出的能力(在我上面的示例中为 printf 或 std::cout)。但是在 Haskell 中(据我目前所知),如果我想使用“putStrLn”向标准输出写入内容,则必须更改函数的签名,因为“putStrLn”只返回一个 IO Monad 包含我想要打印的信息,但在调用“putStrLn”时不打印它,对吧?因此,每次我想使用“putStrLn”进行诊断输出时,我都必须更改当前函数的签名以及我所有其他函数调用它的方式等等……</p>

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

或者仅仅是我要求的事实表明我不了解 Haskell 编程的基本部分?

4

6 回答 6

13

没有什么好方法可以做你想做的事。你可以接近,Debug.Trace但我不建议在学习时这样做,因为 Haskell 的非标准评估顺序。Haskell 不能像 C 和 C++ 这样的语言通过顺序设置“变量”的值来工作。因为它是惰性的,所以 Haskell 表达式的求值顺序取决于使用情况,所以增量值的事情并没有真正起作用。

Haskell 是一种面向表达式的语言。利用它来发挥你的优势:

  1. 编写简短的函数。以这种方式更容易看到每个函数的作用。大多数函数应该是每个方程一行,真正的“一行”应该是常见的。
  2. 使用 REPL。你应该不断地在 GHCi 中试验你的代码
  3. 使用类型系统。Haskell 的类型系统比大多数命令式语言中的类型系统有用几个数量级。以机器检查的方式键入文档意图。你不能指望在不理解类型的情况下理解代码。编写代码时,一旦你得到正确的类型,你就完成了大部分工作。

结合以上建议。您可以使用:t.

于 2013-02-10T10:07:50.170 回答
9

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

  • 在编辑器的同时打开 GHCi。
  • 编写更小的、单一用途的函数。起初这似乎很奇怪,但函数应用程序是 Haskell 中的基本工作单元,并且没有命令式语言中的那种开销。
  • 每次编写函数时,:r在 GHCi 中执行并使用各种输入对其进行测试。
  • Haskell 非常密集,因此在屏幕上值得制作成单独函数的内容比您习惯的要短得多。

有时,您最终会陷入冗长的单子计算或其他事情中。GHCi 允许您设置断点 - 优先使用这些断点,而不是在代码中添加打印语句,因为您可以在不编辑代码的情况下搞砸和调查更多内容,最重要的是,您不需要在类型签名上添加 Show 约束。

完成后,您可以手动内联简短的辅助函数并使用ghc -O2.

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

摘要:尽可能避免在测试代码时对其进行编辑。大量使用 GHCi。

于 2013-02-10T14:56:33.530 回答
1

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

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

如果您需要经常使用它,这也可能表明您需要将代码分解为更小的函数,这样只需指定参数并查看返回值,就可以更轻松地检查它们的行为。

于 2013-02-10T10:03:42.297 回答
1

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

然后,您可以在计算表达式时使用tracefrom打印字符串。Debug.Trace但是请注意,由于 Haskell 使用惰性求值,在大多数情况下,表达式的求值时间与您预期的不同。另请参阅WikibooksHaskell wiki。(内部trace使用不安全的调用,即使在纯代码中也可以打印输出。通常你不应该使用它们,但在这种特殊情况下它是可以的。)

于 2013-02-10T10:07:50.410 回答
0

Approximation:

  1. GHCi debugging is the way to print local variables without cluttering your code.

  2. A WriterT monad transformer, either strict or lazy, can serialize your logs if you return your traced value, paired with the result.

{-# 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

outputs:

local1= 10
local2= 4

result= ...
于 2013-02-11T11:13:20.033 回答
0

这里有一个相当简短的示例,说明如何构建与您描述的内容相似的东西。如果我没看错,作者创建了一个简单的 monad,可以让你在计算过程中打印出来,可以这么说。

于 2013-02-10T12:03:43.017 回答