GHC无法推断存在GADT和类型家族的类型

时间:2015-01-14 01:12:52

标签: haskell types ghc

我有一个简单的长度索引向量类型和长度索引向量的append函数:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}

module LengthIndexedList where

  data Zero
  data Succ a

  type family Plus (a :: *) (b :: *) :: *
  type instance Plus Zero b = b
  type instance Plus (Succ a) b = Succ (Plus a b)

  data Vec :: * -> * -> * where
    VNil :: Vec a Zero
    VCons :: a -> Vec a n -> Vec a (Succ n)

  -- If you remove the following type annotation, type inference
  -- fails.
  -- append :: Vec a n1 -> Vec a n2 -> Vec a (Plus n1 n2)
  append v1 v2 = case v1 of
    VNil -> v2
    (VCons x xs) -> VCons x (append xs v2)

编译失败,因为GHC无法推断append函数的类型。我知道类型推断在GADT和类型系列存在的情况下很棘手,部分原因在于多态递归。然而,根据Vytiniotis等人的JFP paper GHC7类型推断应该在"类型类+ GADTs +类型族"的存在下工作。在这方面,我有两个问题:

  1. 为什么不能为上述示例进行类型推断(我使用的是GHC7)?
  2. 什么是涉及GADT和类型函数(如上面的append)的非平凡示例,GHC可以推断出类型?

2 个答案:

答案 0 :(得分:8)

我没有阅读过一篇文章,这完全超出了我的想法,但我相信问题几乎肯定是由类型系列造成的。你有一个类型

的功能
Vec a n1 -> Vec a n2 -> Vec a (Plus n1 n2)

但原则上,类型推断无法识别。我可以为你的代码添加第二个类型系列,

type family Plus' (a :: *) (b :: *) :: *
type instance Plus' Zero b = b
type instance Plus' (Succ a) b = Succ (Plus' a b)

看起来就像Plus,但名称不同。推理无法确定您是想要Plus还是Plus'。推论永远不会选择,永远不会让自己进入一个可能必须选择的位置(没有像IncoherentInstances这样的一些非常不愉快的事情)。因此,如果没有Plus现有,推理只能在处有效。我对类型检查背后的理论知之甚少,但我不认为类型族可以无处推断。

我相信论文的意思是推理在所有这些事物存在的情况下仍然有用,并且在没有它们的情况下仍然保持良好状态。例如,您可以编写使用您的append函数且没有类型签名的代码:

append3 a b c = a `append` b `append` c

额外奖励说明:DataKinds和封闭式家庭使一些代码更容易理解。我会写这样的代码:

data Nat = Zero | Succ Nat

type family Plus (a :: Nat) (b :: Nat) :: Nat where
  Plus Zero b = b
  Plus (Succ a) b = Succ (Plus a b)

data Vec :: * -> Nat -> * where
  VNil :: Vec a Zero
  VCons :: a -> Vec a n -> Vec a (Succ n)

答案 1 :(得分:4)

假设我们有以下定义:

append VNil         v2 = v2
append (VCons x xs) v2 = VCons x (append xs v2)

从定义中可以明显看出:

append :: Vec a n -> Vec a m -> Vec a p

好像你不介意Nat中的Vec索引,它的HM类型,一切都应该简单。

然后我们可以为nmp写出约束:

appendIndex Zero m ~ m                          -- from VNil case
appendIndex (Succ n) m ~ Succ (appendIndex n m) -- from VCons case

我没有读过JFP论文,但我认为 OutsideIn 无法解决这个问题。它必须能够在没有任何上下文的情况下解决它们,即知道某处是Plus

它可以用(pseudosyntax,type lambda)来解决约束:

append :: Vec a n -> Vec a m -> Vec a (rec f (λ n → case n of { Zero -> m ; Succ n' -> Succ (f n') }))

使用更好的编译器时,使用函数时,可以使用PlusPlus'统一加上匿名定义。


值得从更简单的论文中获取建议:FPH: First-class Polymorphism for Haskell,,特别是对于顶级定义:

  • 如果类型是HM,则可以推断
  • 其他类型(RankN)应注释,不能推断。

至于非平凡的例子,我想不可能是因为GHC类型的语言没有(甚至是非递归的)匿名类型函数(AFAIK)。

即使是非常简单的(非递归类型)示例也会失败

data NonEmpty :: * -> Bool -> * where
  VNil :: NonEmpty a False
  VCons :: a -> NonEmpty a b -> NonEmpty a True

append VNil         v2 = v2
append (VCons x xs) v2 = VCons x (append xs v2)

因为它必须推断

appendIndex True b = True
appendIndex False b = b

在类型级别上基本上是||。 GHC不支持(还有?)功能推广。所以你甚至不能写

append :: NonEmpty a x -> NonEmpty b y -> NonEmpty b (x '|| y)

但是有可能使http://www.cis.upenn.edu/~eir/papers/2014/promotion/promotion.pdf

成为可能