parseJSON class method with dependent type - haskell

and thanks in advance for the help :)
Here's the problem I'm trying to solve:
I have a type (MyType) and I wrote a JSON parser for it (Using aeson library), and this parser depends on another value (Config):
import Data.Aeson
data MyType = MyType Text
data Config = Config Text
parseMyType :: Config -> Value -> Parser MyType
parseMyType (Config f) (Object o) = do (String v) <- o .: f
return $ MyType v
What I really wanted to write a FromJSON instance for it... but the parseJSON only depends on the Value (cannot have Config):
instance FromJSON MyType where
parseJSON :: Value -> Parser MyType
parseJSON = ???
I'm wondering if it's possible to use type class in this case. I'm probably missing some type trick... or maybe there's a language extension I can use?
Thank you!

If you want to "magic up" an instance declaration based on runtime information, this is possible through the reflection library, though it is some rather deep magic and there is probably a nicer way: https://www.fpcomplete.com/user/thoughtpolice/using-reflection

Related

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", ...]

Haskell aeson ParseJSON example using (.:)

There is the following snippet of code in the Aeson package usage example:
data Coord = Coord { x :: Double, y :: Double }
instance FromJSON Coord where
parseJSON (Object v) = Coord <$>
v .: "x" <*>
v .: "y"
The type of parseJSON function is parseJSON :: Value -> Parser a.
I have the following question about this code: what is the .: function? From the example I might say that its type is Object -> String -> Parser String, however I can't find anything about it on hoogle/hackage. Any help would be appreciated!
It retrieves the value associated with the key. (.:) produces a parse failure (via empty from Alternative) if the key isn't there, so it is suitable for mandatory keys (as opposed to (.:?), which makes sense for optional ones).

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.

Ignoring/Overriding an Instance generated using TemplateHaskell

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

How do I write a ToJSON/FromJSON instance for a datatype with multiple constructors?

Every example I've seen for ToJSON and FromJSON are for data types with single constructors, like so :
data RewindConfig = RConfig JobID Phase
deriving Show
instance FromJSON RewindConfig where
parseJSON (Object o) = RConfig
<$> o .: "JobID"
<*> o .: "Phase"
parseJSON _ = fail "invalid RewindConfig"
I thought I would look to how Aeson makes the instance for a type with multiple constructors, for example Either:
instance (FromJSON a, FromJSON b) => FromJSON (Either a b) where
parseJSON (Object (H.toList -> [(key, value)]))
| key == left = Left <$> parseJSON value
| key == right = Right <$> parseJSON value
parseJSON _ = fail ""
The pattern-matching in parseJSON confuses me, I don't understand what is going on with (H.toList -> [(key, value)]).
The data type I want to make instances for looks like this:
data Foo = Bar String
| Baz String
| Bin String
it did occur to me to do something I knew how to implement
data Foo = (Maybe Bar) (Maybe Baz) (Maybe Bin)
But that seems unsatisfying. Could someone help me out by explaining what's going on with the Either instance, and perhaps giving me some guidance on To/From instances for Foo?
update: I think the instances Aeson implements for Maybe are much clearer and tells me what I need to know for my needs. Still, I'd like to know what's going on with Either.
The pattern (Object (H.toList -> [(key, value)])) is called a view pattern. You can read it as something like this:
parseJSon (Object o) = case H.toList o of
[(key, value)]
| key == left -> Left <$> parseJSON value
| key == right -> Right <$> parseJSON value
It's actually slightly different, since the above will always commit to the pattern Object o when handed an Object, whereas the view pattern will only commit when both the "matches the Object o pattern" and the "H.toList o matches the [(key, value)] pattern" conditions hold, but for this example that doesn't matter.
The json package contains an encoding for data types that you might want to adopt.
If you just derive Data you can use it. It's not very fast, but very easy to use.
Assuming each datatype has a distinct key, another approach could use lenses - I like it because it's concise and is readable. For example if you have a wrapper around an A, B, and C which all have FromJSON instances:
import Data.Aeson
import Data.Maybe
import Data.Aeson.Lens
import Control.Lens
data Wrap = WrapA A | WrapB B | WrapC C
instance FromJSON Wrap where
parseJSON json
| isJust (json ^? key "A's unique key") = WrapA <$> parseJSON json
| isJust (json ^? key "B's unique key") = WrapB <$> parseJSON json
| isJust (json ^? key "C's unique key") = WrapC <$> parseJSON json
| otherwise = fail "Bad message"

Resources