我有一个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
的类型和一个类似{{1}的类型}}。如前所述,可以使用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
我必须传播,因为并非所有Maybe
都可以成为Thing B
s(事实上,只有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
的并集的某种方式(也许这需要更改此GADT的其他构造函数的类型,但这很好)。
为了清楚起见,如果以上内容有效,我希望给出以下功能:
B
然后,我将能够完成以下所有操作:
processThingA :: Thing A -> String
processThingB :: Thing B -> Int
processThingC :: Thing C -> Float
tl; dr在第一个代码段中,是否可以将processThingA $ Foo "Hello"
processThingB $ Foo "World"
processThingA $ Bar 3.14
processThingB $ Baz 42
processThingC $ Bah '\n'
和AFoo
合并到BFoo
和Foo
类型的Thing A
中?
修改:注意:Thing B
还有EmptyDataDecls
,而不是A
和B
。我添加了Thing a
作为示例。
答案 0 :(得分:2)
可能的解决方案如下,即使我不认为它很优雅。
我们首先定义GADT和类型类为" A或B":
{-# 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
需要更多空间,因为它需要在运行时存储类型类字典。这是一个很小的开销,但它仍然是不幸的。另一方面,这也可以在运行时识别s
中使用的Foo
实际上是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'
这种技术不能很好地扩展。对于Thing
的任何构造函数,我们需要一个自定义的GADT和类型类,它可以在某些" union"中生成任何标记。这仍然可以。要检查详尽性,我们需要利用所有这些类。
我认为这应该需要一定量的样板,但它仍然很重要。
在一般情况下,使用单例更简单,可以将s
的值存储在构造函数Foo
中,并避免使用所有自定义GADT和类型类。