避免在Haskell中重复实例声明

时间:2019-03-20 19:18:33

标签: haskell instance dry

我的问题似乎与this有密切关系 一。

我的代码解析一个yaml文件,重新排列对象并编写一个新的yaml文件。它工作得很好,但是其中尤为难看。

我必须这样声明自己的数据结构为smartsheet.SheetResources.RowResources.DeleteRows( sheetId, new long[] { 207098194749316, 207098194749317 }, true) FromJson的实例:

ToJson

问题是我必须在8种左右的其他情况下重复此操作:

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

我不知道如何避免这种重复。是否有某种方法可以一次声明两个函数(例如在一个新类中),然后让所有这些数据类型从中派生?

解决方案(另请参见dfeuer接受的答案):

我个人喜欢这种解决方案。您需要添加

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

...
...

然后您可以声明如下类型:

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

3 个答案:

答案 0 :(得分:3)

这是相当新的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的定义,它就会开始变得很有价值。

警告:我没有对此进行测试。

答案 1 :(得分:2)

就像

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

当然,这仍然是重复的,但我想这不再是引起担忧的原因。

答案 2 :(得分:0)

如果显式声明所有这些相似的实例被证明太麻烦了,也许您可​​以使用phantom type之类的参数来设置数据类型

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

这值得吗?可能不是,因为数据类型的定义变得更加复杂。另一方面,您可以通过更改标记来更改序列化的各个方面,从而获得一定的灵活性。

相关问题