Aeson泛型类型解码类型检查失败问题求助
你遇到的这个类型检查错误,核心原因是Haskell默认的类型变量作用域规则:函数签名里的dData和你在decData类型注解里写的dData,其实是两个完全独立的类型变量!编译器会把局部注解里的dData当成一个全新的类型(它给起了个名字叫dData1),但你的上下文只有FromJSON dData的约束,完全没提到FromJSON dData1,自然就推导失败了。
我给你整理了几种能快速解决这个问题的方法,你可以根据自己的代码风格选择:
方案1:启用ScopedTypeVariables扩展
这是最直接的修复方式,它能让局部代码里的类型变量和函数签名里的同名变量绑定起来。
首先在你的源文件最顶部加上扩展声明:
{-# LANGUAGE ScopedTypeVariables #-}
然后修改函数签名(显式加上forall让绑定更清晰,GHC 8.0+也支持隐式,但显式写出来可读性更好):
loadDomainData :: forall dData. J.FromJSON dData => FC.AbsFilePath -> IO dData loadDomainData filePath = do fileContents <- readFileBinary $ P.toFilePath filePath let decData :: Maybe dData -- 现在这个dData和函数签名的是同一个变量了 decData = J.decode $ BL.fromStrict fileContents case decData of Just d -> return d Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)
这样编译器就能正确识别到decData的类型就是函数签名里的dData对应的Maybe类型,自然就能找到FromJSON实例了。
方案2:移除局部类型注解
其实你完全不需要给decData加类型注解!编译器可以从上下文自动推导出来:后续的case分支需要decData是Maybe dData类型,再结合FromJSON dData的约束,它能自动判断J.decode的返回类型。去掉注解后代码更简洁,还能正常编译:
loadDomainData :: J.FromJSON dData => FC.AbsFilePath -> IO dData loadDomainData filePath = do fileContents <- readFileBinary $ P.toFilePath filePath let decData = J.decode $ BL.fromStrict fileContents case decData of Just d -> return d Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)
方案3:使用类型应用语法(GHC 8.0+)
如果你偏爱显式指定类型的风格,可以用GHC的类型应用语法,直接给J.decode指定要解码的目标类型,这样也能绕开作用域的问题:
loadDomainData :: J.FromJSON dData => FC.AbsFilePath -> IO dData loadDomainData filePath = do fileContents <- readFileBinary $ P.toFilePath filePath let decData = J.decode @dData $ BL.fromStrict fileContents case decData of Just d -> return d Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)
@dData直接告诉编译器,要把字节流解码成dData类型,不需要依赖局部类型注解。
本质就是Haskell默认不让局部代码引用函数签名的类型变量,上面三种方法都能打破这个限制,选你觉得最顺手的就行~
内容的提问来源于stack exchange,提问作者Ulrich Schuster

