简单说一下 Haskell 中的调试技巧

入坑差不多一个月,一直就没关注过 Haskell 中调试这一茬,一是没写什么复杂的程序,大多数都是编译错误,调调编译过了就结了。二是手头的几本书,Learn you a haskellReal World Haskell 还有 The craft of functional Programming 都没讲关于 Haskell 调试的技巧。当然,这也很符合 Haskell 比较高冷的特质。虽然都会介绍 putStrLn 这个函数,但这函数必须跟 IO Monad 一起使用,并不方便,如果用的地方多了会非常不美观。

所以一直以来遇到的运行时错误,我都是采用 BVM(Brain Virtual Machine, _)的方式来查找。

当然,BVM 也就适用于简单的程序。最近在写一个简单的 Compiler,在生成 LLVM IR 的时候遇到一个 Bug,对所有 Variable 类型的数据均会失败,返回一个 <badref>, 当尝试去 load 的时候就会报一个 casting error

在我用 BVM 在大脑中把流程演绎的千百万遍依然特喵没有找到这 bug 的时候。我彻底服了,乖乖的去学习了一下 Haskell 的调试技巧。

对于命令式编程,最基本的调试技巧就是在适当的地方插入 print 语句来把一些 context info 打印出来。那在 Haskell 中能否优雅地实现呢?

The answer is YES!

trace

GHC 其实内置了相当多的 debug 方法,具体见这里 ,这里就讲一下最简单但也是最实用的一个方法—— Debug.Trace

使用 trace 很简单,只需要在文件开头 import Debug.Trace 即可。

假设你有这样一个函数:

subStringToIndex :: String -> Int -> String
subStringToIndex s n = take n s 

故名思议,接受一个字符串 s 和一个整数 n ,返回一个 s[0..<n] 的 substring。如果这个函数不 work 。那我们首先会想到的就是要看看传进去的参数是不是正确,毕竟逻辑这么简单,不大可能在函数内部有什么bug。对此,我们只需要简单的写成这样:

subStringToIndex :: String -> Int -> String
subStringToIndex s n = trace ("para s is " ++ show s)  take n s 

运行一下,能看到这样的输出:

para s is "abcde"
ab

是不是有一种重新看到熟悉的 printf 的感觉? 一股浓浓的亲切之风铺面而来。trace 也是函数,使用非常简单,这一点可以从其原型的声明中看出来:

trace :: String -> a -> a

trace 的作用官方文档的话说,他接受一个字符串,以及一个任意类型 a 的参数, 然后打印出这个字符串,并返回 a。

什么鬼?相信我,我第一次看的时候和你有相同的感觉。

用人话来说,String 参数, 就是你要打印的,里面包含了你需要的 context info 的字符串,而 a,一般是一个表达式,或者一个函数, 这里写 a 代表其执行的结果可以是任意类型。trace 会先打印字符串,然后把第二个参数作为结果返回。

所以就程序的执行来说,trace "message : " (f x) 和直接执行 f x 效果是一样的,只是 trace 在执行前就会打印出相关的信息。

调试 do block

事实上,Haskell 程序中大多数比较复杂的逻辑都是以 monadic 的形式来写的。也就是我们熟悉的 do block, 看如下的例子:

monadicSubStringToIndex ::  Int -> State String String
monadicSubStringToIndex n = do
    s1 <- get
    traceM $ "original string is " ++ show s1
    return $ take n s1


main :: IO ()
main = putStrLn $ show $ runState (monadicSubStringToIndex 2) "abcde"

这次我们要操作的字符串不是通过参数传入了,而是 wrap 在 State Monad 里的一个字符串。如果这个时候我们要来 debug monadicSubStringToIndex 该怎么做呢?我们都知道 do block 里的语句块都接受当前的 monad 作为输入,然后也必须输出一个 monad。显然上述的 trace 并不能适用于这里的情况。

像大多数 Haskell 的函数一样,trace 也有一个 monadic 的孪生兄弟—— traceM, 这让一切都变得很简单。

我们只需要简单加一句话:

monadicSubStringToIndex ::  Int -> State String ()
monadicSubStringToIndex n = do
    s <- get
    traceM $ "original string is " ++ show s   <-- 看这里看这里
    modify $ (\s -> take n s)


main :: IO ()
main = putStrLn $ snd $ runState (monadicSubStringToIndex 2) "abcde"

运行程序,输出:

original string is "abcde"
ab

你看,我们成功的像命令式语言一样在可能会弄错的地方打 log 来帮我们 debug。虽然这并不是那么容易理解。

traceM 的定义为:

traceM :: Monad m => String -> m ()

trace 比较,traceM 只有一个参数,就是要打印的字符串,然后返回一个任意类型的 monad。但值得注意的是任何 monadic 的函数其实都是有一个隐藏 的输入参数的,那就是当前的 monad。 所以结构上, tracetraceM 是一致的。从这里也能看到普通的 Haskell 编程是以函数调用为主线,而 monadic 得写法则是以 monad 的传递为主线的。

要注意的一点是,无论是 trace 还是 traceM, 都是有副作用的。使得使用的地方不 pure, 不 referentially transparent。 简单的说他们并不是满足 Haskell 语言特性的 feature,所以仅供调试的时候使用。


想看更多内容? 可以关注我的知乎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,360评论 5 461
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,195评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,543评论 0 323
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,420评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,242评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,224评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,626评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,301评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,589评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,660评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,423评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,286评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,662评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,951评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,237评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,613评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,804评论 2 335

推荐阅读更多精彩内容