我正在学习Monad变形金刚,并决定使用Monad Transformers为类似于Brainfuck的简单语言(具有循环结构)编写一个解释器。我想在一定数量的语句后终止解释器。
这种简单的语言是由单个存储单元构成的,该存储单元能够存储一个Int和5条指令Input,Output,Increment,Decrement和Loop。当存储器中的值为零时,循环终止。从列表中读取输入,类似地,将输出写入另一个列表。增量和减量分别对内存执行+1和-1。
我使用World
类型来跟踪输入,输出(流)和内存,Sum Int
用来计算所评估的指令数。 Except World
在某些语句后终止评估。
module Transformers where
import qualified Data.Map as Map
import Data.Maybe
import Control.Monad.State.Lazy
import Control.Monad.Writer.Lazy
import Control.Monad.Except
data Term = Input
| Output
| Increment
| Decrement
| Loop [Term]
deriving (Show)
data World = World {
inp :: [Int],
out :: [Int],
mem :: Int
} deriving Show
op_limit = 5
loop
:: [Term]
-> StateT World (WriterT (Sum Int) (Except World)) ()
-> StateT World (WriterT (Sum Int) (Except World)) ()
loop terms sp = sp >> do
s <- get
if mem s == 0 then put s else loop terms (foldM (\_ t -> eval t) () terms)
limit :: StateT World (WriterT (Sum Int) (Except World)) ()
limit = do
(s, count) <- listen get
when (count >= op_limit) $ throwError s
tick :: StateT World (WriterT (Sum Int) (Except World)) ()
tick = tell 1
eval :: Term -> StateT World (WriterT (Sum Int) (Except World)) ()
eval Input =
limit >> tick >> modify (\s -> s { inp = tail (inp s), mem = head (inp s) })
eval Output = limit >> tick >> modify (\s -> s { out = mem s : out s })
eval Increment = limit >> tick >> modify (\s -> s { mem = mem s + 1 })
eval Decrement = limit >> tick >> modify (\s -> s { mem = mem s - 1 })
eval (Loop terms) = loop terms (void get)
type Instructions = [Term]
interp :: Instructions -> World -> Either World (World, Sum Int)
interp insts w =
let sp = foldM (\_ inst -> eval inst) () insts
in runExcept (runWriterT (execStateT sp w))
示例在ghci中运行:
*Transformers> interp [Loop [Output, Decrement]] $ World [] [] 5
Right (World {inp = [], out = [1,2,3,4,5], mem = 0},Sum {getSum = 10})
单子limit
基于计数,应决定以当前状态失败还是不执行任何操作。但是我注意到count
中的(s, count) <- listen get
始终为零。我不明白为什么会这样。请帮助我了解我出了什么问题。
答案 0 :(得分:3)
Writer
monad内部的计算无法访问其自己的累加器。更重要的是:在运行计算时,甚至从未强制累加器,甚至没有强制WHNF。这同时适用于{em> {em> 和Writer
的strict和lazy变体,严格变体在某种意义上是严格的,与累加器无关。如果计算运行时间过长,累加器中这种不可避免的惰性会导致空间泄漏。
您的limit
函数不在“ mainline” WriterT
累加器的值上分支。 get
操作(您正在使用mtl)仅从StateT
层读取状态,而在其他层则不起作用:它将mempty
添加到其WriterT
累加器中不会抛出错误。
然后,listen
提取Writer
动作的get
累加器(仅get
,而不是整个计算)并将其添加到“主行”中”蓄能器。但是此提取的值(在元组中返回的值)将始终为mempty
,即Sum 0
!
就像@chi提到的那样,您可以将计数器置于WriterT
状态,而不是StateT
。您还可以使用AccumT
,它与WriterT
非常相似,但是您可以检查累加器(也可以使用爆炸模式将其强制为WHNF)。
AccumT
似乎没有对应的mtl类型类,所以您需要洒一些升降机才能使用它。