haskell - 解析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 结构?
解决方案
这个
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
推荐阅读
- rust - 如何将 yaml-rust 枚举存储到结构的实例中?
- git - 验证子模块初始化 [Git]
- c# - 在 ASP.net API 核心 2.1 中将部分添加到 JSON 输出
- scala - if else 表达式后的函数返回类型错误
- sphinx - 为什么这条线会停止 Sphinx 搜索?
- mongodb - MongoDB查询最相似的数组
- php - 如何在php中获取关联数组值?
- c++ - 在 msvc 中使用 cmake 构建 boost 项目
- angular - Angular 5 谷歌地图:仅显示 # 公里半径内的标记
- javascript - Redux 使用 Map() 形成初始值