haskell - 试图将 CPS 应用于口译员

标签 haskell continuation

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

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

  • 我需要在 AST 上运行 CPS 转换吗?我是否应该将此 AST 降低为较小的较低级别的 IR,然后对其进行转换?
  • 我是否需要更新评估者以接受所有地方的成功延续? (我假设是这样)。

  • 我想我大致了解 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
    

    插入整个事情的是实现 break 所需的恶作剧。 break 定义是合理的,但它对 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 转换工作更多。

    最佳答案

    这终于给了我一个尝试使用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
    

    关于haskell - 试图将 CPS 应用于口译员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25365900/

    相关文章:

    haskell - 存在类型的理论基础是什么?

    scheme - 方案中的调用延续 CC

    c# - 当数组中的一个或多个任务被取消或失败时继续?

    具有字符串连接和延续的 Python str.format

    haskell - ST monad 有特殊的编译器支持吗?

    haskell - Haskell 中的 Let 与 Lambda

    haskell - "Data types à la carte"与嵌套的 FreeT 变压器

    haskell - 安装Haskell时出错无法安装ghc

    python - 如何优雅地分解一个长字符串

    java - 如何保存java程序的状态并在以后调用它?