我可以在 Data.Aeson 和 Data.Yaml 之间重复使用 ToJSON 和 FromJSON 实例吗?

Can I reuse ToJSON and FromJSON instances between Data.Aeson and Data.Yaml?

Data.Aeson 和 Data.Yaml 分别是用于使用 JSON 和 YAML 的库,它们具有几乎相同的接口。我已经为我的某些类型编写了“基于 Aeson 的”FromJSONToJSON 实例:

import Data.Aeson

data PropertyValue = Nested Microformat | Flat Text
    deriving (Generic, Show)

instance ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

虽然 Aeson 使用这些实例,但 Yaml 似乎忽略了它们。 (具体来说,我相信 Yaml 会自动生成编码器和解码器,这要归功于我的类型派生 Generic。)我试图通过将代码更改为

来使实例与 Yaml 一起工作
import Data.Aeson
import qualified Data.Yaml as Y

data PropertyValue = Nested Microformat | Flat Text
    deriving (Generic, Show)

instance ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

instance Y.ToJSON PropertyValue where
    toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }

instance Y.FromJSON PropertyValue where
    parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }

但 GHC 抱怨:

Duplicate instance declarations:
  instance ToJSON PropertyValue
    -- Defined at src/Microformats.hs:54:10
  instance ToJSON PropertyValue
    -- Defined at src/Microformats.hs:60:10

是否不能为碰巧同名的两个不同 class 定义实例?或者 Yaml 的 ToJSON 实际上与 Aeson 的 相同 class?

更重要的是,是否可以重用我的 ToJSONFromJSON 实例以避免必须将相同的代码编写两次?如果做不到这一点,是否至少可以在不混淆编译器的情况下编写两个库的 ToJSONFromJSON 的实例?

简答Data.AesonData.YamlToJSON(和FromJSON)类型类 相同Data.Yaml 实际上只对 Data.Aeson.

ToJSON 类型类执行 reimport

如果我们看 Data.Yaml source code:

#if (defined (ghcjs_HOST_OS))
module Data.Yaml {-# WARNING "GHCJS is not supported yet (will break at runtime once called)." #-}
#else
<b>module Data.Yaml</b>
#endif
    ( -- * Types
      Value (..)
    , Parser
    , Object
    , Array
    , ParseException(..)
    , prettyPrintParseException
    , YamlException (..)
    , YamlMark (..)
      -- * Constructors and accessors
    , object
    , array
    , (.=)
    , (.:)
    , (.:?)
    , (.!=)
      -- ** With helpers (since 0.8.23)
    , withObject
    , withText
    , withArray
    , withScientific
    , withBool
      -- * Parsing
    , parseMonad
    , parseEither
    , parseMaybe
      -- * Classes
    <b>, ToJSON (..)
    , FromJSON (..)</b>
      -- * Encoding/decoding
    , encode
    , encodeFile
    , decode
    , decodeFile
      -- ** Better error information
    , decodeEither
    , decodeEither'
    , decodeFileEither
      -- ** More control over decoding
    , decodeHelper
    ) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative(())
#endif
import Control.Exception
<b>import Data.Aeson</b>
    ( Value (..), <b>ToJSON (..), FromJSON (..)</b>, object
    , (.=) , (.:) , (.:?) , (.!=)
    , Object, Array
    , withObject, withText, withArray, withScientific, withBool
    )
(...)

所以模块导出了一个 ToJSON 类型类,但这只是 ToJSON 导入 Data.Aeson 的结果,因此类型类实际上是相同的。

因此为了程序员的方便而重新导入它(例如,您不必导入 Data.Aeson 来实现 FromJOSN),但实际上您仍然使用相同的类型,相同类型 类,等等。毕竟你的 ToJSONY.ToJSON 指的是 same 类型类。

由于两者实际上是一样的,所以不能为同一个类型实例化同一个类型类两次,但是我们不需要这样做:如果我们为Data.Aeson(或Data.Yaml),这就足够了,因为例如写在 Data.Yaml(或 Data.Aeson)中的类型类约束将会成功。结果是我们不能(至少不能没有一些技巧),以不同的方式实现 Data.YamlData.Aeson.

ToJSON