在计算异构列表的长度时,我遇到了模糊类型变量的问题。问题似乎是长度函数在HList的元素中不是多态的。
首先,我正在使用的所有语言扩展程序:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
自然数的定义如下:
data Nat = Zero | Succ Nat
data HNat n where
HZero :: HNat Zero
HSucc :: HNat n -> HNat (Succ n)
异构列表被定义为GADT
data HList (ts :: [*]) where
HNil :: HList '[]
(:::) :: t -> HList ts -> HList (t ': ts)
infixr 5 :::
我们可以查询HList的长度:
class HLength xs n | xs -> n where
hLength :: HList xs -> HNat n
instance HLength '[] Zero where
hLength HNil = HZero
instance HLength xs n => HLength (x ': xs) (Succ n) where
hLength (_ ::: xs) = HSucc (hLength xs)
我们可以索引到HList并检索i
- 元素:
class HIndex xs i y | xs i -> y where
hIndex :: HList xs -> HNat i -> y
instance HIndex (x ': xs) Zero x where
hIndex (x ::: xs) HZero = x
instance HIndex xs i y => HIndex (x ': xs) (Succ i) y where
hIndex (x ::: xs) (HSucc i) = hIndex xs i
有了这个,我将证明这个问题。
假设我构造一个包含一个函数的HList,该函数本身接受一个HList并用它的第一个元素做一些事情。
test1 = ((\l n -> l `hIndex` HZero || n == 0) ::: HNil)
在这种情况下,第一个元素必须是Bool。派生的类型签名确认了约束:
:: (Eq a, Num a, HIndex xs 'Zero Bool) =>
HList '[HList xs -> a -> Bool]
现在我想计算列表test
的长度:
test2 = hLength test1
不幸的是,这无法使用以下错误消息进行编译:
HListConstraints.hs:55:17:
No instance for (HIndex xs0 'Zero Bool)
arising from a use of ‘test1’
The type variable ‘xs0’ is ambiguous
Note: there is a potential instance available:
instance HIndex (x : xs) 'Zero x
-- Defined at HListConstraints.hs:42:10
In the first argument of ‘hLength’, namely ‘test1’
In the expression: hLength test1
In an equation for ‘test2’: test2 = hLength test1
Failed, modules loaded: none.
列表元素的约束导致模糊的类型变量。
我的理解是我需要在传递给它的列表元素中使hLength
多态。 我该怎么做?
答案 0 :(得分:4)
问题不在于HLength
,而且它已经是多态的。问题出在HIndex
,这在xs
参数中是不必要的。
从HIndex xs 'Zero Bool
开始,我们应该可以推断某些xs
Bool ': xs'
具有xs'
形状。由于HIndex
实现由Nat
类参数驱动,我们可以在实例头中保留其他参数未指定,而是在实例约束中对它们进行优化,使GHC能够进行上述推断: / p>
class HIndex xs i y | xs i -> y where
hIndex :: HList xs -> HNat i -> y
instance (xs ~ (x ': xs')) => HIndex xs Zero x where
hIndex (x ::: xs) HZero = x
instance (xs ~ (y ': xs'), HIndex xs' i x) => HIndex xs (Succ i) x where
hIndex (x ::: xs) (HSucc i) = hIndex xs i
之后:
test1 :: (Eq a, Num a) => HList '[HList (Bool : xs') -> a -> Bool]
当HIndex
使Num a
默认为a
时,Integer
约束消失,其余约束得到解决:
> :t hLength test1
hLength test1 :: HNat ('Succ 'Zero)
使用类型类进行计算时的一般规则是将类型依赖项移动到实例约束中,并且只在实例头中执行那些对实例定义必不可少的模式匹配。这会将实例头匹配问题(当参数不正确时立即失败)转化为约束解决问题(可以根据来自程序其他部分的信息来懒散地解决)。
答案 1 :(得分:0)
或者,我们可以将test1
的类型明确定义为单独的test1 :: HList ((HList (Bool ': xs) -> Int -> Bool) ': '[])
。
这使我们可以保留实例定义,并消除András提出的xs ~ (x ': xs')
形式的类型相等约束。
然后选择是是否要明确定义test1
的类型。使用类型相等约束可以定义test1
而无需提供类型注释。