尝试将CPS应用于口译员

时间:2014-08-18 14:35:15

标签: haskell continuation

我正在尝试使用CPS来简化Python解释器中的控制流实现。具体来说,在实施return / break / continue时,我必须手动存储状态和展开,这很乏味。我已经读过以这种方式实现异常处理非常棘手。我想要的是每个eval函数能够将控制流程引导到下一条指令或完全不同的指令。

一些比我更有经验的人建议将CPS作为一种妥善处理此问题的方法。我真的很喜欢它如何简化解释器中的控制流程,但我不确定为了实现这一点我需要做多少工作。

  1. 我是否需要在AST上运行CPS转换?我应该将这个AST降低到较小的较低级别的IR,然后转换它吗?

  2. 我是否需要更新评估者以接受所有地方的成功延续? (我假设是这样)。

  3. 我认为我通常理解CPS转换:目标是将整个AST的延续线程包括在内,包括所有表达式。

    我也有点困惑Cont monad适合这里,因为宿主语言是Haskell。

    编辑:这是AST的精简版本。它是Python语句,表达式和内置值的1-1映射。

    data Statement
        = Assignment Expression Expression
        | Expression Expression
        | Break
        | While Expression [Statement]
    
    data Expression
        | Attribute Expression String
        | Constant Value
    
    data Value
        = String String
        | Int Integer
        | None
    

    为了评估陈述,我使用eval

    eval (Assignment (Variable var) expr) = do
        value <- evalExpr expr
        updateSymbol var value
    
    eval (Expression e) = do
        _ <- evalExpr e
        return ()
    

    要评估表达式,我使用evalExpr

    evalExpr (Attribute target name) = do
        receiver <- evalExpr target
        attribute <- getAttr name receiver
        case attribute of
            Just v  -> return v
            Nothing -> fail $ "No attribute " ++ name
    
    evalExpr (Constant c) = return c
    

    整个事情的动机是实施休息所需要的恶作剧。中断定义是合理的,但它对while定义的作用有点多:

    eval (Break) = do
        env <- get
        when (loopLevel env <= 0) (fail "Can only break in a loop!")
        put env { flow = Breaking }
    
    eval (While condition block) = do
        setup
        loop
        cleanup
    
        where
            setup = do
                env <- get
                let level = loopLevel env
                put env { loopLevel = level + 1 }
    
            loop = do
                env <- get
                result <- evalExpr condition
                when (isTruthy result && flow env == Next) $ do
                    evalBlock block
    
                    -- Pretty ugly! Eat continue.
                    updatedEnv <- get
                    when (flow updatedEnv == Continuing) $ put updatedEnv { flow = Next }
    
                    loop
    
            cleanup = do
                env <- get
                let level = loopLevel env
                put env { loopLevel = level - 1 }
    
                case flow env of
                    Breaking    -> put env { flow = Next }
                    Continuing  -> put env { flow = Next }
                    _           -> return ()
    

    我确信在这里可以做更多的简化,但核心问题是在某个地方填充状态并手动清理。我希望CPS能让我记录簿(如循环退出点)进入状态,并在需要时使用它们。

    我不喜欢语句和表达式之间的分歧,并担心它可能会使CPS变换更加有效。

1 个答案:

答案 0 :(得分:10)

这最终给了我一个尝试使用ContT的好借口!

这里有一种可能的方法:存储(在Reader中包裹在ContT中)继续退出当前(最内层)循环:

newtype M r a = M{ unM :: ContT r (ReaderT (M r ()) (StateT (Map Id Value) IO)) a }
              deriving ( Functor, Applicative, Monad
                       , MonadReader (M r ()), MonadCont, MonadState (Map Id Value)
                       , MonadIO
                       )

runM :: M a a -> IO a
runM m = evalStateT (runReaderT (runContT (unM m) return) (error "not in a loop")) M.empty

withBreakHere :: M r () -> M r ()
withBreakHere act = callCC $ \break -> local (const $ break ()) act

break :: M r ()
break = join ask

(我还添加了IO以便在我的玩具解释器中轻松打印,并State (Map Id Value)添加变量)。

使用此设置,您可以将BreakWhile写为:

eval Break = break
eval (While condition block) = withBreakHere $ fix $ \loop -> do
    result <- evalExpr condition
    unless (isTruthy result)
      break
    evalBlock block
    loop

以下是完整的参考代码:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Interp where

import Prelude hiding (break)
import Control.Applicative
import Control.Monad.Cont
import Control.Monad.State
import Control.Monad.Reader
import Data.Function
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe

type Id = String

data Statement
    = Print Expression
    | Assign Id Expression
    | Break
    | While Expression [Statement]
    | If Expression [Statement]
    deriving Show

data Expression
    = Var Id
    | Constant Value
    | Add Expression Expression
    | Not Expression
    deriving Show

data Value
    = String String
    | Int Integer
    | None
    deriving Show

data Env = Env{ loopLevel :: Int
              , flow :: Flow
              }

data Flow
    = Breaking
    | Continuing
    | Next
    deriving Eq

newtype M r a = M{ unM :: ContT r (ReaderT (M r ()) (StateT (Map Id Value) IO)) a }
              deriving ( Functor, Applicative, Monad
                       , MonadReader (M r ()), MonadCont, MonadState (Map Id Value)
                       , MonadIO
                       )

runM :: M a a -> IO a
runM m = evalStateT (runReaderT (runContT (unM m) return) (error "not in a loop")) M.empty

withBreakHere :: M r () -> M r ()
withBreakHere act = callCC $ \break -> local (const $ break ()) act

break :: M r ()
break = join ask

evalExpr :: Expression -> M r Value
evalExpr (Constant val) = return val
evalExpr (Var v) = gets $ fromMaybe err . M.lookup v
  where
    err = error $ unwords ["Variable not in scope:", show v]
evalExpr (Add e1 e2) = do
    Int val1 <- evalExpr e1
    Int val2 <- evalExpr e2
    return $ Int $ val1 + val2
evalExpr (Not e) = do
    val <- evalExpr e
    return $ if isTruthy val then None else Int 1

isTruthy (String s) = not $ null s
isTruthy (Int n) = n /= 0
isTruthy None = False

evalBlock = mapM_ eval

eval :: Statement -> M r ()
eval (Assign v e) = do
    val <- evalExpr e
    modify $ M.insert v val
eval (Print e) = do
    val <- evalExpr e
    liftIO $ print val
eval (If cond block) = do
    val <- evalExpr cond
    when (isTruthy val) $
      evalBlock block
eval Break = break
eval (While condition block) = withBreakHere $ fix $ \loop -> do
    result <- evalExpr condition
    unless (isTruthy result)
      break
    evalBlock block
    loop

这是一个简洁的测试示例:

prog = [ Assign "i" $ Constant $ Int 10
       , While (Var "i") [ Print (Var "i")
                         , Assign "i" (Add (Var "i") (Constant $ Int (-1)))
                         , Assign "j" $ Constant $ Int 10
                         , While (Var "j") [ Print (Var "j")
                                           , Assign "j" (Add (Var "j") (Constant $ Int (-1)))
                                           , If (Not (Add (Var "j") (Constant $ Int (-4)))) [ Break ]
                                           ]
                         ]
       , Print $ Constant $ String "Done"
       ]

i = 10
while i:
  print i
  i = i - 1
  j = 10
  while j:
    print j
    j = j - 1
    if j == 4:
      break

所以它会打印

10 10 9 8 7 6 5
 9 10 9 8 7 6 5
 8 10 9 8 7 6 5
...
 1 10 9 8 7 6 5