避免在 Haskell 中重复声明实例
Avoiding repeated instance declarations in Haskell
我的问题似乎与this密切相关
一。
我的代码解析一个 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 })
...
...
我不知道如何避免这种重复。是否有某种方法可以只声明这两个函数一次(例如在新的 class 中)并让所有这些数据类型都从中派生?
解决方案(另请参阅 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)
喜欢,
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
...
当然这仍然有点重复,但我想说这不再是任何担心的原因。
如果显式声明所有这些相似的实例被证明过于繁琐,也许您可以使用 phantom type 之类的
来参数化您的数据类型
data User x = User { aa :: Int, bb :: Bool } deriving Generic
data Role x = Role { xx :: Int, dd :: Bool } deriving Generic
然后定义一个 "marker" 数据类型,例如
data Marker
此数据类型将仅用作挂钩,在其上挂起如下所示的实例
{-# language UndecidableInstances #-}
instance (Generic (f Marker), GFromJSON Zero (Rep (f Marker))) => FromJSON (f Marker) where
parseJSON = noPrefixParseJSON
值得吗?可能不会,因为数据类型的定义变得更加复杂。另一方面,您可以通过改变标记来改变序列化的各个方面,从而获得一些灵活性。
这就是相当新的 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)
等等。
这并没有比 leftaroundabout 的答案节省很多。但是,一旦您添加了 toEncoding
的定义,它就开始变得有价值了。
注意:我已经测试了 none。
我的问题似乎与this密切相关 一。
我的代码解析一个 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 })
...
...
我不知道如何避免这种重复。是否有某种方法可以只声明这两个函数一次(例如在新的 class 中)并让所有这些数据类型都从中派生?
解决方案(另请参阅 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)
喜欢,
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
...
当然这仍然有点重复,但我想说这不再是任何担心的原因。
如果显式声明所有这些相似的实例被证明过于繁琐,也许您可以使用 phantom type 之类的
来参数化您的数据类型data User x = User { aa :: Int, bb :: Bool } deriving Generic
data Role x = Role { xx :: Int, dd :: Bool } deriving Generic
然后定义一个 "marker" 数据类型,例如
data Marker
此数据类型将仅用作挂钩,在其上挂起如下所示的实例
{-# language UndecidableInstances #-}
instance (Generic (f Marker), GFromJSON Zero (Rep (f Marker))) => FromJSON (f Marker) where
parseJSON = noPrefixParseJSON
值得吗?可能不会,因为数据类型的定义变得更加复杂。另一方面,您可以通过改变标记来改变序列化的各个方面,从而获得一些灵活性。
这就是相当新的 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)
等等。
这并没有比 leftaroundabout 的答案节省很多。但是,一旦您添加了 toEncoding
的定义,它就开始变得有价值了。
注意:我已经测试了 none。