检查一个类型级别列表是否包含另一个

时间:2016-07-05 18:02:42

标签: haskell

如果一个类型级列表包含另一个类型级列表,是否可以编写一个返回True的类型级函数?

这是我的尝试:

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module TypePlayground where

import Data.Type.Bool

type family InList (x :: *) (xs :: [*]) where
    InList x '[] = 'False
    InList x (x ': xs) = 'True
    InList x (a ': xs) = InList x xs
type family ListContainsList (xs :: [*]) (ys :: [*]) where
    ListContainsList xs (y ': ys) = InList y xs && ListContainsList xs ys
    ListContainsList xs '[] = 'True

适用于简单的情况:

data A
data B
data C

test1 :: (ListContainsList '[A, B, C] '[C, A] ~ 'True) => ()
test1 = ()
-- compiles.
test2 :: (ListContainsList '[A, B, C] '[B, C, A] ~ 'True) => ()
test2 = ()
-- compiles.
test3 :: (ListContainsList (A ': B ': '[C]) (B ': A ': '[C]) ~ 'True) => ()
test3 = ()
-- compiles.
test4 :: (ListContainsList '[A, C] '[B, C, A] ~ 'True) => ()
test4 = ()
-- Couldn't match type ‘'False’ with ‘'True’

但是这样的情况呢?

test5 :: (ListContainsList (A ': B ': a) a ~ 'True) => ()
test5 = ()
-- Should compile, but fails:
-- Could not deduce (ListContainsList (A : B : a0) a0 ~ 'True)
-- from the context (ListContainsList (A : B : a) a ~ 'True)

2 个答案:

答案 0 :(得分:7)

麻烦的是你已经通过对包含列表的结构的归纳来定义你的子集类型系列,但是你传递的是一个完全多态(未知)的列表,其结构对于GHC来说是一个谜。你可能认为GHC无论如何都能使用感应,但你错了。特别是,正如每个类型都有未定义的,因此每个都有“卡住”类型。一个值得注意的例子,GHC在内部使用并通过(IIRC)GHC.Exts出口:

{-# LANGUAGE TypeFamilies, PolyKinds #-}

type family Any :: k

Any类型系列位于每种类型中。因此,您可以拥有类型级别列表Int ': Char ': Any,其中Any用于[*]种类。但是没有办法将Any解构为':[];它没有任何这种明智的形式。由于类型系列如Any存在,GHC无法按照您希望的方式安全地使用归纳法。

如果你想让归纳在类型列表上正常工作,你真的需要使用单身人士或类似的人,正如本杰明霍奇森所建议的那样。您不需要仅传递类型级别列表,还需要传递GADT,以证明类型级别列表已正确构造。递归破坏GADT会对类型级别列表执行归纳。

对于类型级自然数,存在相同的限制。

data Nat = Z | S Nat
type family (x :: Nat) :+ (y :: Nat) :: Nat where
   'Z :+ y = y
   ('S x) :+ y = 'S (x :+ y)

data Natty (n :: Nat) where
  Zy :: Natty 'Z
  Sy :: Natty n -> Natty ('S n)

您可能希望证明

associative :: p1 x -> p2 y -> p3 z -> ((x :+ y) :+ z) :~: (x :+ (y :+ z))

但你不能,因为这需要在xy上进行归纳。但是,您可以证明

associative :: Natty x -> Natty y -> p3 z -> ((x :+ y) :+ z) :~: (x :+ (y :+ z))

毫无困难。

答案 1 :(得分:5)

对于Haskell社区特有的布尔类型家族似乎有一种痴迷。不要使用它们!在使用这种测试的结果时,你为自己做的工作比必要的多。

子集是命题,可以证明具有信息丰富的证据。这是设计此类证据的一种简单方法。首先,可以在列表中找到元素的证据类型:

data Elem xs x where
    Here :: Elem (x ': xs) x
    There :: Elem xs x -> Elem (y ': xs) x

Elem的结构类似于自然数(比较There (There Here)S (S Z)),但有更多类型。要证明元素在列表中,您可以给它索引。

data All f xs where
    Nil :: All f '[]
    Cons :: f x -> All f xs -> All f (x ': xs)

All证明给定谓词适用于列表的每个元素。它的结构为f的证明列表。

现在,列表是另一个列表的子集的证明类型很容易使用这些机器写下来。

type IsSubset xs ys = All (Elem ys) xs

IsSubset表示为xs中可以找到ys的每个元素的证明列表。

您可以通过黑客攻击类型类系统来自动搜索IsSubset值,但这是另一篇文章。