此代码
{-# 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&#34 ;遗忘"大约a
,一旦Eq
返回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
,因此类型检查失败。
答案 0 :(得分:5)
一个选项是不需要Show a
超级约束。
instance Show (Expr a) where
showsPrec p (Eq x y) = showParen (p>9)
$ ("Eq "++) . showsPrec 10 x . (' ':) . showsPrec 10 y
当然,这有点踢石头了,因为现在你可以不写
showsPrec p (Val x) = showParen (p>9) $ ("Val "++) . showsPrec 10 x
- 现在叶子值不受Show
限制。但是在这里你可以通过使Num
约束更强一些来解决这个问题:
data Expr a where
Val :: RealFrac a => a -> Expr a
Eq :: Eq a => Expr a -> Expr a -> Expr Bool
instance Show (Expr a) where
showsPrec p (Val x) = showParen (p>9) $ ("Val "++)
. showsPrec 10 (realToFrac x :: Double)
嗯,这是一个很大的黑客,那时你也可以简单地使用
data Expr a where
Val :: Double -> Expr Double
Eq :: Eq a => Expr a -> Expr a -> Expr Bool
(或其他任何最适合您数量要求的单一类型)。这不是一个好的解决方案。
要保留在Expr
树叶中存储任何类型数量的功能,但如果需要,可以将它们限制为Show
,您需要关于约束的多态性。
{-# LANGUAGE ConstraintKinds, KindSignatures #-}
import GHC.Exts (Constraint)
data Expr (c :: * -> Constraint) a where
Val :: (Num a, c a) => a -> Expr a
Eq :: Eq a => Expr a -> Expr a -> Expr Bool
然后你可以做
instance Show (Expr Show a) where
showsPrec p (Val x) = showParen (p>9) $ ("Val "++) . showsPrec 10 x
showsPrec p (Eq x y) = showParen (p>9)
$ ("Eq "++) . showsPrec 10 x . (' ':) . showsPrec 10 y
答案 1 :(得分:3)
我只会解释这一点。
但我不明白,为什么GHC选择这样做。
问题是,可以使用Num
和Eq
实例编写自定义类型,但不能编写Show
个实例。
newtype T = T Int deriving (Num, Eq) -- no Show, on purpose!
然后,这种类型检查:
e1 :: Expr T
e1 = Val (T 42)
就像这样:
e2 :: Expr Bool
e2 = Eq e1 e1
但是,应该很清楚e1
和e2
不能show
。要打印这些内容,我们需要Show T
,这是缺失的。
此外,约束
instance Show a => Show (Expr a) where
-- ^^^^^^
在这里没用,因为我们确实有Show Bool
,但这还不足以打印e2 :: Expr Bool
。
这是存在类型的问题:你得到你付出的代价。当我们构建e2 = Eq e1 e2
时,我们只需要“支付”约束Eq T
。因此,当我们模式匹配Eq a b
时,我们只返回Eq t
(其中t
是合适的类型变量)。我们不知道Show t
是否成立。实际上,即使我们记得t ~ T
,我们仍然缺少必需的Show T
实例。