Haskell GADT' Show' - 实例类型变量推导

时间:2017-09-06 15:31:21

标签: haskell type-inference gadt type-variables

此代码

{-# 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。这是 - 至少有点 - 这里发生了什么?

此外,有一个干净的解决方案吗?我尝试了几件事,包括试图强迫"强迫"通过InstanceSigsScopedTypeVariables执行此类操作所需的类型:

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,因此类型检查失败。

2 个答案:

答案 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选择这样做。

问题是,可以使用NumEq实例编写自定义类型,但不能编写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

但是,应该很清楚e1e2不能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实例。