首页 > 解决方案 > 如何在具有自定义类型类约束的 GADT 上实现 fromJSON?

问题描述

我有以下 GADT:

{-# LANGUAGE GADTs #-}

data LogProtocol a where
  Message :: String -> LogProtocol String
  StartRun :: forall rc. (Show rc, Eq rc, Titled rc, ToJSON rc, FromJSON rc) 
   => rc -> LogProtocol rc
  ... and many more...

toJSON 是直截了当的,没有显示。fromJSON 实现基于:

这个 SO QuestionThis Blog Post - 模式 2

如下:

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}

-- tag type is used in to/ from JSON to reduce the use of magic strings
data LPTag = MessageT |
             StartRunT |
             ... and many more...
             deriving (Show, Eq, Enum)

tagList :: Enum a => [a]
tagList = enumFrom $ toEnum 0

$(deriveJSON defaultOptions ''LPTag) 

-- a wrapper to hide the a type param in the GADT
data Some (t :: k -> *) where
  Some :: t x -> Some t

instance FromJSON (Some LogProtocol) where

parseJSON :: Value -> Parser (Some LogProtocol)
parseJSON v@(Object o) =
  let 
    tag :: Maybe LPTag 
    tag = do 
      t <- (HML.lookup "type" o) 
      parseMaybe parseJSON t 

    failMessage :: [Char]
    failMessage = toS $ "Could not parse LogProtocol no type field or type field value is not a member of specified in: " 
                    <> (show(tagList :: [LPTag])) 
                    <> show v

  in 
    maybe  
      (fail failMessage )
      (
        \case 
          MessageT -> Some <$> (Message <$> o .: "txt")    
          StartRunT -> Some <$> (StartRun <$> o .: "runConfig")
      )
      tag        

parseJSON wrng = typeMismatch "LogProtocol" wrng

'''Message''' 的情况很好。我遇到的问题是错误,例如:

* No instance for (Titled x2) arising from a use of `StartRun'
* In the first argument of `(<$>)', namely `StartRun'
  In the second argument of `(<$>)', namely
    `(StartRun <$> o .: "runConfig")'
  In the expression: Some <$> (StartRun <$> o .: "runConfig")

在数据构造函数中有我自己的类型类约束(例如 Titled)的任何地方,编译器都会说“否”。有没有办法解决这个问题?

标签: haskelltypeclassaesongadt

解决方案


存在类型是一种反模式,尤其是当您需要进行反序列化时。StartRun 应该包含一个具体的类型。反序列化无论如何都需要一个具体的类型,因此您不妨将 StartRun 专门用于它。


推荐阅读