我想创建几个不兼容但相同的数据类型。也就是说,我想要一个参数化类型Foo a
,以及
bar :: (Foo a) -> (Foo a) -> (Foo a)
没有真正关心关于a
的内容。为了进一步澄清,我希望类型系统阻止我做
x :: Foo Int
y :: Foo Char
bar x y
虽然我同时并不关心Int
和Char
(我只关心他们不一样)。
在我的实际代码中,我有一个给定环上的多项式类型。我实际上并不关心不确定性是什么,只要类型系统阻止我在t中用多项式添加多项式。到目前为止,我已经通过创建类型类Indeterminate
并将我的多项式类型参数化为
data (Ring a, Indeterminate b) => Polynomial a b
这种方法对Ring
部分感觉非常自然,因为我做关心给定多项式结束的特定环。对于Indeterminate
部分感觉非常有用,详情如下。
上述方法运行良好,但感觉做作。特别是这一部分:
class Indeterminate a where
indeterminate :: a
data T = T
instance Indeterminate T where
indeterminate = T
data S = S
instance Indeterminate S where
indeterminate = S
(等等可能还有一些不确定的因素)。这感觉奇怪和错误。基本上我试图要求Indeterminate
的实例是单身(this sense)。奇怪的感觉是我可能会错误地攻击这个问题的一个指标。另一个原因是我最终不得不注释我的Polynomial a b
很多,因为实际类型b
通常无法推断(这并不奇怪,但仍然令人讨厌)。
有什么建议吗?我应该继续这样做,还是我错过了什么?
PS:如果我不立即投票或接受答案,请不要觉得被冒犯。我将无法再回来查看几天。
答案 0 :(得分:7)
首先,我不确定:
data (Ring a, Indeterminate b) => Polynomial a b
......正在做你期望的事情。关于data
定义的上下文并不十分有用 - 请参阅the discussion here出于某些原因,其中大部分原因都是强制您添加额外注释而不实际提供许多其他类型保证。
其次,除了确保类型保持不同之外,您是否真的关心“不确定”参数?做这种事情的一种非常标准的方法就是所谓的phantom types - 本质上是类型构造函数中未在数据构造函数中使用的参数。你永远不会使用或需要幻像类型的值,因此函数可以是你想要的多态,例如:
data Foo a b = Foo b
foo :: Foo a b -> Foo a b
foo (Foo x) = Foo x
bar :: Foo a c -> Foo b c
bar (Foo x) = Foo x
baz :: Foo Int Int -> Foo Char Int -> Foo () Int
baz (Foo x) (Foo y) = Foo $ x + y
显然这确实需要注释,但仅限于您故意添加限制的地方。否则,推理将对幻像类型参数正常工作。
在我看来,上述方法应该足以满足您在此所做的事情 - 单例类型的业务主要是通过创建类型来弥合更复杂的类型级别的东西和常规的价值级别计算之间的差距值的代理。例如,这可以用于标记具有指示其基础的类型的向量,或用物理单位标记数值 - 这两种情况下注释具有更多意义而不仅仅是“不确定的X”。