从包装器中动态模式匹配嵌套的 GADT
Dynamically pattern matching nested GADT back out of a wrapper
我最近问了如何制作 GADT 实例的同质列表:
tl;博士
{-#LANGUAGE GADTs, EmptyDataDecls #-}
module Main where
-- Define a contrived GADT
data TFoo
data TBar
data Thing a where
Foo :: Int -> Thing TFoo
Bar :: String -> Thing TBar
data SomeThing = forall a. SomeThing (Thing a)
combine :: [SomeThing]
combine = [Something $ Foo 1, SomeThing $ Bar "abc"]
现在,我在动态 "unwrapping" 它们时遇到了一些麻烦。假设我们有这个(仍然是人为的,但更接近我的真实用例)代码:
{-#LANGUAGE GADTs, EmptyDataDecls #-}
module Main where
-- Define a contrived GADT
data Thing a where
Thing :: TKind a -> Thing a
data TFoo
data TBar
data TKind a where
Foo :: TKind TFoo
Bar :: TKind TBar
data SomeThing = forall a. SomeThing (Thing a)
example :: SomeThing
example = SomeThing $ Thing Foo
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
extractThingWithTKind k st = case st of
SomeThing t@(Thing k) -> Right t
_ -> Left "nope"
上面的方法不起作用,因为 Right t
中的 t
没有类型 Thing a
。从本质上讲,我理解为什么这行不通。 k
上的模式匹配不符合我的要求(仅当 k
与传入的相同时才匹配)。但这是我接近我想要的东西的最佳尝试。我尝试在 TKind a
上 instance
ing Eq
,但是因为 (==) :: a -> a -> Bool
,这将不起作用(相等性取决于运行时可能不同的类型)。我可以像 Thing
那样包装 TKind
,但那样我只会把问题推低。
删除动态,我尝试只模式匹配 Thing TFoo
显式:
extractThingWithFoo :: SomeThing -> Either String (Thing TFoo)
extractThingWithFoo st = case st of
SomeThing t@(Thing Foo) -> Right t
_ -> Left "nope"
这很有效!但这是否意味着我不能进行动态匹配?必须为每种 TKind
重复上述方法将是一个真正的痛苦(在非人为版本中,有很多)。我看到的唯一其他解决方案是将 SomeThing
更改为一种总和类型,每个 TKind
都有一个包装器,但是你只是将重复的代码移动到不同的地方(并强制所有使用SomeThing
模式匹配每个)。
为了实现带有签名的功能
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
,我们需要能够确定 SomeThing
里面的内容是否是 TKind a
。 GADT 构造函数是此类类型相等性的见证,但它们需要明确 pattern-matched 到 "unwrap" 函数局部范围内的这些假设。
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
extractThingWithTKind Foo (SomeThing t@(Thing Foo)) = Right t
extractThingWithTKind Bar (SomeThing t@(Thing Bar)) = Right t
extractThingWithTKind _ _ = Left "nope"
第一个参数的模式匹配产生了a ~ TFoo
的假设(在第一种情况下),进一步的第二个参数的模式匹配证明SomeThing
里面的东西也是一个TFoo
。关键是个案要一一列举,证据是施工方自己提供的。
我最近问了如何制作 GADT 实例的同质列表:
tl;博士
{-#LANGUAGE GADTs, EmptyDataDecls #-}
module Main where
-- Define a contrived GADT
data TFoo
data TBar
data Thing a where
Foo :: Int -> Thing TFoo
Bar :: String -> Thing TBar
data SomeThing = forall a. SomeThing (Thing a)
combine :: [SomeThing]
combine = [Something $ Foo 1, SomeThing $ Bar "abc"]
现在,我在动态 "unwrapping" 它们时遇到了一些麻烦。假设我们有这个(仍然是人为的,但更接近我的真实用例)代码:
{-#LANGUAGE GADTs, EmptyDataDecls #-}
module Main where
-- Define a contrived GADT
data Thing a where
Thing :: TKind a -> Thing a
data TFoo
data TBar
data TKind a where
Foo :: TKind TFoo
Bar :: TKind TBar
data SomeThing = forall a. SomeThing (Thing a)
example :: SomeThing
example = SomeThing $ Thing Foo
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
extractThingWithTKind k st = case st of
SomeThing t@(Thing k) -> Right t
_ -> Left "nope"
上面的方法不起作用,因为 Right t
中的 t
没有类型 Thing a
。从本质上讲,我理解为什么这行不通。 k
上的模式匹配不符合我的要求(仅当 k
与传入的相同时才匹配)。但这是我接近我想要的东西的最佳尝试。我尝试在 TKind a
上 instance
ing Eq
,但是因为 (==) :: a -> a -> Bool
,这将不起作用(相等性取决于运行时可能不同的类型)。我可以像 Thing
那样包装 TKind
,但那样我只会把问题推低。
删除动态,我尝试只模式匹配 Thing TFoo
显式:
extractThingWithFoo :: SomeThing -> Either String (Thing TFoo)
extractThingWithFoo st = case st of
SomeThing t@(Thing Foo) -> Right t
_ -> Left "nope"
这很有效!但这是否意味着我不能进行动态匹配?必须为每种 TKind
重复上述方法将是一个真正的痛苦(在非人为版本中,有很多)。我看到的唯一其他解决方案是将 SomeThing
更改为一种总和类型,每个 TKind
都有一个包装器,但是你只是将重复的代码移动到不同的地方(并强制所有使用SomeThing
模式匹配每个)。
为了实现带有签名的功能
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
,我们需要能够确定 SomeThing
里面的内容是否是 TKind a
。 GADT 构造函数是此类类型相等性的见证,但它们需要明确 pattern-matched 到 "unwrap" 函数局部范围内的这些假设。
extractThingWithTKind :: TKind a -> SomeThing -> Either String (Thing a)
extractThingWithTKind Foo (SomeThing t@(Thing Foo)) = Right t
extractThingWithTKind Bar (SomeThing t@(Thing Bar)) = Right t
extractThingWithTKind _ _ = Left "nope"
第一个参数的模式匹配产生了a ~ TFoo
的假设(在第一种情况下),进一步的第二个参数的模式匹配证明SomeThing
里面的东西也是一个TFoo
。关键是个案要一一列举,证据是施工方自己提供的。