很难让这个问题变得简洁,但是为了提供一个最小的例子,假设我有这种类型:
{-# 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)
。完全正确;定义中没有任何内容表示x
和y
必须是同一类型。事实上,重点是允许它们不同的可能性。
让我们将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
这很不错。如果x
和y
属于同一类型,则会使用基础Eq
实例。如果它们不同,则只返回False
。但是,此检查会延迟到运行时间,允许nonsense = Val2 True == Val2 "Hello"
进行类型检查而无需投诉。
我意识到我在这里调用依赖类型,但Haskell类型系统是否有可能静态拒绝上述nonsense
之类的东西,同时允许像sensible = Val2 True == Val2 False
这样的东西交回{{} 1}}在运行时?
我越是处理这个问题,我越需要采用HList的一些技术来实现我需要的操作作为类型级函数。然而,我对使用存在感和GADT相对较新,我很想知道是否有一个解决方案可以找到这些。所以,如果答案是否定的,我非常感谢您讨论这个问题究竟在哪些方面达到了这些特性的极限,以及对适当的技术,HList或其他方面的推动。
答案 0 :(得分:13)
为了根据包含的类型做出类型检查决策,我们需要通过将它作为类型参数公开来“记住”包含的类型。
data Val a where
Val :: Eq a => a -> Val a
现在Val Int
和Val Bool
是不同的类型,因此我们可以轻松强制执行仅允许相同类型的比较。
instance Eq (Val a) where
(Val x) == (Val y) = x == y
但是,由于Val Int
和Val 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]
所以我很确定满足这些约束的函数会违反类型系统的某些理想属性。它对你来说不是那样吗?我强烈猜测“不,你不能这样做。”