[英]Union in GADT type variable
我有一个表示相同想法的 GADT 构造函数,但我需要其中两个,因为类型根据上下文灵活。 请参阅这个人为的示例:
data A
data B
data C
data Thing a where
AFoo :: String -> Thing A
Bar :: Float -> Thing A
BFoo :: String -> Thing B
Baz :: Int -> Thing B
Bah :: Char -> Thing C
AFoo
和BFoo
代表相同的底层数据概念:一些带有字符串的数据。 但在这种情况下,它不止于此。 理想情况下, AFoo
和BFoo
将合并到Foo
,因为它们代表完全相同的东西,但我需要一个类型像Thing A
类型和一个类型像Thing B
类型。 如前所述,一些可以使用Foo
上下文需要一个Thing A
而一些需要一个Thing B
,所以为了满足类型系统,每个都需要一个构造函数。
如果需要,我当然可以通过切换构造函数来编写一个“强制转换”为所需类型的函数:
fooAsThingA :: Thing a -> Maybe (Thing A)
fooAsThingA t@(AFoo _) = Just t
fooAsThingA (BFoo s) = Just $ AFoo s
fooAsThingA _ = Nothing
(对于fooAsThingB
也是fooAsThingB
)
但这很丑陋,因为它需要一个我必须传播的Maybe
,因为并非所有的Thing B
都可以成为Thing A
(实际上,只有BFoo
可以)。
理想情况下,我想写:
data A
data B
data C
data Thing a where
Foo :: String -> Thing ??
Bar :: Float -> Thing A
Baz :: Int -> Thing B
Bah :: Char -> Thing C
但我不清楚我用什么来代替??
. 大概它是表示A
和B
联合的某种方式(也许这需要更改此 GADT 的其他构造函数的类型,但这很好)。
需要明确的是,如果上述内容有效,我希望具有以下功能:
processThingA :: Thing A -> String
processThingB :: Thing B -> Int
processThingC :: Thing C -> Float
然后,我将能够执行以下所有操作:
processThingA $ Foo "Hello"
processThingB $ Foo "World"
processThingA $ Bar 3.14
processThingB $ Baz 42
processThingC $ Bah '\n'
tl;dr 在第一个片段中,是否可以将AFoo
和BFoo
合并到一个类型为Thing A
和Thing B
的Foo
中?
编辑:注意:除了A
和B
,还有其他EmptyDataDecls
用于Thing a
。 我以C
为例。
一个可能的解决方案如下,即使我不认为它优雅。
我们首先为“A 或 B”定义一个 GADT 和一个类型类:
{-# LANGUAGE DataKinds, KindSignatures, GADTs, EmptyCase,
ScopedTypeVariables #-}
{-# OPTIONS -Wall #-}
data Sort = A | B | C | D
data AorB (s :: Sort) where
IsA :: AorB 'A
IsB :: AorB 'B
class IsAorB (s :: Sort) where
aorb :: AorB s
instance IsAorB 'A where aorb = IsA
instance IsAorB 'B where aorb = IsB
然后我们在我们的类型中利用我们的类型类。 这将导致Foo
需要更多空间,因为它需要在运行时存储类型类字典。 这是少量的开销,但仍然很不幸。 另一方面,这也可以在运行时辨别Foo
使用的s
实际上是A
还是B
。
data Thing (s :: Sort) where
Foo :: IsAorB s => String -> Thing s
Bar :: Float -> Thing 'A
Baz :: Int -> Thing 'B
Bah :: Char -> Thing 'C
一些测试:
processThingA :: Thing 'A -> String
processThingA (Foo x) = x
processThingA (Bar x) = show x
processThingB :: Thing 'B -> Int
processThingB (Foo _) = 0
processThingB (Baz i) = i
一个主要的缺点是我们需要让穷举检查器相信我们的模式匹配是可以的。
-- This unfortunately will give a spurious warning
processThingC :: Thing 'C -> Float
processThingC (Bah _) = 42.2
-- To silence the warning we need to prove that this is indeed impossible
processThingC (Foo _) = case aorb :: AorB 'C of {}
processThingAorB :: forall s. IsAorB s => Thing s -> String
processThingAorB (Foo x) = x
processThingAorB (Bar x) = "Bar " ++ show x
processThingAorB (Baz x) = "Baz " ++ show x
-- Again, to silence the warnings
processThingAorB (Bah _) = case aorb :: AorB 'C of {}
test :: ()
test = ()
where
_ = processThingA $ Foo "Hello"
_ = processThingB $ Foo "World"
_ = processThingA $ Bar 3.14
_ = processThingB $ Baz 42
_ = processThingC $ Bah '\n'
这种技术不能很好地扩展。 我们需要一个自定义的 GADT 和类型类,用于Thing
任何构造函数,它可以在某个“联合”中生成任何标记。 这仍然可以。 为了检查穷举性,我们需要利用所有这些类。
我认为这应该需要线性量的样板,但它仍然很重要。
也许使用单例更简单,在一般情况下,将s
的值存储在构造函数Foo
,并避免所有自定义 GADT 和类型类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.