在Haskell中调试/可视化递归函数调用的最简单方法?
我正在学习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)) &&
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列表上实现插入排序是否有意义当然是另一回事!)
trace
常常以意外的、可能混乱的顺序出现。实际上,只使用它来调试一个已经存在的大函数中的单个细节。一般来说,最好是彻底重构和单元测试。并且永远不要使用
跟踪
进行实际日志记录
注意:此解决方案过于苛刻,不适合初学者。如需实际建议,请遵循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