解析时构建 GADT json
Construct GADT while parsing json
我有一个用 GADT 创建的数据结构,我想使用 aeson
将一些 json 解析到这个 GADT。但是类型检查器抱怨说在所有情况下都只能创建 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 结构?
这个
instance FromJSON (SFoo a) where
不会飞。你会得到
parseJSON :: forall a. Value -> Parser (SFoo a)
这意味着调用者可以选择他们想要的 a
,并且 parseJSON
无法控制从 JSON 解析 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
上进行匹配时,that 将告诉您必须处理哪种类型:
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
我有一个用 GADT 创建的数据结构,我想使用 aeson
将一些 json 解析到这个 GADT。但是类型检查器抱怨说在所有情况下都只能创建 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 结构?
这个
instance FromJSON (SFoo a) where
不会飞。你会得到
parseJSON :: forall a. Value -> Parser (SFoo a)
这意味着调用者可以选择他们想要的 a
,并且 parseJSON
无法控制从 JSON 解析 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
上进行匹配时,that 将告诉您必须处理哪种类型:
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