高阶列表和类型族的歧义错误

时间:2014-11-30 21:21:21

标签: haskell gadt type-families

我有一个更高的订单列表GADT定义如下:

data HList as where
    HNil :: HList '[]
    HCons :: a -> HList as -> HList (a ': as)

我可以写一个函数来获取该列表中的第一个项目,只要它是非空的:

first :: HList (a ': as) -> HList '[a]
first (HCons a _) = HCons a HNil

但是,如果我创建一个新类型Cat和一个类型系列,它只是将类型级别列表中的每个类型应用于Cat(类似于类型级别映射):

newtype Cat a = Cat a

type family MapCat (as :: [*]) :: [*] where
    MapCat '[] = '[]
    MapCat (a ': as) = Cat a ': MapCat as

类型CatList可将类型列表转换为HList,其中包含应用于Cat的所有类型:

type CatList cs = HList (MapCat cs)

我无法编写适用于CatList的类似功能。

first' :: CatList (a ': as) -> CatList '[a]
first' (HCons a _) = HCons a HNil

它出错:

Couldn't match type ‘MapCat as0’ with ‘MapCat as’
NB: ‘MapCat’ is a type function, and may not be injective
The type variable ‘as0’ is ambiguous
Expected type: CatList (a : as) -> CatList '[a]
  Actual type: CatList (a : as0) -> CatList '[a]
In the ambiguity check for:
  forall a (as :: [*]). CatList (a : as) -> CatList '[a]
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the type signature:
  Testing.fst :: CatList (a : as) -> CatList '[a]

出了什么问题? Here is a gist with the entire code

1 个答案:

答案 0 :(得分:1)

让我们从一个更简单的例子开始:

type family F a

err :: F a -> ()
err _ = ()

引发

Couldn't match type ‘F a0’ with ‘F a’ NB:
F is a type function, and may not be injective
The type variable a0 is ambiguous
Expected type: F a -> ()
  Actual type: F a0 -> () …

虽然编译得很好:

ok :: (a ~ Int) => F a -> ()
ok _ = ()

而且:

type family F a where
    F a = a

ok :: F a -> ()
ok _ = ()

这是另一个更接近你的问题的例子:

data D = C

data Id (d :: D) where
    Id :: Id d

type family TF_Id (d :: D) :: D where
    TF_Id C = C

err :: Id (TF_Id d) -> ()
err _ = ()

错误是

Couldn't match type ‘TF_Id d0’ with ‘TF_Id d’ NB:
TF_Id is a type function, and may not be injective
The type variable d0 is ambiguous
Expected type: Id (TF_Id d) -> ()
  Actual type: Id (TF_Id d0) -> () …

但这有效:

ok :: Id (TF_Id C) -> ()
ok _ = ()

和此:

type family TF_Id (d :: D) :: D where
    TF_Id x = x

ok :: Id (TF_Id d) -> ()
ok _ = ()

因为TF_Id a在两种情况下都会立即缩减为a

因此编译器每次无法减少SomeTypeFamily a时都会抛出错误,其中a是某种类型变量,而之前未确定。

所以如果我们想解决这个问题:

type family TF_Id (d :: D) :: D where
    TF_Id C = C

err :: Id (TF_Id d) -> ()
err _ = ()

如果不重新定义类型系列,我们需要在d类型签名中确定err。最简单的方法是

type family TF_Id (d :: D) :: D where
    TF_Id C = C

ok :: Id d -> Id (TF_Id d) -> ()
ok _ _ = ()

或者我们可以为此定义数据类型:

data Proxy (d :: D) = Proxy

ok :: Proxy d -> Id (TF_Id d) -> ()
ok _ _ = ()

现在回到first'函数:

data Proxy (a :: [*]) = Proxy

first' :: Proxy (a ': as) -> CatList (a ': as) -> CatList '[a]
first' _ (HCons a _) = HCons a HNil

编译好。您可以像这样使用它:

main = print $ first' (Proxy :: Proxy '[Int, Bool]) (HCons (Cat 3) $ HCons (Cat True) HNil)

打印Cat3:[]

但编译器必须知道的唯一减少MapCat as的东西是as的弱头正常形式,所以我们实际上不需要在Proxy :: Proxy '[Int, Bool]中提供这种额外的类型信息。这是一个更好的方法:

data ListWHNF (as :: [*]) where
    LZ :: ListWHNF '[]
    LS :: ListWHNF as -> ListWHNF (a ': as)

first' :: ListWHNF (a ': as) -> CatList (a ': as) -> CatList '[a]
first' _ (HCons a _) = HCons a HNil

main = print $ first' (LS $ LS LZ) (HCons (Cat 3) $ HCons (Cat True) HNil)

所以first'现在收到一些东西,看起来像列表的长度。但我们可以静态地这样做吗?不是那么快:

data ListWHNF (as :: [*]) where
    LZ :: ListWHNF '[]
    LS :: ListWHNF as -> ListWHNF (a ': as)

data HList as ln where
    HNil  :: HList '[] LZ
    HCons :: a -> HList as ln -> HList (a ': as) (LS ln)

抛出Data constructor LZ comes from an un-promotable type ListWHNF …。我们不能将索引数据类型用作Haskell中的索引。

有某种功能依赖,允许将一种类型的术语与另一种类型的弱头正常形式相关联,我们可能会做到这一点,但我不知道GHC中的任何类似的东西(但我不是专家)。在Agda中,这只是

_^_ : ∀ {α} -> Set α -> ℕ -> Set α
A ^ 0     = Lift ⊤
A ^ suc n = A × A ^ n

因此,如果唯一的事情,我们需要推断一个列表,是它的长度,那么我们可以用n嵌套元组替换一个列表。滥用符号,[?, ?] :: [*]变为(? :: *, (? :: *, Lift ⊤)),所有这些?现在可以自动推断为的居民(Agda相当于())。但是在Agda中,值级和类型级编程之间没有区别,所以根本没有这样的问题。