在Megaparsec中提升ParsecT单子转换器的实现疑问
Megaparsec ParsecT 类型转换与底层Monad选择问题
问题背景
在开发文本版战舰游戏、练习Lenses与MTL时,遇到Megaparsec中ParsecT单子转换器的类型转换问题,已定义以下类型:
type P = ParsecT Void String (ReaderT UI IO) UI type Q = ParsecT Void String ((->) UI) (UI -> UI,String) type Q' = ParsecT Void String ((->) UI) (UI,String)
已实现从Q到Q'的转换:
qtoq' p = do x <- ask (a,b) <- p return (a x,b)
现在需要实现反向转换qprimetop(将Q'转换为Q),理论上考虑借助reader,但看到ParsecT的内部结构由四个函数组成,希望避免直接修改这些内部字段;同时在考虑是否直接将Q的底层Monad改为ReaderT UI IO(此方案可行但需要调整已有代码)。
解决方案
1. 无需修改ParsecT内部字段实现Q'到Q的转换
ParsecT会自动提升底层Monad的MonadReader实例,因此我们可以直接利用Monad的do语法和Reader的特性完成转换,完全不需要触碰ParsecT的内部构造。
根据Q和Q'的类型差异:
Q的返回值是(UI -> UI, String):在Reader环境(接收一个UI参数)下,返回一个UI转换函数和字符串Q'的返回值是(UI, String):在Reader环境下,返回一个具体的UI值和字符串
反向转换的核心是把Q'得到的具体UI值,包装成一个忽略输入参数的UI转换函数,代码如下:
qprimetop :: Q' -> Q qprimetop p = do (uiRes, str) <- p -- 将具体UI值包装为UI->UI函数,这里函数忽略输入参数,直接返回Q'得到的UI return (\_ -> uiRes, str)
如果业务逻辑中,UI转换函数需要依赖Reader环境中的UI参数,可以调整为:
qprimetop :: Q' -> Q qprimetop p = do envUi <- ask -- 获取当前Reader环境中的UI参数 (uiRes, str) <- p -- 可基于envUi和uiRes生成自定义转换函数 return (\inputUi -> uiRes, str)
2. 关于是否切换底层Monad为ReaderT UI IO的建议
是否切换取决于业务需求:
- 如果后续解析逻辑需要执行IO操作(比如读取用户输入、打印日志等),推荐切换到
ReaderT UI IO——纯函数的((->) UI)无法处理IO,而ReaderT UI IO可以兼顾Reader环境和IO能力,扩展性更强。 - 如果所有逻辑都是纯函数式的,不需要IO,那么继续使用
((->) UI)更轻量,无需调整已有代码。
若决定切换,只需修改Q的类型定义,并将原有基于((->) UI)的代码适配到ReaderT UI IO中:
type Q = ParsecT Void String (ReaderT UI IO) (UI -> UI,String)
适配时主要注意:用liftIO执行IO操作,ask的用法和纯Reader一致,无需额外修改。
内容的提问来源于stack exchange,提问作者Ashok Kimmel
相关产品推荐
相关产品推荐

