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

在Haskell中调试/可视化递归函数调用的最简单方法?,haskell,functional-programming,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 我喜欢这样: miniSort:: (Eq(a), Ord(a)) => Int -> [a] -> [a] miniSort j list = if (list !! j) < (list !! (j-1)) &&

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

while j > 0 and A[j-1] > A[j]
    swap A[j] and A[j-1]
    j ← j - 1
end while
我喜欢这样:

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
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]
在Haskell上,通过Writer Monad插入日志功能要困难得多。我甚至没有尝试,因为这会是一个混乱,然后当我想清理日志记录的东西,这将是另一个混乱

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

例如:

miniSort 3 [1,2,4,3,7,8,5]
将扩展到如下内容:

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
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]
miniSort 3[1,2,4,3,7,8,5]=if(3)<4&&3>0
然后是miniSort(2)[1,2,3,4,7,8,5]
小端口2[1,2,3,4,7,8,5]=如果(3)<2&&2>0
else[1,2,3,4,7,8,5]
[1,2,3,4,7,8,5]
正如Fyodor Soikin所说,将调试消息插入到任意代码中,在计算应用调试消息的语句时,将打印这些代码

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
看,任何地方都没有索引。更不用说在这里出错了。
(首先在Haskell列表上实现插入排序是否有意义当然是另一回事!)

  • 因为(非一元的)Haskell实际上没有指定任何求值顺序,
    trace
    常常以意外的、可能混乱的顺序出现。实际上,只使用它来调试一个已经存在的大函数中的单个细节。一般来说,最好是彻底重构和单元测试。
    并且永远不要使用
    跟踪
    进行实际日志记录

  • 使用writer monad不仅解决了这些问题,还使再次删除此类语句变得更容易(而且更可靠)。首先,您根本不需要这样做,因为您可以忽略日志数据,使用带有虚拟日志的多态monad,这样可以优化它,避免一开始就生成它,等等。。如果您确实删除了这些语句并取消了对类型的编写,那么类型检查器将突出显示您忘记执行该操作的任何位置


  • 注意:此解决方案过于苛刻,不适合初学者。如需实际建议,请遵循leftaroundabout的答案

    我们可以将minisort定义为一元函数,但它在单元上是多态的。此外,我们将使用“开放递归”来定义它,这意味着函数接收自己的递归步骤作为参数,而不是直接调用自己。这将打开一个接缝,我们可以在其中插入仪器:

    {-# LANGUAGE ScopedTypeVariables #-}
    
    -- some helper type synonyms
    type Minisort m a = Int -> [a] -> m [a]
    type Open f = f -> f
    
    minisortAux :: (Eq a, Ord a, Monad m) => Open (Minisort m a)
    minisortAux recurse j list =
        if (list !! j) < (list !! (j-1)) && j >0
            then recurse (j-1) (swapElements j (j-1) list) 
            else pure list
    
    但我们也可以对函数进行检测,使其在
    IO
    中工作,并在每次迭代时打印其参数:

    -- An Instrumentation transforms an open function into
    -- another open function with extra behaviour.
    -- Notice that instrumentations of the same type can be composed!
    type Instrumentation f = Open f -> Open f
    
    minisortIO :: forall a. (Eq a, Ord a, Show a) => Minisort IO a
    minisortIO j list = fix (instrument minisortAux) j list 
      where
        instrument :: Instrumentation (Minisort IO a)
        instrument openFunction recurse j list = 
            do print $ "starting call with params " ++ show j ++ "  " ++ show list
               r <- openFunction recurse j list
               print $ "ending call with value" ++ show r
               return r
    

    除了打印或记录内容外,我们还可以做一些事情,比如在递归过程中询问用户参数的值,甚至让用户为递归调用输入一些结果值,并避免完全执行调用。

    脱离主题,但您最好学习编写惯用的Haskell代码 而不是试图将一个不纯的迭代算法植入Haskell

    -- insert a value into a sorted list, preserving the sort
    insert :: Ord a => [a] -> a -> [a]
    insert [] y = [y]
    insert (x:xs) y | x < y = x : insert xs y
                    | otherwise = y : x : xs
    
    
    -- sort a list by repeated inserting the first item of the list
    -- into its proper place in the sorted remnant.
    insSort :: Ord a => [a] -> [a]
    insSort [] = []
    insSort (x:xs) = insert (insSort xs) x
    
    ——在排序列表中插入一个值,保留排序
    插入::Ord a=>[a]->a->[a]
    插入[]y=[y]
    插入(x:xs)y | x[a]->[a]
    insSort[]=[]
    insSort(x:xs)=插入(insSort xs)x
    

    交换的整个业务就是如何用更具命令性的语言实现
    insert
    ;这不是你在Haskell中应该怎么做的。

    看看一个解决方案(对于初学者来说,这可能是过分的,很难理解,所以请恕我直言),就是让你的函数是一元函数,但在一元函数上是多态的,并且用“匿名递归”的方式编写它使用辅助
    fix
    功能的样式。然后,您可以通过调整递归步骤来添加或删除“插装”。这里有一个例子:当strictness analyzer确定主程序从不读取时,它真的可以检查所有函数并删除日志记录吗?我希望它仍然会创建一个thunk来确定要记录的字符串,这可能比生成要记录的字符串更糟糕,因为它会在内存中保留您的域对象,以便在任何人请求时创建一个字符串。你是对的,这句话的措辞有点过于简单。的确如此。但我正在实现一个特定的算法。你认为我可以用一种更实用的方式吗?我认为该算法的本质是不起作用的。该算法是“选择一个元素,并将其插入排序列表”。您展示的命令式代码是实现该算法的一种方法。在Haskell中,您不会这样做(或者至少,不使用常规列表;可以使用可变
    IORef
    值更准确地模拟可变值)。我认为,我在这里展示的是一种实现插入排序的更实用的方法。区别在于您正在构建一个新的列表,而不是重复使用输入列表来存储输入的已排序和未排序部分。它真的是命令式的还是功能性的?我认为更多的是关于惰性列表和可变索引数组。如果有人想在Haskell中对可变向量进行排序,一些典型的命令式算法是合理的,不是吗?@chepner确实,这打开了我的思路。我很恼火,因为我的代码看起来很复杂imperative@amalloy当然,但我认为OP还没有准备好对可变向量进行排序。这很酷,我会仔细阅读,非常感谢!“功能性珍珠:哈斯克尔的装饰图案”
    import Control.Monad.Writer
    
    minisortWriter :: forall a. (Eq a, Ord a) => Minisort (Writer [(Int,[a])]) a
    minisortWriter j list = fix (instrument minisortAux) j list 
      where
        instrument :: Instrumentation (Minisort (Writer [(Int,[a])]) a)
        instrument openFunction recurse j list = 
            do tell [(j,list)]
               openFunction recurse j list
    
    -- insert a value into a sorted list, preserving the sort
    insert :: Ord a => [a] -> a -> [a]
    insert [] y = [y]
    insert (x:xs) y | x < y = x : insert xs y
                    | otherwise = y : x : xs
    
    
    -- sort a list by repeated inserting the first item of the list
    -- into its proper place in the sorted remnant.
    insSort :: Ord a => [a] -> [a]
    insSort [] = []
    insSort (x:xs) = insert (insSort xs) x