我在 Haskell 中有以下代码,它是 Brainfuck 解释器的一部分(当我学习一门新语言时,我最喜欢的第一个中型项目):
type Program = String
type Pointer = Int
type Stack = [Int]
data ExecutionState = ExecutionState {
programPointer :: Pointer,
program :: Program,
stackPointer :: Pointer,
stack :: Stack
} deriving (Eq, Show)
moveBackwards :: ExecutionState -> ExecutionState
moveBackwards (ExecutionState pp p sp s) = ExecutionState (pp-1) p sp s
curr :: ExecutionState -> Char
curr execState = (stack execState) !! (stackPointer execState)
currByte :: ExecutionState -> Int
currByte execState = (program execState) !! (programPointer execState)
comeBackToOpeningBracket :: ExecutionState -> IO ExecutionState
comeBackToOpeningBracket executionState = result
where
result = if currByte executionState /= 0
then comeBackToOpeningBracketWithDepth 0 (moveBackwards executionState)
else return executionState
comeBackToOpeningBracketWithDepth n executionState
| n == 0 = do
print $ map programPointer [executionState, moveBackwards executionState, prev] -- debug
case curr executionState of
'[' -> return executionState
']' -> comeBackToOpeningBracketWithDepth 1 prev
_ -> comeBackToOpeningBracketWithDepth 0 prev
| otherwise = case curr executionState of
'[' -> comeBackToOpeningBracketWithDepth (n-1) prev
']' -> comeBackToOpeningBracketWithDepth (n+1) prev
_ -> comeBackToOpeningBracketWithDepth n prev
prev = moveBackwards executionState
Run Code Online (Sandbox Code Playgroud)
如果您不知道 Brainfuck 是什么,请快速回顾一下(抱歉,可能没有使用正确的技术术语):它由一个program(连续的一些字符,每个字符都是一个单独的指令)组成,我在这里称之为programPointer(其中表示我们当前正在应用的字符或指令),a stack(实际上它是一个无限的字节数组,这里我用整数代替)和astackPointer表示我们当前正在处理哪个字节。
每条指令都有特定的效果(请求输入、更改堆栈的值等),这里是“]”程序字符的实现。它说,当你遇到它时,如果堆栈上指向的字节不为0,你就向后走,直到遇到匹配的'['。
IO ExecutionState 的返回类型需要与其他指令的返回类型相匹配(我知道这里我可以返回一个没有打印行的普通 ExecutionState,这是为了调试)。
我的程序的其余部分是其他指令的实现,这些指令似乎都按预期工作。
然而,当在 Brainfuck 程序上测试它时,打印的输出是 [37, 36, 37] ;第一个值是好的(它是 ']' 之前的 char 的位置,因为我们首先向后移动一次),第二个值是我向后移动时所期望的,但为什么第三个值是 37 ?由于我的moveBackward函数是纯函数(aaah,函数式编程),它应该打印 36 两次,不是吗?我认为这是因为一些未知的(至少对我来说)变量绑定规则(事实上有 prev = ... 然后再次使用 prev 而不是内联表达式),但我真的不明白,我不想(moveBackwards executionState)到处写,而不是首先将其绑定到 prev 并使用 prev,这样更好更短。你能解释一下这种行为吗?
谢谢 !
PS:有没有一种有效的方法来正确调试 Haskell 程序,因为你不能像大多数其他语言那样到处写一些“打印”行?
您对两个不同的变量使用相同的名称:executionState。这是具有唯一名称的函数版本:
comeBackToOpeningBracket :: ExecutionState -> IO ExecutionState\ncomeBackToOpeningBracket executionState1 = result\n where \n result = if currByte executionState1 /= 0 \n then comeBackToOpeningBracketWithDepth 0 (moveBackwards executionState1)\n else return executionState1\n\n comeBackToOpeningBracketWithDepth n executionState2\n | n == 0 = do \n print $ map programPointer [executionState2, moveBackwards executionState2, prev] -- debug\n case curr executionState2 of\n \'[\' -> return executionState2\n \']\' -> comeBackToOpeningBracketWithDepth 1 prev\n _ -> comeBackToOpeningBracketWithDepth 0 prev\n | otherwise = case curr executionState2 of\n \'[\' -> comeBackToOpeningBracketWithDepth (n-1) prev\n \']\' -> comeBackToOpeningBracketWithDepth (n+1) prev\n _ -> comeBackToOpeningBracketWithDepth n prev\n\n prev = moveBackward executionState1\nRun Code Online (Sandbox Code Playgroud)\n如果您-Wall通过运行ghc -Wall MyFile.hs或放置{-# OPTIONS_GHC -Wall #-}在文件顶部来启用该选项,您将收到有关此问题的自动警告(称为“遮蔽”):
comeBackToOpeningBracket :: ExecutionState -> IO ExecutionState\ncomeBackToOpeningBracket executionState1 = result\n where \n result = if currByte executionState1 /= 0 \n then comeBackToOpeningBracketWithDepth 0 (moveBackwards executionState1)\n else return executionState1\n\n comeBackToOpeningBracketWithDepth n executionState2\n | n == 0 = do \n print $ map programPointer [executionState2, moveBackwards executionState2, prev] -- debug\n case curr executionState2 of\n \'[\' -> return executionState2\n \']\' -> comeBackToOpeningBracketWithDepth 1 prev\n _ -> comeBackToOpeningBracketWithDepth 0 prev\n | otherwise = case curr executionState2 of\n \'[\' -> comeBackToOpeningBracketWithDepth (n-1) prev\n \']\' -> comeBackToOpeningBracketWithDepth (n+1) prev\n _ -> comeBackToOpeningBracketWithDepth n prev\n\n prev = moveBackward executionState1\nRun Code Online (Sandbox Code Playgroud)\nPS您可以使用以下函数更轻松地进行调试Debug.Trace:
import Debug.Trace (traceShow)\n\n...\n\n comeBackToOpeningBracketWithDepth n executionState2\n | n == 0 && traceShow (map programPointer [executionState2, moveBackwards executionState2, prev]) True =\n case curr executionState2 of\n \'[\' -> return executionState2\n \']\' -> comeBackToOpeningBracketWithDepth 1 prev\n _ -> comeBackToOpeningBracketWithDepth 0 prev\n...\nRun Code Online (Sandbox Code Playgroud)\n这样你就可以删除IO.