This is my first attempt at JSON deserialising with Aeson. I'm having trouble to type-check a generic decoding function for all my domain data types, even though a corresponding decoding function for a single concrete type does work.
Here is the polymorphic function:
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)
After initial failures, I inserted a type annotation for the target type of the decoder, but to no avail. If I try to compile it, the following type-check error results:
• 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
[..]
What am I missing? Thanks four any insight!
You shouldn't need the type annotation at all. Does it not compile fine without it?
What you are missing is that variables in a type signature in a let
or where
clause are not scoped within the type signature of the containing function. So, the type variable dData
in the signature for loadDomainData
is completely unrelated to the dData
in the signature for decData
. GHC is complaining that the type in decData
has no J.FromJSON
instance because the type signature says it doesn't have one. You can add it:
decData :: J.FromJSON dData => Maybe dData
or you can turn on the ScopedTypeVariables
extension and modify the type signature for the containing function to mark the dData
variable as scoped:
loadDomainData :: forall dData. J.FromJSON dData => FilePath -> IO dData
while keeping the same decData
declaration as before (no forall
and no constraint
):
decData :: Maybe dData
or, as mentioned above, you can remove the type signature for decData
entirely. So, all three of the following should work:
{-# 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)