rubik
rubik

Reputation: 9104

Compile errors when declaring ToJSON instances

I'm having trouble declaring ToJSON instances of my type (synonyms):

{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module Argon.Types (ComplexityBlock, AnalysisResult, ResultsOptions(..)
                   , OutputMode(..))
    where

import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as BL


-- | Hold the data associated to a function binding:
--   (line number, column, function name, complexity)
type ComplexityBlock = (Int, Int, String, Int)

instance ToJSON ComplexityBlock where
    toJSON (l, c, func, cc) = object [ "lineno"     .= l
                                     , "col"        .= c
                                     , "name"       .= func
                                     , "complexity" .= cc
                                     ]

-- | Represent the result of the analysis of one file.
--   It can either be an error message or a list of
--   'ComplexityBlock's.
type AnalysisResult = Either String [ComplexityBlock]

instance ToJSON (FilePath, AnalysisResult) where
    toJSON (p, Left err) = object [ "path"    .= p
                                  , "type"    .= "error"
                                  , "message" .= err
                                  ]
    toJSON (p, Right rs) = object [ "path"   .= p
                                  , "type"   .= "result"
                                  , "blocks" .= rs
                                  ]

Having imported the bytestring package and enabled the OverloadedStrings extension I thought it could work, but it does not:

/home/miki/exp/argon/src/Argon/Types.hs:31:47:
    No instance for (ToJSON a0) arising from a use of ‘.=’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance ToJSON (FilePath, AnalysisResult)
        -- Defined at src/Argon/Types.hs:29:10
      instance ToJSON ComplexityBlock
        -- Defined at src/Argon/Types.hs:17:10
    In the expression: type .= error
    In the first argument of ‘object’, namely
      ‘[path .= p, type .= error, message .= err]’
    In the expression:
      object [path .= p, type .= error, message .= err]

/home/miki/exp/argon/src/Argon/Types.hs:31:50:
    No instance for (Data.String.IsString a0)
      arising from the literal ‘error’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Data.String.IsString Value
        -- Defined in ‘aeson-0.8.0.2:Data.Aeson.Types.Internal’
      instance (a ~ Data.ByteString.Internal.ByteString) =>
               Data.String.IsString
                 (attoparsec-0.12.1.6:Data.Attoparsec.ByteString.Internal.Parser a)
        -- Defined in ‘attoparsec-0.12.1.6:Data.Attoparsec.ByteString.Char8’
      instance Data.String.IsString
                 Data.ByteString.Builder.Internal.Builder
        -- Defined in ‘Data.ByteString.Builder’
      ...plus four others
    In the second argument of ‘(.=)’, namely ‘error’
    In the expression: type .= error
    In the first argument of ‘object’, namely
      ‘[path .= p, type .= error, message .= err]’

/home/miki/exp/argon/src/Argon/Types.hs:35:46:
    No instance for (ToJSON a1) arising from a use of ‘.=’
    The type variable ‘a1’ is ambiguous
    Note: there are several potential instances:
      instance ToJSON (FilePath, AnalysisResult)
        -- Defined at src/Argon/Types.hs:29:10
      instance ToJSON ComplexityBlock
        -- Defined at src/Argon/Types.hs:17:10
    In the expression: type .= result
    In the first argument of ‘object’, namely
      ‘[path .= p, type .= result, blocks .= rs]’
    In the expression:
      object [path .= p, type .= result, blocks .= rs]

/home/miki/exp/argon/src/Argon/Types.hs:35:49:
    No instance for (Data.String.IsString a1)
      arising from the literal ‘result’
    The type variable ‘a1’ is ambiguous
    Note: there are several potential instances:
      instance Data.String.IsString Value
        -- Defined in ‘aeson-0.8.0.2:Data.Aeson.Types.Internal’
      instance (a ~ Data.ByteString.Internal.ByteString) =>
               Data.String.IsString
                 (attoparsec-0.12.1.6:Data.Attoparsec.ByteString.Internal.Parser a)
        -- Defined in ‘attoparsec-0.12.1.6:Data.Attoparsec.ByteString.Char8’
      instance Data.String.IsString
                 Data.ByteString.Builder.Internal.Builder
        -- Defined in ‘Data.ByteString.Builder’
      ...plus four others
    In the second argument of ‘(.=)’, namely ‘result’
    In the expression: type .= result
    In the first argument of ‘object’, namely
      ‘[path .= p, type .= result, blocks .= rs]’

I don't understand why the type variables are ambiguous.

Upvotes: 2

Views: 503

Answers (1)

Yuriosity
Yuriosity

Reputation: 1918

Compare the signatures:

fromString :: IsString a => String -> a
toJSON :: ToJSON a => a -> Value

Technically, to serialize a string literal, your custom instance uses their superposition:

toJSON . fromString :: (IsString a, ToJSON a) => String -> Value

Note how the type variable disappears from the signature, so the ambiguity arises. For example, "error" can happily be String, Value or Text, all of which have both IsString and ToJSON instances.

A quick workaround is to resolve the ambiguity manually, by providing the explicit type signature:

    toJSON (p, Left err) = object [ "path"    .= p
                                  , "type"    .= ("error" :: String)

Upvotes: 4

Related Questions