量化约束下SomeMaybe的Semigroup实例编译失败原因及疑问
为带量化约束的GADT实现Semigroup实例时的编译错误
你定义了如下GADT并尝试实现Semigroup实例:
{-# LANGUAGE QuantifiedConstraints #-} data SomeMaybe c t where SomeNothing :: SomeMaybe c t SomeJust :: c t => t -> SomeMaybe c t instance (forall b. c b => Semigroup b) => Semigroup (SomeMaybe c u) where x <> y = case x of SomeNothing -> y SomeJust x' -> case y of SomeNothing -> x SomeJust y' -> SomeJust (x' <> y')
编译时会报错,提示无法从forall b. c b => Semigroup b上下文推导出c (SomeMaybe c u),错误根源在于Semigroup类的默认sconcat实现。
错误原因
Semigroup的默认sconcat实现依赖类的内置逻辑,GHC推导该实现时,会错误地认为需要SomeMaybe c u本身满足c约束——但我们的实例设计完全不需要这个条件:<>只要求SomeJust包裹的t在满足c t时是Semigroup,SomeMaybe自身不需要符合c约束。默认实现的推导路径触发了不必要的约束检查,最终导致编译失败。
解决方法:手动实现sconcat和stimes
直接手动实现这两个方法,绕开默认实现的约束推导问题即可。示例代码如下:
{-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE GADTs #-} data SomeMaybe c t where SomeNothing :: SomeMaybe c t SomeJust :: c t => t -> SomeMaybe c t instance (forall b. c b => Semigroup b) => Semigroup (SomeMaybe c u) where x <> y = case x of SomeNothing -> y SomeJust x' -> case y of SomeNothing -> x SomeJust y' -> SomeJust (x' <> y') -- 手动实现sconcat,用自定义的<>折叠元素 sconcat (a :| as) = foldl (<>) a as -- 手动实现stimes,贴合SomeMaybe的语义处理 stimes n x = case x of SomeNothing -> SomeNothing SomeJust x' -> SomeJust (stimes n x')
为什么单独定义semigroupOp能解决问题
手动实现sconcat和stimes后,GHC不会再调用默认实现,也就不会去推导那个多余的c (SomeMaybe c u)约束。我们的手动实现完全贴合SomeMaybe的语义:
sconcat直接用已定义的<>折叠NonEmpty列表中的元素,不需要SomeMaybe满足任何额外约束stimes把操作传递给SomeJust包裹的t(而t满足c t => Semigroup t,可以安全调用stimes),SomeNothing则直接返回自身
内容的提问来源于stack exchange,提问作者Clinton
相关产品推荐
相关产品推荐

