我有一个简单的长度索引向量类型和长度索引向量的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 +类型族"的存在下工作。在这方面,我有两个问题:
append
)的非平凡示例,GHC可以推断出类型? 答案 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类型,一切都应该简单。
然后我们可以为n
,m
和p
写出约束:
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') }))
使用更好的编译器时,使用函数时,可以使用Plus
或Plus'
统一加上匿名定义。
值得从更简单的论文中获取建议:FPH: First-class Polymorphism for Haskell,,特别是对于顶级定义:
至于非平凡的例子,我想不可能是因为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
成为可能