Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding repeated instance declarations in Haskell

My question seems to be closely related to this one.

My code parses a yaml file, rearanges the objects and writes a new yaml file. It works perfectly well, but there is a particularly ugly part in it.

I have to declare my data structures as instances of FromJson and ToJson like this:

instance FromJSON Users where
  parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Users where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })

The problem is that I have to repeat this for 8 or so other cases:

instance FromJSON Role where
  parseJSON = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
instance ToJSON Role where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })

...
...

I could not figure out how to avoid this repetition. Is there some method to declare the two functions just once (for example in a new class) and let all these data types derive from it?

Solution (see also accepted answer by dfeuer):

I personally like this solution. You'll need to add

{-# language DerivingVia #-}
{-# language UndecidableInstances #-}

newtype NP a = NP {unNP::a} 

instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (NP a) where
  parseJSON = fmap NP . genericParseJSON 
    (defaultOptions { fieldLabelModifier = body_noprefix })

instance (Generic a, GToJSON Zero (Rep a)) => ToJSON (NP a) where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix }) . unNP

Then you can declare the types like this:

data User = User { ... } deriving (Show, Generic)
                         deriving FromJSON via (NP User)
                         deriving ToJSON via (NP User)
like image 915
fata Avatar asked Mar 20 '19 19:03

fata


Video Answer


2 Answers

This is what the fairly new DerivingVia extension is for, among other things.

{-# language DerivingVia #-}

newtype NP a = NP {unNP::a}

instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (NP a) where
  parseJSON = fmap NP . genericParseJSON 
    (defaultOptions { fieldLabelModifier = body_noprefix })

instance (Generic a, GToJSON Zero (Rep a)) => ToJSON (NP a) where
  toJSON = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix }) . unNP

Now, you can write

deriving via (NP User) instance FromJSON User

Or

data User = ...
  deriving Generic
  deriving (FromJSON, ToJSON) via (NP User)

and so on.

This doesn't save a lot over leftaroundabout's answer as it is. However, once you add a definition of toEncoding, it starts to look worthwhile.

Caution: I have tested none of this.

like image 84
dfeuer Avatar answered Sep 28 '22 09:09

dfeuer


Like,

noPrefixParseJSON :: (Generic a, GFromJSON Zero (Rep a)) => Value -> Parser a
noPrefixParseJSON
    = genericParseJSON (defaultOptions { fieldLabelModifier = body_noprefix })
noPrefixToJSON :: (Generic a, GToJSON Zero (Rep a)) => a -> Value
noPrefixToJSON
    = genericToJSON (defaultOptions { fieldLabelModifier = body_noprefix })

instance FromJSON User where parseJSON = noPrefixParseJSON
instance ToJSON User where toJSON = noPrefixToJSON
instance FromJSON Role where parseJSON = noPrefixParseJSON
instance ToJSON Role where toJSON = noPrefixToJSON
...

Sure this is still kind of repetitive, but I'd say this isn't any cause of worry any more.

like image 31
leftaroundabout Avatar answered Sep 28 '22 09:09

leftaroundabout