【发布时间】:2017-09-07 04:50:59
【问题描述】:
这段代码
{-# LANGUAGE GADTs #-}
data Expr a where
Val :: Num a => a -> Expr a
Eq :: Eq a => Expr a -> Expr a -> Expr Bool
eval :: Expr a -> a
eval (Val x) = x
eval (Eq x y) = (eval x) == (eval y)
instance Show a => Show (Expr a) where
show (Val x) = "Val " ++ (show x)
show (Eq x y) = "Eq (" ++ (show y) ++ ") (" ++ (show x) ++ ")"
编译失败并显示以下错误消息:
Test.hs:13:32: error:
* Could not deduce (Show a1) arising from a use of `show'
from the context: Show a
bound by the instance declaration at test.hs:11:10-32
or from: (a ~ Bool, Eq a1)
bound by a pattern with constructor:
Eq :: forall a. Eq a => Expr a -> Expr a -> Expr Bool,
in an equation for `show'
at test.hs:13:11-16
Possible fix:
add (Show a1) to the context of the data constructor `Eq'
* In the first argument of `(++)', namely `(show y)'
In the second argument of `(++)', namely
`(show y) ++ ") (" ++ (show x) ++ ")"'
In the expression: "Eq (" ++ (show y) ++ ") (" ++ (show x) ++ ")" Failed, modules loaded: none.
注释掉最后一行解决了这个问题,检查 GHCi 中 Expr 的类型显示,GHC 不是将 Eq 推断为 Eq a => (Expr a) -> (Expr a) -> Expr Bool 类型,而是将其推断为 Eq a1 => (Expr a1) -> (Expr a1) -> Expr Bool
data Expr a where ...。这解释了错误消息,因为 instance Show a => Show (Expr a) where ... 不会强制 a1 成为 Show 的实例。
但是我不明白,为什么 GHC 选择这样做。如果我不得不猜测,我会说它与Eq 返回一个值有关,这根本不依赖于a,因此GHC“忘记”了a,一次@987654335 @ 返回 Expr Bool。这是 - 至少在某种程度上 - 这里发生了什么?
另外,有没有一个干净的解决方案?我尝试了几件事,包括尝试通过 InstanceSigs 和 ScopedTypeVariables 执行以下操作“强制”所需的类型:
instance Show a => Show (Expr a) where
show :: forall a. Eq a => Expr a -> String
show (Eq x y) = "Eq (" ++ (show (x :: Expr a)) ++ ") (" ++ (show (y :: Expr a)) ++ ")"
...
,但没有成功。而且由于我仍然是 Haskell 新手,我什至不确定这是否可以以某种方式工作,因为我最初猜测为什么 GHC 不会首先推断出“正确”/所需的类型。
编辑:
好的,所以我决定添加一个函数
extract (Eq x _) = x
到文件(没有类型签名),只是为了看看它会有什么返回类型。 GHC 再次拒绝编译,但这一次,错误信息包含了一些关于 skolem 类型变量的信息。快速搜索得到this question,它(我认为?)形式化了,我认为问题是:一旦Eq :: Expr a -> Expr a -> Expr Bool 返回Expr Bool,a 就会“超出范围”。现在我们没有关于a 的任何信息,所以extract 必须有签名exists a. Expr Bool -> a,因为forall a. Expr Bool -> a 不会对每个a 都是正确的。但是因为 GHC 不支持exists,所以类型检查失败。
【问题讨论】:
-
最简单的解决方案是将 Eq 的签名从
Eq a => Expr a -> Expr a -> Expr Bool更改为 @ 987654352@ 仅在比较可显示类型的表达式时才允许构造 eq 表达式。 -
谢谢,这行得通;然而,在类型构造函数中没有这个额外要求的解决方案也会很有趣
-
原始类型没有好的解决方案。对于存在主义类型,你能做的实在太少了。另一种方法是将
Val限制为固定类型(或使用多个类似Val的构造函数来表示一组有限类型)。
标签: haskell type-inference gadt type-variables