bubblesort2 :: (Ord a, Show a) => [a] -> [a]
bubblesort2 [] = []
bubblesort2 [x] = [x]
bubblesort2 (x:y:rest) =
bubblesort2 (init bubbled) ++ [last bubbled]
where
(first,second) = if x > y then (y,x) else (x,y)
bubbled = first : bubblesort2(second:rest)
我试图理解上面的haskell代码。我尝试在 intellij,jetbrains haskell 插件中调试代码,但由于某种原因,它会引发调试执行错误。有什么好的方法可以通过 ide 进行调试。通过gchi进行正常调试似乎太复杂了。
最佳答案
FWIW,如果您来自面向对象的背景,您会发现使用函数式编程不需要调试器,这似乎是一种常见的体验。我不知道这是否是你的背景,但这就是我的旅程。在我编写 Haskell 代码的几年中,我从未研究过如何调试它。
我有时必须调试 F# 代码,但仅限于它与 .NET 的面向对象部分交互时。
调试器允许您通过计算检查各个阶段的变量的内部状态。当代码涉及可变状态时这是有道理的,但当一切都是不可变的并且表达式是引用透明时变得不那么重要了。
当我不理解一段 Haskell 代码时,我通常会开始分解它并使用 GHCi 中的各种子表达式。在此特定示例中,您可以执行以下操作。
首先,希望清楚当输入为 []
时会发生什么。或 [x]
:
Prelude> bubblesort2 []
[]
Prelude> bubblesort2 [42]
[42]
我假设您想要理解的代码部分是
bubblesort2 (x:y:rest)
案子。那么,我要做的就是从 []
继续前进。和 [42]
到下一个最简单的情况,您恰好有两个值:Prelude> bubblesort2 [1337,42]
[42,1337]
这对应于
bubblesort2 (x:y:rest)
案例,其中:Prelude> x = 1337
Prelude> y = 42
Prelude> rest = []
请注意,我只是将值绑定(bind)到符号
x
, y
和 rest
在 GHCi 中。这使您能够计算 where
中的第一个表达式。函数中的 block :Prelude> (first,second) = if x > y then (y,x) else (x,y)
Prelude> first
42
Prelude> second
1337
接下来你可以运行
bubblesort2(second:rest)
子表达式:Prelude> bubblesort2(second:rest)
[1337]
如果您需要提醒为什么这是结果,您甚至可以检查
second
, rest
, 和 second:rest
:Prelude> second
1337
Prelude> rest
[]
Prelude> second:rest
[1337]
迟早,您可能会意识到这是
bubblesort2 [x]
情况,这就是为什么 bubblesort2(second:rest)
返回 [1337]
.您现在应该很清楚 bubbled
是什么是,但除此之外,您也可以评估它:Prelude> bubbled = first : bubblesort2(second:rest)
Prelude> bubbled
[42,1337]
继续,您现在可以开始分解
bubblesort2
的主体了.首先,例如:Prelude> [last bubbled]
[1337]
和:
Prelude> init bubbled
[42]
所以,再次,
bubblesort2 (init bubbled)
匹配 bubblesort2 [x]
案例,以便您获得:Prelude> bubblesort2 (init bubbled)
[42]
最后:
Prelude> bubblesort2 (init bubbled) ++ [last bubbled]
[42,1337]
通过这些步骤,您应该能够理解列表恰好包含两个元素的情况。一旦这对您有用,您就可以继续将 GHCi 中的值重新绑定(bind)到例如浏览案例
[1337, 42, 12345]
:Prelude> x = 1337
Prelude> y = 42
Prelude> rest = [12345]
Prelude> (x:y:rest)
[1337,42,12345]
我不打算带您完成这个案例,但我希望您能清楚地了解如何以与上述相同的方式完成它。
我想我知道你会说什么:
每次我必须调试时都必须这样做吗?!
根据我的经验,当您开始使用 Haskell 或其他函数式编程语言时,有很多您不理解的地方,并且您会经常感到需要使用调试器。
这只是一个阶段,它会过去的。
在 REPL 中使用代码是一种更惯用的函数式编程方法,一旦你习惯了它,你就会倾向于始终打开一个 REPL。在谈到 Haskell 和 F# 时,这对我来说都是正确的,而且我听到其他函数式程序员也这么说。 Clojure 程序员似乎也是如此。
需要明确的是,这些天来,我很少觉得需要在上面概述的详细级别上单步执行 Haskell 代码。通常,只有一两个我觉得难以理解的表达式,然后我只是将其隔离并在 GHCi 中使用它,直到我理解发生了什么。
我认为,在 GHCi 中解决问题会让你对 Haskell 有更好的长期理解,而不是试图让调试器工作。
关于debugging - intellij中的haskell调试插件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48208827/