Избегание повторных объявлений экземпляров в Haskell - PullRequest
4 голосов
/ 20 марта 2019

Кажется, мой вопрос тесно связан с этим один.

Мой код анализирует файл yaml, переупорядочивает объекты и записывает новый файл yaml. Он отлично работает, но в нем есть особенно уродливая часть.

Я должен объявить свои структуры данных как экземпляры FromJson и ToJson следующим образом:

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

Проблема в том, что я должен повторить это для 8 или около того других случаев:

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

...
...

Я не мог понять, как избежать этого повторения. Есть ли какой-нибудь метод, чтобы объявить две функции только один раз (например, в новом классе) и позволить всем этим типам данных наследоваться от него?

Решение (см. Также принятый ответ от dfeuer):

Мне лично нравится это решение. Вам нужно будет добавить

{-# 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

Затем вы можете объявить типы следующим образом:

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

Ответы [ 3 ]

4 голосов
/ 21 марта 2019

Это, между прочим, довольно новое расширение DerivingVia.

{-# 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

Теперь вы можете написать

deriving via (NP User) instance FromJSON User

Или

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

и т. Д.

Это не сильно экономит на ответе слева, как есть. Однако, как только вы добавите определение toEncoding, оно начинает выглядеть стоящим.

Внимание: я не проверял ничего из этого.

2 голосов
/ 20 марта 2019

Как,

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
...

Конечно, это все еще повторяется, но я бы сказал, что это больше не повод для беспокойства.

0 голосов
/ 21 марта 2019

Если явное объявление всех подобных примеров оказывается слишком обременительным, возможно, вы могли бы параметризовать свои типы данных с помощью фантомного типа , например

data User x = User { aa :: Int, bb :: Bool } deriving Generic

data Role x = Role { xx :: Int, dd :: Bool } deriving Generic

, а затем определите тип маркера типа

data Marker

Этот тип данных будет служить только в качестве колышка, на котором можно повесить экземпляры, подобные следующим

{-# language UndecidableInstances #-}
instance (Generic (f Marker), GFromJSON Zero (Rep (f Marker))) => FromJSON (f Marker) where 
    parseJSON = noPrefixParseJSON

Это стоило бы того? Вероятно, нет, потому что определение ваших типов данных становится более сложным. С другой стороны, вы можете изменить аспекты сериализации, изменив маркер, чтобы получить некоторую гибкость.

...