gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 01:04:04 25 4
gpt4 key购买 nike

我正在尝试使用 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/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com