解释器中的Monad Transformers使用问题求助
解决方案建议
针对你在Monad Transformer重构中遇到的Builtin类型不匹配问题,这里提供几个可行的优化方案:
方案1:实现Eval到EvalT m的lift函数(可行且直接)
你之前的思路是对的,Eval本质上就是EvalT Identity(因为原来的State是StateT Identity的别名),所以可以写一个转换函数把Eval a提升到任意EvalT m a:
import Control.Monad.Trans.State.Strict (runState, StateT(..)) import Control.Monad.Trans.Except (ExceptT(..)) import Control.Monad.Trans.Reader (ReaderT(..)) liftEval :: Eval a -> EvalT m a liftEval (ReaderT r) = ReaderT $ \env -> ExceptT $ fmap Right $ StateT $ \s -> return $ runState (runExceptT (r env)) s
这个函数的逻辑是逐层拆解原有的monad栈:
- 先取出
ReaderT的内容,传入环境env得到ExceptT String (State ProgState) a - 再取出
ExceptT的内容,用runExceptT转成State ProgState (Either String a) - 最后把
State转成StateT m,通过return把runState的结果包装成m类型的计算
这样原来的Builtin函数返回Eval Value,现在只需要在eval中调用liftEval (f x)就能得到EvalT m Value,完美匹配重构后的eval类型签名。
方案2:用约束同义词简化Value m的参数化冗余
如果你选择把Value改成Value m = Value' (EvalT m),可以通过定义约束同义词来减少类型签名的冗余:
-- 定义一个约束,封装EvalT m所需的基础monad要求 type EvalCtx m = Monad m -- 用约束同义词简化函数签名 eval :: EvalCtx m => Expr -> EvalT m (Value m) eval (Apply l r) = do lVal <- eval l rVal <- eval r case lVal of Builtin f -> f rVal -- 其他分支处理...
虽然还是需要参数化m,但通过EvalCtx可以把多个约束(比如后续需要MonadIO的话,直接加到EvalCtx里)统一管理,避免重复写一堆约束。
方案3:用Rank-N Types让Builtin支持任意monad(进阶)
如果想彻底避免在Value中暴露m参数,可以使用Rank-N Types让Builtin的函数对任意符合条件的m都生效:
{-# LANGUAGE RankNTypes #-} data Value = IntVal Int -- ... 其他构造器 | Builtin (forall m. EvalCtx m => Value -> EvalT m Value) eval :: EvalCtx m => Expr -> EvalT m Value eval (Apply l r) = do lVal <- eval l rVal <- eval r case lVal of Builtin f -> f rVal -- ...
这种方式下,Value不再需要参数化m,所有Builtin函数都必须能在任意EvalCtx m的环境下工作。但要注意,这种写法会增加类型系统的复杂度,而且如果你的Builtin需要依赖特定的m(比如IO),就需要在EvalCtx中添加对应的约束(比如MonadIO m)。
内容的提问来源于stack exchange,提问作者idka
相关产品推荐
相关产品推荐

