我是Haskell和函数式编程的新手,我有一个程序可以运行但几秒钟后溢出堆栈。我的问题是,我该怎么做?我怎样才能至少得到它出现的位置,打印堆栈或其他什么?
在ghci中使用:trace运行时程序非常慢,因此不会发生堆栈溢出。 runhaskell也不会出现这种情况,只会占用越来越多的内存。只有在使用ghc进行编译并执行时才会收到错误。
答案 0 :(得分:3)
在您的情况下,严重性问题导致堆栈溢出。找到此类问题的一种非常简单的方法是使用deepseq library。这增加了一些函数,允许您完全评估一个值(优于seq
,只有一个级别下降)。关键功能是force :: NFData a => a -> a
。这需要一个值,完全评估它,并返回它。
它仅适用于实现NFData
类型类的类型。幸运的是,deepseq-th library:deriveNFData
中有一个模板haskell宏。这与您自己的数据类型一起使用,例如deriveNFData ''BfMachine
。
要使用,请将force $
放在可能存在严格性问题的函数前面(或liftM force $
用于monadic函数)。例如,使用您的代码,我将它放在step
前面,因为这是文件中的关键功能:
{-# LANGUAGE TemplateHaskell #-}
import Data.Char
import Debug.Trace
import Control.DeepSeq
import Control.DeepSeq.TH
import Control.Monad (liftM)
type Stack = [Int]
data BfMachine = BfMachine
{ program :: String
, pc :: Int
, stack :: Stack
, sp :: Int
} deriving Show
deriveNFData ''BfMachine
setElem :: [Int] -> Int -> Int -> [Int]
setElem list n value = map (\(i, v) -> if i == n then value else v) (zip [0..] list)
step :: BfMachine -> IO (BfMachine)
step m@(BfMachine { program = program, pc = pc, stack = stack, sp = sp }) = liftM force $
case program !! pc of
'-' -> return m { pc = pc + 1, stack = setElem stack sp ((stack !! sp) - 1) }
'+' -> return m { pc = pc + 1, stack = setElem stack sp ((stack !! sp) + 1) }
'<' -> return m { pc = pc + 1, sp = sp - 1 }
'>' -> return m { pc = pc + 1, sp = sp + 1 }
'[' -> return $ if stack !! sp /= 0 then m { pc = pc + 1 }
else m { pc = (findNextBracket program $ pc + 1) + 1 }
']' -> return m { pc = findPrevBracket program $ pc - 1 }
'.' -> do putChar $ chr $ stack !! sp
return m { pc = pc + 1 }
',' -> do c <- getChar
let s' = setElem stack sp $ ord c
in return m { stack = s', pc = pc + 1 }
a -> return m { pc = pc + 1 }
findNextBracket :: String -> Int -> Int
findNextBracket program pos =
case program !! pos of
'[' -> findNextBracket program $ (findNextBracket program $ pos + 1) + 1
']' -> pos
x -> findNextBracket program (pos + 1)
findPrevBracket :: String -> Int -> Int
findPrevBracket program pos =
case program !! pos of
']' -> findPrevBracket program $ (findPrevBracket program $ pos - 1) - 1
'[' -> pos
x -> findPrevBracket program (pos - 1)
isFinished :: BfMachine -> Bool
isFinished m@(BfMachine { program = p, pc = pc })
| pc == length p = True
| otherwise = False
run :: BfMachine -> IO ()
run m = do
if isFinished m then
return ()
else do
m <- step m
run m
fib = ">++++++++++>+>+[ [+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[ [-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- [>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>> ]<<< ] This program doesn't terminate; you will have to kill it. Daniel B Cristofani (cristofdathevanetdotcom) http://www.hevanet.com/cristofd/brainfuck/"
main = run BfMachine { program = fib , pc = 0, stack = replicate 1024 0, sp = 0 }
这实际上解决了这个问题 - 即使在运行几分钟后,它也没有崩溃,内存使用量仅为3.2MB。
您可以坚持使用该解决方案,或者尝试找出真正的严格性问题(因为这会使一切严格)。您可以通过从step
函数中删除力并在其使用的辅助函数上尝试它来执行此操作(例如setElem
,findPrevBacket
等)。事实证明setElem
是罪魁祸首,将force
放在该函数前面也解决了严格性问题。我猜这是因为地图lambda中的if
意味着大多数值永远不必在列表中进行评估,并且可能随着程序的继续而产生巨大的thunk。
答案 1 :(得分:1)
答案 2 :(得分:1)
最简单的策略是使用跟踪功能。例如,考虑这个功能:
badFunction :: Int -> Int
badFunction x
| x < 10 = x * 2
| x == 15 = badFunction 480
| even x = badFunction $ x `div` 2
| odd x = badFunction $ x + 1
main = print . badFunction . read . head =<< getArgs
例如,如果您运行./program 13
,则会获得42
。但是,如果运行./program 29
,则会出现堆栈溢出。
要对此进行调试,请为每个案例放置trace
语句(来自Debug.Trace
):
badFunction :: Int -> Int
badFunction x
| x < 10 = trace ("badF -> small " ++ show x) x * 6
| x == 15 = trace "badF -> x == 15" $ badFunction 480
| even x = trace ("badF -> even " ++ show x) $ badFunction $ x `div` 2
| odd x = trace ("badF -> odd " ++ show x) badFunction $ x + 1
trace
具有类型String -> a -> a
,并打印给定的字符串,然后返回第二个参数的值。它是一个特殊功能,因为它在纯函数中执行IO。它非常适合调试。
在这种情况下,现在使用./program 19
运行程序,您将获得输出:
badF -> odd 19
badF -> even 20
badF -> even 10
badF -> small 5
30
准确显示所谓的内容。
如果您现在使用./program 29
运行它,则会获得:
badF -> odd 29
badF -> even 30
badF -> x == 15
badF -> even 960
badF -> even 480
badF -> even 240
badF -> even 120
badF -> even 60
badF -> even 30
badF -> x == 15
badF -> even 960
badF -> even 480
badF -> even 240
badF -> even 120
badF -> even 60
badF -> even 30
badF -> x == 15
badF -> even 960
badF -> even 480
badF -> even 240
badF -> even 120
badF -> even 60
badF -> even 30
badF -> x == 15
badF -> even 960
....
这很清楚地显示了循环是如何发生的。虽然在这个例子中很明显问题出在哪里,但它对于更复杂的函数很有用(特别是如果堆栈溢出涉及多个函数 - 只需对你怀疑可能存在问题的所有函数执行此操作)。