首页 > 解决方案 > 解析json时构造GADT

问题描述

我有一个使用 GADT 创建的数据结构,我想使用aeson. 但是类型检查器抱怨在所有情况下都只能创建 GADT 的构造函数之一。看这个例子:

data Foo = Hello | World

data SFoo :: Foo -> Type where
  SHello :: SFoo 'Hello 
  SWorld :: SFoo 'World

instance FromJSON (SFoo a) where
  parseJSON = withText "Foo" \case
    "hello" -> pure SHello
    "world" -> pure SWorld

所以我希望能够将“hello”字符串解析为SHello,将“world”字符串解析为SWorld. 类型检查器抱怨以下错误:

• Couldn't match type ‘'World’ with ‘'Hello’
  Expected type: Parser (SFoo 'Hello)
    Actual type: Parser (SFoo 'World)
• In the expression: pure SWorld
  In a case alternative: "world" -> pure SWorld
  In the second argument of ‘withText’, namely
    ‘\case
       "hello" -> pure SHello
       "world" -> pure SWorld’

如何将一些 json 解析为这样的 GADT 结构?

标签: haskelltext-parsingaesongadt

解决方案


这个

instance FromJSON (SFoo a) where

不飞。你会得到

parseJSON :: forall a. Value -> Parser (SFoo a)

这意味着调用者可以选择a他们想要的,并且无法控制从 JSONparseJSON中解析。a相反,你想要

data SomeFoo = forall a. SomeFoo (SFoo a)
instance FromJSON SomeFoo where
    parseJSON = withText "Foo" \case
        "hello" -> pure $ SomeFoo SHello
        "world" -> pure $ SomeFoo SWorld
        _ -> fail "not a Foo" -- aeson note: without this you get crashes!

现在在哪里

fromJSON :: Value -> Result SomeFoo

不会告诉您它的哪个分支SFoo将以它的类型返回。SomeFoo现在是一对a :: Foo类型和SFoo a值。fromJSON现在负责解析整个对,因此它控制返回的类型和值。当您使用它并在 上匹配时SomeFoo它将告诉您必须处理哪种类型:

example :: Value -> IO ()
example x = case fromJSON x of
    Error _ -> return ()
    Success (SomeFoo x) -> -- know x :: SFoo a where a is a type extracted from the match; don't know anything about a yet
        case x of
            SHello -> {- now know a ~ Hello -} return ()
            SWorld -> {- now know a ~ World -} return ()

请注意,SomeFoo它基本上同构于Foo. 你也可以写

instance FromJSON Foo where ..

接着

someFoo :: Foo -> SomeFoo
someFoo Hello = SomeFoo SHello
someFoo World = SomeFoo SWorld
instance FromJSON SomeFoo where parseJSON = fmap someFoo . parseJSON

请注意,您可以编写以下两个实例:

instance FromJSON (SFoo Hello) where
    parseJSON = withText "SFoo Hello" \case
        "hello" -> pure SHello
        _ -> fail "not an SFoo Hello"
instance FromJSON (SFoo World) where
    parseJSON = withText "SFoo World" \case
        "world" -> pure SWorld
        _ -> fail "not an SFoo World"

...但它们并不是特别有用,除非作为另一种编写方式FromJSON SomeFoo

instance FromJSON SomeFoo where
    parseJSON x = prependFailure "SomeFoo: " $
        SomeFoo @Hello <$> parseJSON x <|> SomeFoo @World <$> parseJSON x

推荐阅读