Haskell-Monad变形金刚-口译员的评估数量限制

时间:2018-11-25 18:31:30

标签: haskell monad-transformers state-monad

我正在学习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始终为零。我不明白为什么会这样。请帮助我了解我出了什么问题。

  • 我堆叠中的变压器订购是否正确?是否有任何规则(非正式)来决定分层?

1 个答案:

答案 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类型类,所以您需要洒一些升降机才能使用它。