2013-02-10 63 views
3

我学习了命令式编程语言,主要是C++和C语言,所以功能方法对我来说是非常新的。当我在写函数/方法之前,我通常采用“增量”方法(可能大多数人都这样做):编写一小部分代码,然后检查结果是否如预期的那样(通常由只是用printf或std :: cout将它们打印到stdout中),改进算法,增强算法,然后检查到目前为止的结果是否如预期的那样(通常只需将它们打印到标准输出或标准输出),精炼...我很少写完整的方法。Haskell诊断输出

基本这个“增量”的方法是有诊断输出(printf的或std :: COUT在我的例子以上)的能力。但是在Haskell中(据我了解截至目前),如果我想 - 比如说 - 使用'putStrLn'将某些内容写入标准输出,我必须更改函数的签名,因为'putStrLn'只返回一个IO Monad包含我想要打印的信息,但不会在调用'putStrLn'时打印它,对吗?所以每次我想使用'putStrLn'进行诊断输出时,我都必须更改当前函数的签名以及所有其他函数的调用方式等。

所以有一种便宜且简单的方法将函数的“局部变量”的值打印到标准输出中?

或者仅仅是这样一个事实:我要求的是一个标志,我不了解Haskell编程的基础部分?

回答

8

这很奇怪,因为我发现没有使用过的语言的阅读评估打印循环(REPE),我总是感到沮丧,因为我一直在测试我的代码有多少工作。 REPL是我增量代码开发的基础。您可以使用它来测试您的代码而不需要必须添加一堆打印语句。

  • 让GHCi与您的编辑器同时打开。
  • 编写更小,单一用途的功能。这看起来似乎很滑稽,但函数应用程序是Haskell的基本工作单元,并没有在命令式语言中获得的那种开销。
  • 每次您编写函数时,都要在GHCi中执行:r并用各种输入进行测试。
  • Haskell非常密集,因此屏幕上显示的内容比您习惯的短得多。

偶尔你最终陷入漫长的单子计算或其他事情。 GHCi允许您设置断点 - 优先使用这些代码将打印语句添加到代码中,因为您可以在不编辑代码的情况下进行更多调查和调查,最重要的是,您不需要在类型签名中添加显示约束。

完成后,您可以手动内联免费短助手功能并使用ghc -O2进行编译。

(使用手动添加打印语句,或者Debug.Trace模块是在我的经验相比,这是一个完整的疼痛。)

摘要:只要有可能,避免编辑您的代码,而测试它。使用GHCi很多。

1

它能够迅速地不纯调试输出加到使用Debug.Trace模块的trace功能的纯函数。它是一个函数,当第二个参数/返回值被强制时,返回第二个参数,并带有打印第一个参数的附加副作用。

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

如果您需要使用这个很多时候也可能是你需要系数代码为更小的功能,这使得它更容易被刚刚指定的参数来看,在返回值检查自己的行为的迹象。

13

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

Haskell是一个表达取向的语言。用它来你的优势:

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

结合上述建议。您可以通过:t获得GHCi中表达式的类型。

+0

但并不意味着我将有很多功能,因此命名空间将满的功能呢?因为在函数中局部定义许多辅助函数使得它们无法从外部访问,因此很难尝试...另外,为这些函数找到有意义的函数名将更加困难,并且我看到了Haskell中保留函数名称的趋势简短...这一切都让我困惑:)在C++中,例如,尽量保持“本地”和尽可能封装... – Sh4pe 2013-02-10 11:51:57

+1

在Haskell中,我们使用模块和导出列表进行封装。为函数寻找有意义的名字并不比通过一些练习找到局部变量的名字更困难。我认为Haskell的趋势就是保持经常使用的函数名称简短。我经常对模块内部函数有很长的描述性名称,这些名字只用于我目前正在使用的代码中的一个例子'boundedValueBlockTraversal'。我也倾向于完整地记录这些功能,这会产生非常易读且易于维护的代码。 – jix 2013-02-10 19:00:33

1

首先,您可以通过将它们加载到ghci并在那里玩它们来调试您的功能。

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

+0

为了更加明确,“这个特例”就是发展。 'Debug.Trace'在生产代码中没有地位。 – amindfv 2013-02-11 02:11:37

0

有怎样一个相当短的例子here去构建一些与你描述的东西类似的东西。如果我正确地阅读它,作者创建了一个简单的monad,可以让您在计算过程中打印出来,可以这么说。

0

逼近:

  1. GHCI调试打印局部变量而不会干扰你的代码的方式。

  2. WriterT monad变换器,无论是严格还是懒惰,都可以对日志进行序列化,如果您返回跟踪值并与结果配对。

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