尝试将CPS应用于口译员

Mat*_*een 12 haskell continuation

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

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

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

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

我想我通常理解CPS转换:目标是在整个AST中包含所有表达式的延续.

我也有点困惑Contmonad适合这里,因为宿主语言是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
Run Code Online (Sandbox Code Playgroud)

为了评估陈述,我使用eval:

eval (Assignment (Variable var) expr) = do
    value <- evalExpr expr
    updateSymbol var value

eval (Expression e) = do
    _ <- evalExpr e
    return ()
Run Code Online (Sandbox Code Playgroud)

为了评估表达式,我使用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
Run Code Online (Sandbox Code Playgroud)

整个事情的动机是实施休息所需的诡计.中断定义是合理的,但它对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 ()
Run Code Online (Sandbox Code Playgroud)

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

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

Cac*_*tus 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
Run Code Online (Sandbox Code Playgroud)

(我还添加IO了在我的玩具解释器和State (Map Id Value)变量中轻松打印).

使用此设置,您可以编写Break并执行以下While操作:

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

以下是完整的参考代码:

{-# 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
Run Code Online (Sandbox Code Playgroud)

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

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"
       ]
Run Code Online (Sandbox Code Playgroud)

是的

i = 10
while i:
  print i
  i = i - 1
  j = 10
  while j:
    print j
    j = j - 1
    if j == 4:
      break
Run Code Online (Sandbox Code Playgroud)

所以它会打印出来

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
Run Code Online (Sandbox Code Playgroud)