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 = []
请注意,我只是约束值的符号x
,y
并rest
在GHCI.这使您可以评估where
函数块中的第一个表达式:
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中的值,例如遍历案例[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有一个更好的长期理解,而不是试图让调试器工作.
1> Mark Seemann..:
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 = []
请注意,我只是约束值的符号x
,y
并rest
在GHCI.这使您可以评估where
函数块中的第一个表达式:
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中的值,例如遍历案例[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有一个更好的长期理解,而不是试图让调试器工作.