2

我想以 JSON 格式输出我的应用程序的日志,但是有一些普遍存在的数据类型ToJSON没有定义实例 - 最值得注意SomeException的是整个Exception类型的层次结构。

我有两个选择:

  1. ToJSON在我的应用程序中为此类数据类型定义实例
  2. 编写我自己的类型类,比如ToJsonLogs,并使其ToJSON尽可能地重用实例。

第一个是“阻力最小”的路径,但它还有其他含义。由于类型类实例本质上是全局的,我最终可能会定义ToJSON破坏某些东西的实例。此外,对于相同的数据结构,我可能希望 API 中的 JSON 与日志中的 JSON 不同(例如,清理密钥、授权令牌和其他敏感数据或截断非常长的文本字段)。

这个问题是关于探索第二种选择的。我该如何做以下事情:

class ToJsonLogs a where
  toJsonLogs :: a -> Aeson.Value

  default toJsonLogs :: (ToJSON a) => a -> Aeson.Value
  toJsonLogs = toJSON

instance ToJsonLogs SomeException where
  toJsonLogs = toJSON . displayException

我尝试了上述想法,但它在第一步本身就失败了。这是一个示例数据结构:

data SyncResult = SyncResult
  { resAborted :: !Bool
  , resSuccessful :: !Int
  , resFailed :: ![(Int, SomeException)]
  } deriving (Show)

ToJsonLogs如果不首先推导ToJSON整个数据结构,我就无法推导。由于 的推导ToJSON失败SomeException。因此,这个问题的标题。

我什至试着和泛型鬼混,但像往常一样,又被卡住了

4

1 回答 1

0

您非常接近可能的无扩展解决方案。您应该考虑的是为原始ToJson类成员创建一个包装器:

class ToJsonLogs a where
  toJsonLogs :: a -> Aeson.Value

newtype WrapToJson a = WrapToJson a  -- actually an Identity

instance ToJson a => ToJsonLogs (WrapToJson a) where
  toJsonLogs (WrapToJson x) = toJson x

-- example
logInt :: Int -> Aeson.value
logInt x = toJsonLogs (WrapJson x)

如果您只想将包装器限制为ToJson实例,则需要启用一些扩展:

{-# LANGUAGE GADTSyntax, ExistentialQuantifiaction #-}
data WrapToJson a where WrapToJson :: ToJson a => a -> WrapToJson a  

如果你不喜欢这个包装,你可以将它隐藏在另一个定义下toJsonLogs

toJsonLogs' :: ToJson a => a -> Aeson.value
toJsonLogs' = toJsonLogs . WrapToJson
于 2020-02-16T15:56:21.603 回答