我可以静态拒绝存在类型的不同实例吗?

时间:2011-09-27 02:04:09

标签: haskell existential-type gadt type-level-computation

首次尝试

很难让这个问题变得简洁,但是为了提供一个最小的例子,假设我有这种类型:

{-# LANGUAGE GADTs #-}
data Val where
  Val :: Eq a => a -> Val

这种类型让我愉快地构建了以下异构外观列表:

l = [Val 5, Val True, Val "Hello!"]

但是,唉,当我写下Eq个实例时,事情就出错了:

instance Eq Val where
  (Val x) == (Val y) = x == y -- type error

啊,我们Could not deduce (a1 ~ a)。完全正确;定义中没有任何内容表示xy必须是同一类型。事实上,重点是允许它们不同的可能性。

第二次尝试

让我们将Data.Typeable加入混合中,只有当它们碰巧是同一类型时才尝试比较它们:

data Val2 where
  Val2 :: (Eq a, Typeable a) => a -> Val2

instance Eq Val2 where
  (Val2 x) == (Val2 y) = fromMaybe False $ (==) x <$> cast y

这很不错。如果xy属于同一类型,则会使用基础Eq实例。如果它们不同,则只返回False。但是,此检查会延迟到运行时间,允许nonsense = Val2 True == Val2 "Hello"进行类型检查而无需投诉。

问题

我意识到我在这里调用依赖类型,但Haskell类型系统是否有可能静态拒绝上述nonsense之类的东西,同时允许像sensible = Val2 True == Val2 False这样的东西交回{{} 1}}在运行时?

我越是处理这个问题,我越需要采用HList的一些技术来实现我需要的操作作为类型级函数。然而,我对使用存在感和GADT相对较新,我很想知道是否有一个解决方案可以找到这些。所以,如果答案是否定的,我非常感谢您讨论这个问题究竟在哪些方面达到了这些特性的极限,以及对适当的技术,HList或其他方面的推动。

2 个答案:

答案 0 :(得分:13)

为了根据包含的类型做出类型检查决策,我们需要通过将它作为类型参数公开来“记住”包含的类型。

data Val a where
  Val :: Eq a => a -> Val a

现在Val IntVal Bool是不同的类型,因此我们可以轻松强制执行仅允许相同类型的比较。

instance Eq (Val a) where
  (Val x) == (Val y) = x == y

但是,由于Val IntVal Bool是不同的类型,我们不能在列表中将它们混合在一起而不会再次“忘记”包含的类型。

data AnyVal where
  AnyVal :: Val a -> AnyVal

-- For convenience
val :: Eq a => a -> AnyVal
val = AnyVal . Val

现在,我们可以写

[val 5, val True, val "Hello!"] :: [AnyVal]

现在应该很清楚,你不能用单一数据类型满足这两个要求,因为这样做需要同时“忘记”“记住”所包含的类型。

答案 1 :(得分:7)

因此,您需要一个允许您使用异构类型的构造函数,但是您希望在编译时可知的异构类型之间的比较被拒绝。如:

Val True == Val "bar"  --> type error

allSame [] = True
allSame (x:xs) = all (== x) xs

allSame [Val True, Val "bar"]  --> False

但肯定:

(x == y) = allSame [x,y]

所以我很确定满足这些约束的函数会违反类型系统的某些理想属性。它对你来说不是那样吗?我强烈猜测“不,你不能这样做。”