Haskell 中奇怪的变量绑定

Alp*_*aft 1 haskell

我在 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 程序,因为你不能像大多数其他语言那样到处写一些“打印”行?

Nou*_*are 5

您对两个不同的变量使用相同的名称:executionState。这是具有唯一名称的函数版本:

\n
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\n
Run Code Online (Sandbox Code Playgroud)\n

如果您-Wall通过运行ghc -Wall MyFile.hs或放置{-# OPTIONS_GHC -Wall #-}在文件顶部来启用该选项,您将收到有关此问题的自动警告(称为“遮蔽”):

\n
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\n
Run Code Online (Sandbox Code Playgroud)\n

PS您可以使用以下函数更轻松地进行调试Debug.Trace

\n
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...\n
Run Code Online (Sandbox Code Playgroud)\n

这样你就可以删除IO.

\n

  • 可能值得一提的是启用警告来报告阴影。 (2认同)