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

Ser*_*ich 7 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
Run Code Online (Sandbox Code Playgroud)

它适用于简单的情况:

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’
Run Code Online (Sandbox Code Playgroud)

但是这样的情况呢?

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)
Run Code Online (Sandbox Code Playgroud)

dfe*_*uer 7

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

{-# LANGUAGE TypeFamilies, PolyKinds #-}

type family Any :: k
Run Code Online (Sandbox Code Playgroud)

该Any类型的家庭是每个善良.所以你可以有一个类型级别列表Int ': Char ': Any,其中Any使用的是kind [*].但是,有没有办法来解构Any为':或[]; 它没有任何这种明智的形式.由于Any存在类型族,GHC无法按照您希望的方式安全地使用归纳.

如果你想让归纳在类型列表上正常工作,你真的需要像Benjamin Hodgson所说的那样使用单身或类似的东西.您不需要仅传递类型级别列表,还需要传递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)
Run Code Online (Sandbox Code Playgroud)

你可能希望证明

associative :: p1 x -> p2 y -> p3 z -> ((x :+ y) :+ z) :~: (x :+ (y :+ z))
Run Code Online (Sandbox Code Playgroud)

但你不能,因为这需要感应x和y.但是,你可以证明

associative :: Natty x -> Natty y -> p3 z -> ((x :+ y) :+ z) :~: (x :+ (y :+ z))
Run Code Online (Sandbox Code Playgroud)

没有麻烦.

  • @SergeyMitskevich如果你是依赖类型的新手,我建议不要使用Haskell来学习.Haskell不是一种依赖类型的语言; 像单身人士这样的技巧仅仅是模仿依赖类型的技巧.我建议先拿起一个实际依赖类型的系统,然后在理解了这个概念后再学习Haskell编码.[_Ph的力量](https://cs.ru.nl/~wouters/Publications/ThePowerOfPi.pdf)是我个人最喜欢的介绍 (2认同)

Ben*_*son 5

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

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

data Elem xs x where
    Here :: Elem (x ': xs) x
    There :: Elem xs x -> Elem (y ': xs) x
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

All证明了给定谓词适用于列表的每个元素.它的结构是一系列的证明f.

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

type IsSubset xs ys = All (Elem ys) xs
Run Code Online (Sandbox Code Playgroud)

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

您可以IsSubset通过破解类型类系统来自动验证值的搜索,但这是另一篇文章.

  • 很酷的咆哮,但我怀疑这对OP有帮助. (2认同)