我有一个更高的订单列表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]
答案 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中,值级和类型级编程之间没有区别,所以根本没有这样的问题。