Ignoring/Overriding an Instance generated using TemplateHaskell - haskell

I'm using Aeson for some client-server stuff that I'm doing, encoding ADTs as Json. I'm using Data.Aeson.TH to generate the toJSON instances I need, but the instances generated for Map types are really ugly and awful to deal with.
I've defined my own, simpler encoding which just treats them as lists:
instance (ToJSON a, ToJSON b) => ToJSON (Map a b) where
toJSON m = toJSON $ toList m
Naturally, when I use this in my code, I get a Duplicate instance declarations error.
Is there a way to resolve this? I need to either tell Template Haskell NOT to generate the ToJson instance for Map, or I need to tell GHC to ignore that instance and use the one I supply. Can either of these be done?
Note that this isn't an "overlapping-instances" problem. I want to completely throw out the one instance, not mix it with the other one.

To tell GHC to ignore library-provided instance and use your own instead, you can wrap Map in a newtype:
newtype PrettyMap key val = PrettyMap (Map key val)
instance (ToJSON a, ToJSON b) => ToJSON (PrettyMap a b) where
toJSON (PrettyMap m) = toJSON $ toList m
Another solution is to really use OverlappingInstances:
data MyData = ...
$(deriveToJSON ... ''MyData)
instance ToJSON (Map Text MyData) where
toJSON = toJSON . toList

Related

How to "reuse" instance definitions from another typeclass while introduding minor differences?

I want to output my application's logs in JSON, but there are some ubiquitous data-types for which ToJSON instances are not defined - most notably SomeException and the entire Exception hierarchy of types.
I have two choices:
Define instances of ToJSON for such data-types in my application
Write my own type-class, say ToJsonLogs, and make it reuse ToJSON instances as much as possible.
The first is the path of "least resistance" but it has other implications. Since type-class instances are global in nature, I might end-up defining ToJSON instances that break something. Also, for the same data-structure, I might want the JSON in APIs to be different from the JSON in logs (for example, scrubbing keys, auth-tokens, and other sensitive data OR truncating very long text fields).
This questions is about exploring the second option. How do I go about doing something like the following:
class ToJsonLogs a where
toJsonLogs :: a -> Aeson.Value
default toJsonLogs :: (ToJSON a) => a -> Aeson.Value
toJsonLogs = toJSON
instance ToJsonLogs SomeException where
toJsonLogs = toJSON . displayException
I tried the above idea, but it failed at the very first step itself. Here's an example data-structure:
data SyncResult = SyncResult
{ resAborted :: !Bool
, resSuccessful :: !Int
, resFailed :: ![(Int, SomeException)]
} deriving (Show)
I can't derive ToJsonLogs without first deriving ToJSON for the entire data-structure. Derivation of ToJSON fails because of SomeException. Hence the title of this question.
I even tried fooling around with Generics, but as usual, got stuck again.
You are very close to a possible extension-free solution. The thing you should consider is to create a wrapper for the original ToJson class members:
class ToJsonLogs a where
toJsonLogs :: a -> Aeson.Value
newtype WrapToJson a = WrapToJson a -- actually an Identity
instance ToJson a => ToJsonLogs (WrapToJson a) where
toJsonLogs (WrapToJson x) = toJson x
-- example
logInt :: Int -> Aeson.value
logInt x = toJsonLogs (WrapJson x)
If you want to restrict the wrapper only for ToJson instances, you will need to enable few extensions:
{-# LANGUAGE GADTSyntax, ExistentialQuantifiaction #-}
data WrapToJson a where WrapToJson :: ToJson a => a -> WrapToJson a
If you don't enjoy this wrapper, you may hide it under another definition of toJsonLogs:
toJsonLogs' :: ToJson a => a -> Aeson.value
toJsonLogs' = toJsonLogs . WrapToJson

Single tag constructors in Aeson

I have a data type like this:
data A = A T.Text deriving (Generic, Show)
instance A.ToJSON A
If I use A.encode to it:
A.encode $ A "foobar" -- "foobar"
Then I use singleTagConstructors on it:
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
A.encode $ A "foobarquux" -- "{tag: A, contents: foobarquux}"
At some point I made another data type:
newtype Wrapper a = Wrapper
{ unWrap :: a
} deriving (Show)
instance A.ToJSON a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= unWrap w ]
Here's the part where I get confused:
A.encode $ Wrapper $ A "foobar" -- "{wrapped: foobar}"
How do I get the result to be like this?
"{wrapped: {tag: A, contents: foobarquux}}"
To answer the question directly, you can always implement the Wrapper instance with tagSingleConstructors = False, like this:
instance Generic a => A.ToJSON (Wrapper a) where
toJSON w = A.object [ "wrapped" A..= encA (unWrap w) ]
where
encA = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = False }
But I don't see why you'd want to do that.
If you control the API, then you don't need the tag field: the expected type of the wrapped value is already statically known, so tag would not be helpful.
And if you don't control the API, I would recommend representing it very explicitly, for example as a record that exactly matches the API shape. Otherwise you run a risk of accidentally breaking the API by making unrelated changes in remote parts of the codebase.
The issue is how you implemented your custom ToJSON instance.
instance A.ToJSON A where
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
Since you do not implement toJSON directly default implementation from typeclass definition is used.
class ToJSON a where -- excerpt from Data.Aeson.Types.ToJSON
-- | Convert a Haskell value to a JSON-friendly intermediate type.
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON' Value Zero (Rep a)) => a -> Value
toJSON = genericToJSON defaultOptions
Effectively, you have the following instance:
instance A.ToJSON A where
toJSON = genericToJSON defaultOptions
toEncoding a = A.genericToEncoding $ A.defaultOptions { A.tagSingleConstructors = True }
While toEncoding uses expected tagged encoding, toJSON uses default encoding (which does not tag single constructors). This inconsistency is the root cause of the confusion. Later, in wrapper's ToJSON instance .= operator used. Internally, it uses toJSON and not toEncoding:
class KeyValue kv where
(.=) :: ToJSON v => Text -> v -> kv
infixr 8 .=
instance KeyValue Pair where
name .= value = (name, toJSON value)
As a solution, you should either define only toJSON and keep default toEncoding implementation (that uses toJSON) or implement both.

