您的代码没有按原样编译(它也需要PolyKinds
打开)所以我不知道这是否只是一个意外,但看起来您正在尝试采用您从类型中知道的方法一个可能涉及构造函数的队列,因此可以静态保证只能在某种队列上调用函数。
实际上,您可以使用 GADT 的多个构造函数来使用该方法(而不是使用多个完全独立的类型,在必要时使用类型类将它们组合在一起,在@danidiaz 的回答中建议的方法)。
但首先为什么您当前的代码不起作用。在您的队列类型中:
data Queue (kind :: a -> QueueType)
= Queue Name QueueType
您正在Queue
通过类型变量(称为 )对类型进行参数化,从而允许您在类型级别kind
标记 a您希望在其中的类型。但是只有构造函数根本没有引用;这是一种幻影类型。无论队列类型是什么,该插槽都可以由任何有效的队列类型填充。Queue
QueueType
Queue Name QueueType
kind
QueueType
kind
Queue kind
这意味着 GHC 希望您添加一个匹配;publish
中的主题键的 case 是正确的。Queue 'Direct
您的数据类型定义说可以存在这样的值。
GADT 允许您单独显式声明每个构造函数的完整类型,包括返回类型。因此,您可以在您正在构造的值的类型与可能用于生成该类型值的构造函数(或其参数)之间建立关系。
具体来说,我们可以为您的队列创建一个类型,使其Queue 'Direct
只能包含直接队列类型,并且只能包含主题队列类型,并且您可以通过多态接受 a 来处理。Queue 'Topic
Queue a
最简单的做法是QueueType
只用于标签,并有一个单独的 GADT 存储数据。在您的原始代码中,您可以重用提升到类型级别且未应用的数据保存构造函数,但这会使您的类型签名变得不必要地复杂(引入对 的需要PolyKinds
),并且如果您需要添加更多(以及不同数量的!) 数据构造函数的参数,当提升到类型级别时,将其未应用的类型硬塞起来以适应相同的类型将变得越来越困难。所以:
data QueueType
= Direct
| Topic
data QueueData (a :: QueueType)
where DirectData :: DirectKey -> QueueData 'Direct
TopicData :: TopicKey -> QueueData 'Topic
所以我们QueueType
只需要提升DataKinds
(通常不需要在值级别实际使用这种类型)。然后我们得到了QueueData
由 type-level 参数化的类型QueueType
。一个构造函数接受 aDirectKey
并构造 a QueueData 'Direct
,另一个接受 aTopicKey
并构造 a QueueData 'Topic
。
然后很容易拥有一个Queue
类似地用所表示的队列类型标记的类型:
data Queue (a :: QueueType)
= Queue Name (QueueData a)
现在,如果一个函数在任何队列上工作(比如说因为它只需要访问 之外的名称QueueData
),它可以采用Queue a
:
getName :: Queue a -> Text
getName (Queue name _) = name
如果您可以明确处理所有案例,您也可以采取 a Queue a
,当您错过案例时,您会收到警告:
getKeyText :: Queue a -> Text
getKeyText (Queue _ (DirectData key)) = key
getKeyText (Queue _ (TopicData keys)) = mconcat keys
最后,当Queue 'Direct
在你的publish
函数中使用 as 时,GHC 知道这DirectData
是QueueData
. 因此,您不需要像在 OP 中那样添加错误案例,如果您尝试TopicData
在那里处理内部,它实际上会被检测为类型错误。
完整示例:
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
import Data.Text (Text)
type Name = Text
type DirectKey = Text
type TopicKey = [Text]
data QueueType
= Direct
| Topic
data QueueData (a :: QueueType)
where DirectData :: DirectKey -> QueueData 'Direct
TopicData :: TopicKey -> QueueData 'Topic
data Queue (a :: QueueType)
= Queue Name (QueueData a)
getName :: Queue a -> Text
getName (Queue name _) = name
getKeyText :: Queue a -> Text
getKeyText (Queue _ (DirectData key)) = key
getKeyText (Queue _ (TopicData keys)) = mconcat keys
publish :: Queue 'Direct -> IO ()
publish (Queue name (DirectData key))
= doSomething name key
where doSomething = undefined