无法将 Aeson 解码为通用类型的类型检查

Trouble type-checking Aeson decoding into generic types

这是我第一次尝试 JSON 使用 Aeson 进行反序列化。我无法对我所有域数据类型的通用解码函数进行类型检查,即使单个具体类型的相应解码函数确实有效。

这里是多态函数:

import qualified RIO.ByteString.Lazy           as BL
import qualified Data.Aeson                    as J
import qualified Path.Posix                    as P

loadDomainData :: J.FromJSON dData => FC.AbsFilePath -> IO dData
loadDomainData filePath = do
    fileContents <- readFileBinary $ P.toFilePath filePath
    let
        decData :: Maybe dData
        decData = J.decode $ BL.fromStrict fileContents
    case decData of
        Just d -> return d
        Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)

在最初的失败之后,我为解码器的目标类型插入了一个类型注释,但无济于事。如果我尝试编译它,则会出现以下类型检查错误结果:

    • Could not deduce (J.FromJSON dData1)
        arising from a use of ‘J.decode’
      from the context: J.FromJSON dData
        bound by the type signature for:
                   loadDomainData :: forall dData.
                                     J.FromJSON dData =>
                                     FC.AbsFilePath -> IO dData
        at src/Persistence/File/ParticipantRepository.hs:44:1-64
      Possible fix:
        add (J.FromJSON dData1) to the context of
          the type signature for:
            decData :: forall dData1. Maybe dData1
    • In the expression: J.decode $ BL.fromStrict fileContents
[..]

我错过了什么?感谢四位有识之士!

您根本不需要类型注释。没有它编译不好吗?

您缺少的是 letwhere 子句中类型签名中的变量不在包含函数的类型签名范围内。因此,loadDomainData 签名中的类型变量 dDatadecData 签名中的 dData 完全无关。 GHC 抱怨 decData 中的类型没有 J.FromJSON 实例,因为类型签名说它没有。您可以添加它:

decData :: J.FromJSON dData => Maybe dData

或者您可以打开 ScopedTypeVariables 扩展并修改包含函数的类型签名以将 dData 变量标记为作用域:

loadDomainData :: forall dData. J.FromJSON dData => FilePath -> IO dData

同时保持与以前相同的 decData 声明(没有 forall 也没有 constraint):

decData :: Maybe dData

或者,如上所述,您可以完全删除 decData 的类型签名。因此,以下所有三个都应该有效:

{-# LANGUAGE ScopedTypeVariables #-}

-- Add constraint to `decData` signature
loadDomainData :: J.FromJSON dData => FC.AbsFilePath -> IO dData
loadDomainData filePath = do
    fileContents <- readFileBinary $ P.toFilePath filePath
    let
        decData :: J.FromJSON dData => Maybe dData
        decData = J.decode $ BL.fromStrict fileContents
    case decData of
        Just d -> return d
        Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)

-- Use ScopedTypeVariables
loadDomainData :: forall dData. J.FromJSON dData => FC.AbsFilePath -> IO dData
loadDomainData filePath = do
    fileContents <- readFileBinary $ P.toFilePath filePath
    let
        decData :: Maybe dData
        decData = J.decode $ BL.fromStrict fileContents
    case decData of
        Just d -> return d
        Nothing -> throwString ("Could not decode data file " <> P.toFilePath filePath)

-- No `decData` signature
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)