幻影类型(Phantom type)使模式匹配不可辩驳,但在do记法中为何仍需MonadFail实例?
你的思路本身是成立的——通过GADT结合幻影类型(Phantom Type),确实能让(TI v)这种模式在类型层面成为必然匹配的不可辩驳模式,但GHC的do表达式模式匹配机制并没有自动识别这种类型安全的场景,这就是你遇到错误的核心原因。
问题根源:do表达式的模式匹配处理逻辑
GHC在处理do表达式中的pattern <- action绑定的时候,默认会将所有带构造子的模式视为可辩驳模式(refutable pattern),哪怕从类型上看不可能匹配失败。这是因为do绑定会被翻译为Monad的>>=操作,而GHC会为所有可辩驳模式生成兜底的fail调用——即使你的ti类型明确是T 'I,只能由TI构造,GHC当前的检查逻辑不会在do绑定中深入分析这种GADT的类型约束。
解决方法
你有几种方式绕过这个限制,让代码不需要MonadFail实例:
标记为不可辩驳模式
在模式前加上~前缀,明确告诉GHC这个模式不会失败:test :: Monad m => m Int test = do ~(TI v) <- return ti return v改用let绑定
正如你发现的,let语句中的模式匹配会进行更严格的类型分析。GHC会验证T 'I类型的值只能由TI构造,因此let (TI v) = ti是完全安全的,不需要MonadFail,也不会触发-Wincomplete-uni-patterns警告:test :: Monad m => m Int test = do let (TI v) = ti return v简化为纯表达式绑定
直接将逻辑转为纯函数式的形式,避免do绑定的模式检查:test :: Monad m => m Int test = return $ let (TI v) = ti in v
为什么let语句不会触发警告?
-Wincomplete-uni-patterns的作用是警告那些在单例类型(只有一种构造子的类型)上使用的可辩驳模式,但GHC在处理let绑定的时候,会深入分析类型约束:它能识别出T 'I是单例类型(只有TI构造子能生成该类型),因此(TI v)模式是完全覆盖的,不存在匹配失败的可能,所以不会发出警告。而do表达式的模式检查逻辑没有做这种类型层面的覆盖分析,因此仍会要求MonadFail实例。
内容的提问来源于stack exchange,提问作者amakarov

