Mat*_*een 12 haskell continuation
我正在尝试使用CPS来简化我的Python解释器中的控制流实现.具体来说,在实现return/ break/时continue,我必须手动存储状态和展开,这很乏味.我已经读过以这种方式实现异常处理非常棘手.我想要的是每个eval函数能够将控制流程引导到下一条指令或完全不同的指令.
一些比我更有经验的人建议将CPS作为一种正确处理这一问题的方法.我真的很喜欢它如何简化解释器中的控制流程,但我不确定为了实现这一点我需要做多少实际操作.
我需要在AST上运行CPS转换吗?我应该将这个AST降低到较小的较低级别的IR,然后进行转换吗?
我是否需要更新评估者以接受各地的成功延续?(我这样假设).
我想我通常理解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)