如何为类型 (* -> *) -> * 编写 aeson ToJSON 实例

How do I write aeson ToJSON instances for types with kind (* -> *) -> *

动机

我有一个类型 MyType,它由仿函数 f 参数化。

我想用MyType Identity表示数据的"my view",MyType Maybe表示对数据的updates类型.

问题

是否可以为MyType写一个aeson ToJSON实例?我尝试使用 ToJSON class,但出现错误(请参阅 post 的底部)。

{-# LANGUAGE DeriveGeneric #-}
module Main where

import GHC.Generics
import Data.Aeson

data MyType f = MyType
  { age  :: f Int
  , name :: f String
  } deriving(Generic)

instance ToJSON1 f => ToJSON (MyType f)

main :: IO ()
main = print . encode $ MyType (Just 1) (Just "hi")

如何为任意 f 获取 MyType f 的 ToJSON 实例?

编译错误

Main.hs:12:10: error:
    • Could not deduce (ToJSON (f String))
        arising from a use of ‘aeson-1.2.4.0:Data.Aeson.Types.ToJSON.$dmtoJSON’
      from the context: ToJSON1 f
        bound by the instance declaration
        at Main.hs:12:10-39
    • In the expression:
        aeson-1.2.4.0:Data.Aeson.Types.ToJSON.$dmtoJSON @MyType f
      In an equation for ‘toJSON’:
          toJSON = aeson-1.2.4.0:Data.Aeson.Types.ToJSON.$dmtoJSON @MyType f
      In the instance declaration for ‘ToJSON (MyType f)’
   |
12 | instance ToJSON1 f => ToJSON (MyType f)
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.

在利用 Lifting class 的评论中使用我的想法,经过一些修补,我得出了这个

{-# LANGUAGE DeriveGeneric
           , FlexibleContexts   
           , MultiParamTypeClasses
           , ScopedTypeVariables
           , TypeApplications
           , UndecidableInstances 
           #-}
module Main where

import GHC.Generics
import Data.Aeson
import Data.Constraint
import Data.Constraint.Lifting

data MyType f = MyType
  { age  :: f Int
  , name :: f String
  } deriving(Generic)

instance (Lifting ToJSON f) => ToJSON (MyType f) where
    toJSON mt
        | Sub Dict <- lifting @ToJSON @f @Int
        , Sub Dict <- lifting @ToJSON @f @String
            = genericToJSON defaultOptions mt

instance Lifting ToJSON Maybe where
    lifting = Sub Dict

main :: IO ()
main = print . encode $ MyType (Just 1) (Just "hi")

备注:

  • Dict converts back and forth between constraints (such as ToJSON Int) and values. Sub 只是约束蕴含的构造函数。
  • lifting @ToJSON @f @Inttype application syntax
  • 我通过查找 toJSONdefault implementation 使用了 genericToJSON defaultOptions。我们只需要先手动将一些实例纳入范围 lifting

希望对您有所帮助。