Haskell带函数依赖的newtype封装代码编译失败,求GHC可接受的mkYVal实现
你的代码编译失败的核心问题出在YVal的类型定义和你传入的值的类型不匹配:
你定义的YVal x内部封装的是一个秩2多态值forall y . C x y => y,这个类型的含义是:该值必须能适配所有满足C x y约束的y类型,也就是getYVal的调用方可以任意指定想要的y类型(只要满足C x y),你都要返回对应类型的结果。
而你mkYVal参数里的y是一个具体的、固定的类型,满足C x y约束,你没法用一个固定类型的值去满足「适配所有符合约束的y类型」的要求,这就是报错里提示y2和y1类型不匹配的原因——y2是YVal构造子要求的多态类型变量,y1是你传入的固定类型变量,二者无法统一。
虽然你给类C加了x -> y的函数依赖,逻辑上对固定的x只会有唯一的y满足约束,但GHC的类型检查器在处理这个秩2多态字段的时候,并不会主动用函数依赖来推导「所有满足C x y的y都是同一个类型」,所以不会自动把你传入的固定类型y和多态要求的y做统一。
分两种情况,取决于你实际想要的封装效果:
方案1:保留现有YVal定义的实现
如果你确实需要getYVal返回多态值,那你不能传入具体的y来构造YVal,而是要构造一个能适配所有符合约束的y的多态值,比如给类型类加默认值实现:
class C x y | x -> y where defaultY :: y newtype YVal x = YVal { getYVal :: forall y . C x y => y } mkYVal :: C x y => YVal x mkYVal = YVal defaultY
这种情况下mkYVal不需要传入参数,因为defaultY本身就是满足forall y. C x y => y的多态值。
方案2:修改YVal为存在量化类型(更符合你预期的用法)
你大概率是搞混了全称量化和存在量化的写法,你实际想要的应该是封装「某一个满足C x y约束的y类型的值」,而非多态值,这种情况下应该把YVal定义为存在类型:
{-# LANGUAGE ExistentialQuantification #-} class C x y | x -> y newtype YVal x = forall y. C x y => YVal y -- 这个类型签名现在可以正常编译通过 mkYVal :: C x y => y -> YVal x mkYVal y = YVal y
这种定义下,YVal x内部存的就是由x通过函数依赖唯一确定的那个y类型的值,和你原来写的mkYVal逻辑完全匹配。
内容的提问来源于stack exchange,提问作者fakedrake