How do I get rid of "Defaulting the following constraints to type ‘[Char]’" warnings?

I'm using the Aeson library and the OverloadedStrings pragma, but this question is more general. The immediate context is that I'm writing code for a sum type so I have a "kind" field in the JSON to say which variant is being represented. In the ToJSON instance I write code like this:
toJSON (Foo v) = ["kind" .= "foo" ...]
toJSON (Bar v) = ["kind" .= "bar" ...]
GHC gives me the following warning for "foo" and "bar":
Defaulting the following constraints to type ‘[Char]’
(ToJSON v0)
arising from a use of ‘.=’
The reason is that both String and Text are instances of ToJSON, so the compiler has to pick one, and in this case it has picked String.
I know I can suppress the warning with an in-line type, like this:
toJSON (Foo v) = ["kind" := ("foo" :: Text) ...]
However that clutters up the code with redundant information. Also I've got quite a lot of these warnings.
I've tried putting default (Text) at the top of the file, but that just changes the warning to tell me its defaulted to type Text.
Is there a way of disabling the warnings for Text/String defaults but leaving any others in place?
Why not lift that part of the serialisation logic out into a separate function? It's kind of hacky anyway, so better to section it off. And you'll silence the warning (provided you give the function a type).
Something along the lines of...
toJSON thing#(Foo v) = ["kind" .= kind thing ...]
toJSON thing#(Bar v) = ["kind" .= kind thing ...]
kind :: T -> Text
kind (Foo _) = "foo"
kind (Bar_) = "bar"
However you want to to do it. Maybe use a ViewPattern or maybe make kind :: KeyValue kv => T -> kv. Just don't inline it, IMO.
In the end I took Thomas DuBuisson's suggestion from the comments and added a couple of specialized functions:
-- | Type-restricted version of "(.=)" to silence compiler warnings about defaults.
(..=) :: (KeyValue kv) => Text -> Text -> kv
(..=) = (.=)
-- | Type restricted version of "(.:)" to silence compiler warnings about defaults.
(..:) :: Object -> Text -> Parser Text
(..:) = (.:)
I think I'd probably abstract the key-value pair you're making, like this:
kind :: KeyValue kv => Text -> kv
kind lbl = "kind" .= lbl
toJSON (Foo v) = [kind "foo", ...]
toJSON (Bar v) = [kind "bar", ...]

How to derive instances of Data.Messagepack 1.0.0

The previous version of Data.Messagepack, 0.7.2.5 supports deriving instances via Template Haskell. The current version (1.0.0), however, doesn't.
I was hence wondering if there is an alternative way to automatically derive MessagePack 1.0.0 instances, possibly using XDeriveGeneric?
As a stop-gap measure, have a look at the msgpack-aeson directory of the message-pack github repo:
https://github.com/msgpack/msgpack-haskell/tree/master/msgpack-aeson
You could go from your data values <-> aeson <-> message-pack. Not necessarily efficient, but convenient since you can auto derive ToJSON and FromJSON with DeriveGeneric.
Example code:
{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
import Data.MessagePack.Aeson
import qualified Data.MessagePack as MP
import GHC.Generics
import Data.Aeson
data Foo = Foo { _a :: Int, _b :: String }
deriving (Generic)
instance ToJSON Foo
instance FromJSON Foo
toMsgPack :: Foo -> Maybe MP.Object
toMsgPack = decode . encode
test = toMsgPack (Foo 3 "asd")
You could write your own GMessagePack class and get instances by deriving Generic. I tried doing so to answer this question, but I can't recommend it. msgpack has no support for sums, and the one sum type supported by the Haskell msgpack library, Maybe, has a very poor encoding.
instance MessagePack a => MessagePack (Maybe a) where
toObject = \case
Just a -> toObject a
Nothing -> ObjectNil
fromObject = \case
ObjectNil -> Just Nothing
obj -> fromObject obj
The encoding for Maybes can't tell the difference between Nothing :: Maybe (Maybe a) and Just Nothing :: Maybe (Maybe a), both will be encoded as ObjectNil and decoded as Nothing. If we were to impose on MessagePack instances the obvious law fromObject . toObject == pure, this instance for MessagePack would violate it.

Proxies, type level symbols, and JSON

I'm trying to add automagical json parsing to Data.Vinyl
Here is an instance for FromJSON for records with exactly one element.
It almost works, but I can't satisfy the KnownSymbol constraint, it seems to auto generate a new type variable on me.
instance (KnownSymbol sym, FromJSON a) => FromJSON (PlainRec '[ sym ::: a ]) where
parseJSON (Object v) = (field =:) <$> (v .: json_name)
where field = Field :: (sym ::: a)
json_name = T.pack $ show field
The error is
Could not deduce (KnownSymbol sym0) arising from a use of ‛show’
from the context (KnownSymbol sym, FromJSON a)
More context http://lpaste.net/101005
If I replace all instances of sym with "name", it works, and runs and it is wonderful. Now, I could use template Haskell to generate all the instances ahead of time, since I have a closed list of field names that I'll actually use, but that seems like such a shame. I know next to nothing about Data.Proxy, having just seen in used to define the show instance for the records of Data.Proxy.
You just have to enable ScopedTypeVariables.

Resources