如何让Haskell类型系统识别复合类型的函数依赖,无需UndecidableInstances?
问题
我写了一段代码,用来把类HList的元组树转换成相同结构,但只保留元组的第二个元素。现在这个代码的实例需要启用UndecidableInstances扩展,错误提示左侧类型a :. c无法确定右侧类型b :. d,但逻辑上这应该是成立的。有没有类型技巧能让Haskell类型系统识别a :. c可以确定b :. d,从而消除错误和对该扩展的依赖?
代码如下:
class TupSnd a b | a -> b where tupSnd :: a -> b instance TupSnd PGTup (Only Action) where tupSnd a = Only (snd a) -- 该实例据称需要UndecidableInstances,但或许有类型技巧可解决? -- -- 错误信息: -- -- 原因:左侧类型‘a :. c’无法确定右侧类型‘b :. d’ -- -- 这在逻辑上似乎不应该出现。 instance (TupSnd a b, TupSnd c d) => TupSnd (a :. c) (b :. d) where tupSnd (a :. c) = tupSnd a :. tupSnd c
上下文补充:`:.’是postgresql-simple库中的增强型元组构造辅助器,我还定义了以下类型:
type PGTup = (Identifier, Action) type PGTup2 = PGTup :. PGTup type PGTup2_2 = PGTup2 :. PGTup2 type PGTup3 = PGTup :. PGTup2 type PGTup4 = PGTup :. PGTup3
解决方案
问题根源在于Haskell默认的实例合法性检查规则:当递归实例TupSnd (a :. c) (b :. d)试图通过子约束TupSnd a b和TupSnd c d推导结果类型时,类型系统无法直接确认a :. c能唯一确定b :. d——这违反了禁用UndecidableInstances时的“实例头部必须能直接绑定所有约束变量”的限制。
以下是无需启用UndecidableInstances的解决方案:
方法1:用类型家族显式定义输出类型
通过类型家族直接定义输入到输出的类型映射,让类型系统能直接从输入类型计算出结果类型,彻底消除推导的不确定性:
{-# LANGUAGE TypeFamilies #-} -- 定义类型家族,明确每个输入对应的输出类型 type family TupSndRes a where TupSndRes PGTup = Only Action TupSndRes (a :. c) = TupSndRes a :. TupSndRes c -- 类不再需要函数依赖,直接关联输入类型和类型家族的结果 class TupSnd a where tupSnd :: a -> TupSndRes a instance TupSnd PGTup where tupSnd a = Only (snd a) -- 递归实例只需保证子类型满足TupSnd约束,类型家族会自动推导结果结构 instance (TupSnd a, TupSnd c) => TupSnd (a :. c) where tupSnd (a :. c) = tupSnd a :. tupSnd c
这个方案只需要启用TypeFamilies(Haskell中非常常用的扩展),完全规避了UndecidableInstances的需求,同时逻辑清晰,类型推导直接可靠。
内容的提问来源于stack exchange,提问作者ron
相关产品推荐
相关产品推荐

