在 Haskell 中调试/可视化递归函数调用的最简单方法?

Gue*_*OCs 3 haskell functional-programming

我正在学习 Haskell,我决定实现这个简单的算法来完成插入排序算法的一部分:

while j > 0 and A[j-1] > A[j]
    swap A[j] and A[j-1]
    j ? j - 1
end while
Run Code Online (Sandbox Code Playgroud)

我是这样的:

miniSort:: (Eq(a), Ord(a)) => Int -> [a] -> [a]
miniSort j list = if (list !! j) < (list !! (j-1)) && j >0
        then miniSort (j-1) (swapElements j (j-1) list) 
        else list
Run Code Online (Sandbox Code Playgroud)

做对有点困难,但我做到了(我猜)。

在命令式编程语言中查看每一步我可以简单地做

while j > 0 and A[j-1] > A[j]
    print("j is $j")
    print("swapping $A[j] with $A[j-1]")
    swap A[j] and A[j-1]
    print("swapped list: $A")
    j ? j - 1
end while
print("ended with j $j")
Run Code Online (Sandbox Code Playgroud)

在 Haskell 上,通过 Writer Monad 插入日志功能要困难得多。我什至没有尝试,因为它会一团糟,然后当我想清理日志记录时,它又会变得一团糟。

有没有办法查看 Haskell 中的函数调用分支?

例如:

miniSort 3 [1,2,4,3,7,8,5]
Run Code Online (Sandbox Code Playgroud)

会扩展到这样的:

miniSort 3 [1,2,4,3,7,8,5] = if (3) < 4 && 3 >0
        then miniSort (2) [1,2,3,4,7,8,5]
miniSort 2 [1,2,3,4,7,8,5] = if (3) < 2 && 2 >0
        else [1,2,3,4,7,8,5]
[1,2,3,4,7,8,5]
Run Code Online (Sandbox Code Playgroud)

lef*_*out 6

正如 Fyodor Soikin 所说,trace将调试消息插入到任意代码中,当你应用它的语句被评估时,这些代码会被打印出来。

import Debug.Trace
import Text.Printf

miniSort j list
 = if trace (printf "miniSort %i %s = if %i < %i && %i>0"
                              j (show list) (list!!j) (list!!(j-1)) j)
                      $ list !! j < list !! (j-1) && j>0
    then trace (printf "then miniSort %i %s" (j-1) (show list))
           miniSort (j-1) (swapElements j (j-1) list) 
    else traceShowId list
Run Code Online (Sandbox Code Playgroud)

但是,有几个警告:

  • 如果你发现自己想要一个像 sort 这样的简单算法,那你就做错了。你确实是这样——在 Haskell 中对这样的列表进行排序是没有意义的。具体来说,对列表进行索引几乎从来都不是一个好主意:它笨拙、容易出错且速度缓慢。
    好的 Haskell 代码通常使用模式匹配而不是索引等。例如,

    type SortedList a = [a]
    
    insertion :: Ord a => a -> SortedList a -> SortedList a
    insertion n (x:xs)
     | n>x    = x : insertion n xs
    insertion n xs = n : xs
    
    insertSort :: Ord a => [a] -> SortedList a
    insertSort [] = []
    insertSort (x:xs) = insertion x $ insertSort xs
    
    Run Code Online (Sandbox Code Playgroud)

    看,任何地方都没有索引。更不可能在这里出错。
    (首先在 Haskell 列表上实现插入排序是否有意义当然是另一回事!)

  • 因为(非 monadic)Haskell 并没有真正指定任何评估顺序,trace所以经常会出现意想不到的,可能是混乱的顺序。真的,只用它来调试一个大的、已经存在的函数中间的单个细节。一般来说,最好彻底重构和单元测试。
    从未使用trace的实际记录!

  • 使用 writer monad 不仅可以解决这些问题,还可以更容易(也更可靠)再次删除这些语句。一方面,您根本不需要这样做,因为您可以忽略日志数据,使用带有虚拟日志记录的多态 monad 将优化它而不是首先生成,等等。如果您这样做删除语句并取消编写类型,然后类型检查器将突出显示您忘记这样做的任何地方。