我有关于调试功能的问题。我应该定义下面某些命令的执行,然后我必须创建一个调试函数,在每次调用执行后递归打印出当前的内存值。
这是我的exec(执行),调试和数据类型的代码
data Com = Ass Char Exp
| While Exp Com
| Seq Com Com
deriving Show
exec :: Memory -> Com -> Memory
exec m (Ass c e) = update m (c, (eval m e))
exec m (While e c) = if (eval m e) == 0 then m
else exec (exec m c) (While e c)
exec m (Seq c1 c2) = exec (exec m c1) c2
debug :: Memory -> Com -> [Memory]
debug m (Ass c e) = update m (c, (eval m e)) : [m]
debug m (Seq c1 c2) = (exec (exec m c1) c2) :[m]
debug m (While e c) = if (eval m e) == 0 then [m]
else (exec (exec m c) (While e c)) : [m]
com1 :: Com
com1 = Seq (Ass 'z' (Num 1))
(While (Var 'y') (Seq (Ass 'z' (Mul (Var 'z') (Var 'y')))
(Ass 'y' (Add (Var 'y') (Num (-1))))))
当我将函数运行到某些内存状态和命令时,它只打印出启动内存和终止内存,例如,如果我运行debug [('y',4)] com1
,我得到的只是[[('y',0),('z',24)],[('y',4)]]
而我需要它来打印
[('y',4)]
[('y',4),('z',1)]
[('y',4),('z',4)]
[('y',3),('z',4)]
[('y',3),('z',12)]
[('y',2),('z',12)]
[('y',2),('z',24)]
[('y',1),('z',24)]
[('y',1),('z',24)]
[('y',0),('z',24)]
我想问一下我在调试函数中需要更改什么才能让它以递归方式打印的问题?
答案 0 :(得分:6)
比较您的exec
和debug
函数,我们可以看到为什么debug
没有打印出所有中间状态:它只需要exec
返回的状态和将它附加到原始记忆状态m
。
由于exec
没有保存中间内存状态,并且debug
基本上已经exec
,我建议只重写debug
以递归方式调用自身,而不是{ {1}}。另外,让我们将exec
的返回类型更改为debug
- 第一个组件将是所有先前状态的列表,第二个组件将是最新结果。 (严格来说,由于最新的结果是历史的一部分,因此不需要,但它将有助于保持我们的代码更清洁,更通用 - 我们可以轻松地更改([Memory], Memory)
以保留除了记忆历史记录,例如执行的操作记录。)
无论如何,我们得到这样的东西:
debug
我会把剩下的留给你 - 这不是太难。
这不是太糟糕。但是,仍有一些问题:
debug :: Memory -> Com -> ([Memory], Memory)
debug m (Ass c e) = ([m'],m')
where m' = update m (c, eval m e)
,这有点可怕 - 我们可以轻松地结束一个程序,花费时间在日志的长度上二次方(例如,如果我们的日志是反向写的按时间顺序排列,我们会反复将早期状态附加到长列表的末尾。)++
。上面的程序是函数式编程“模式”的一个典型例子,称为线程化状态,我们在函数调用的返回类型中添加一些信息来存储有关我们历史的信息。程序。实际上,您可以将此程序视为线程化两种类型的状态:我们检查和修改的内存,以及我们只写入但从未读过的日志。
当然,函数式编程都是关于抽象的,并且有众所周知的方法来解决上述问题并消除“线程化”模式:monads!
您可以使用 Writer monad 来消除日志的显式线程。
奖励:您可以使用状态monad 并使用它来消除内存状态的显式线程。
您可以使用差异列表来消除对exec
的潜在代价的使用。
要了解所有这些主题,我建议使用penultimate chapter of Learn You a Haskell,之后您应该可以使用标准库中定义的monad。
答案 1 :(得分:2)
您的debug
函数正在调用exec
(不执行任何日志记录)。相反,您希望它以递归方式调用debug
。
这是一个了解Writer monad的好机会,这对于“除了计算值之外还产生数据流的计算有用”。
答案 2 :(得分:1)
这可能有点矫枉过正,但无论如何......
首先,我把它放在州monad中,所以很清楚你正在做什么。
type MemState a = State Memory a
type EvalVal = ... -- result of eval
-- You'd better define those two right /instead/ of 'eval' and 'update'
eval' :: Exp -> MemState EvalVal
eval' e = state $ \m -> (eval m e, m)
update' :: Char -> EvalVal -> MemState ()
update' c v = state $ \m -> ((), update m (c, v))
exec :: Com -> MemState ()
exec (Ass c e) = eval' e >>= update' c
exec whileCom@(While e c) = do
v <- eval'
when (v /= 0) $ do
exec c
exec whileCom
exec (Seq c1 c2) = do
exec c1
exec c2
现在,正如user5402已经建议的那样,可以通过添加Writer
来完成调试:
type DebugState a = WriterT [Memory] MemState a
tellMemory :: DebugState ()
tellMemory = do
m <- lift . state $ \m' -> (m',m')
tell [m]
debug :: Com -> DebugState ()
debug com@(Ass _ _) = do
tellMemory
lift $ exec com
tellMemory
debug (Seq c1 c2) = do
tellMemory
debug c1
debug c2
debug com@(While e c) = do
tellMemory
v <- lift eval'
when (v /= 0) $ do
debug c
debug com