代码之家  ›  专栏  ›  技术社区  ›  romanb

一类不等式的GHC证明

  •  9
  • romanb  · 技术社区  · 8 年前

    出于教育的目的,我一直试图从《Idris的类型驱动开发》(即 RemoveElem.idr

    {-# LANGUAGE EmptyCase #-}
    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE StandaloneDeriving #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE TypeOperators #-}
    {-# LANGUAGE TypeInType #-}
    
    import Data.Kind
    import Data.Type.Equality
    import Data.Void
    
    -- | Inductively defined natural numbers.
    data Nat = Z | S Nat deriving (Eq, Show)
    
    -- | Singleton types for natural numbers.
    data SNat :: Nat -> Type where
        SZ :: SNat 'Z
        SS :: SNat n -> SNat ('S n)
    
    deriving instance Show (SNat n)
    
    -- | "Demote" a singleton-typed natural number to an ordinary 'Nat'.
    fromSNat :: SNat n -> Nat
    fromSNat SZ = Z
    fromSNat (SS n) = S (fromSNat n)
    
    -- | A decidable proposition.
    data Dec a = Yes a | No (a -> Void)
    
    -- | Propositional equality of natural numbers.
    eqSNat :: SNat a -> SNat b -> Dec (a :~: b)
    eqSNat  SZ     SZ    = Yes Refl
    eqSNat  SZ    (SS _) = No (\case {})
    eqSNat (SS _)  SZ    = No (\case {})
    eqSNat (SS a) (SS b) = case eqSNat a b of
        No  f    -> No (\case Refl -> f Refl)
        Yes Refl -> Yes Refl
    
    -- | A length-indexed list (aka vector).
    data Vect :: Nat -> Type -> Type where
        Nil   :: Vect 'Z a
        (:::) :: a -> Vect n a -> Vect ('S n) a
    
    infixr 5 :::
    
    deriving instance Show a => Show (Vect n a)
    
    -- | @Elem a v@ is the proposition that an element of type @a@
    -- is contained in a vector of type @v@. To be useful, @a@ and @v@
    -- need to refer to singleton types.
    data Elem :: forall a n. a -> Vect n a -> Type where
        Here  :: Elem x (x '::: xs)
        There :: Elem x xs -> Elem x (y '::: xs)
    
    deriving instance Show a => Show (Elem a v)
    
    ------------------------------------------------------------------------
    -- From here on, to simplify things, only vectors of natural
    -- numbers are considered.
    
    -- | Singleton types for vectors of 'Nat's.
    data SNatVect :: forall n. Nat -> Vect n Nat -> Type where
        SNatNil  :: SNatVect 'Z 'Nil
        SNatCons :: SNat a -> SNatVect n v -> SNatVect ('S n) (a '::: v)
    
    deriving instance Show (SNatVect n v)
    
    -- | "Demote" a singleton-typed vector of 'SNat's to an
    -- ordinary vector of 'Nat's.
    fromSNatVect :: SNatVect n v -> Vect n Nat
    fromSNatVect SNatNil = Nil
    fromSNatVect (SNatCons a v) = fromSNat a ::: fromSNatVect v
    
    -- | Decide whether a value is in a vector.
    isElem :: SNat a -> SNatVect n v -> Dec (Elem a v)
    isElem _  SNatNil        = No (\case {})
    isElem a (SNatCons b as) = case eqSNat a b of
        Yes Refl   -> Yes Here
        No notHere -> case isElem a as of
            Yes there   -> Yes (There there)
            No notThere -> No $ \case
                Here        -> notHere Refl
                There there -> notThere there
    
    type family RemoveElem (a :: Nat) (v :: Vect ('S n) Nat) :: Vect n Nat where
        RemoveElem a (a '::: as) = as
        RemoveElem a (b '::: as) = b '::: RemoveElem a as
    
    -- | Remove a (singleton-typed) element from a (non-empty, singleton-typed)
    -- vector, given a proof that the element is in the vector.
    removeElem :: forall (a :: Nat) (v :: Vect ('S n) Nat)
        . SNat a
        -> Elem a v
        -> SNatVect ('S n) v
        -> SNatVect n (RemoveElem a v)
    removeElem x prf (SNatCons y ys) = case prf of
        Here        -> ys
        There later -> case ys of
            SNatNil    -> case later of {}
            SNatCons{} -> SNatCons y (removeElem x later ys)
                -- ^ Could not deduce:
                --            RemoveElem a (y '::: (a2 '::: v2))
                --          ~ (y '::: RemoveElem a (a2 '::: v2))
    

    显然,类型系统需要说服 x 和 y 在代码的该分支中不可能相等,因此可以根据需要明确地使用类型族的第二个表达式来减少返回类型。我不知道怎么做。天真地说,我想要构造器 There 因此模式匹配 There later

    下面是一个明显多余的部分解决方案,它只是演示了GHC对递归调用进行类型检查所需的类型不等式:

            SNatCons{} -> case (x, y) of
                (SZ, SS _) -> SNatCons y (removeElem x later ys)
                (SS _, SZ) -> SNatCons y (removeElem x later ys)
    

    现在,这个工作:

    λ> let vec = SNatCons SZ (SNatCons (SS SZ) (SNatCons SZ SNatNil))
    λ> :t vec
    vec
      :: SNatVect ('S ('S ('S 'Z))) ('Z '::: ('S 'Z '::: ('Z '::: 'Nil)))
    λ> let Yes prf = isElem (SS SZ) vec
    λ> :t prf
    prf :: Elem ('S 'Z) ('Z '::: ('S 'Z '::: ('Z '::: 'Nil)))
    λ> let vec' = removeElem (SS SZ) prf vec
    λ> :t vec'
    vec' :: SNatVect ('S ('S 'Z)) ('Z '::: ('Z '::: 'Nil))
    λ> fromSNatVect vec'
    Z ::: (Z ::: Nil)
    

    正如@chi评论中暗示的,并在 HTNW's answer ,我试图通过写作来解决错误的问题 removeElem 有了上面的类型签名和类型系列,如果我可以的话,生成的程序将是不正确的类型。

    以下是我根据HTNW的答案所做的更正(在继续阅读之前,您可能需要阅读)。

    SNatVect s型。我认为有必要写 fromSNatVect ,但肯定不是:

    data SNatVect (v :: Vect n Nat) :: Type where
        SNatNil  :: SNatVect 'Nil
        SNatCons :: SNat a -> SNatVect v -> SNatVect (a '::: v)
    
    deriving instance Show (SNatVect v)
    
    fromSNatVect :: forall (v :: Vect n Nat). SNatVect v -> Vect n Nat
    -- implementation unchanged
    

    现在有两种写作方法 拆卸 . 第一个需要一个 Elem 蛇形 Vect :

    removeElem :: forall (a :: Nat) (n :: Nat) (v :: Vect ('S n) Nat)
        . Elem a v
        -> SNatVect v
        -> Vect n Nat
    removeElem prf (SNatCons y ys) = case prf of
        Here        -> fromSNatVect ys
        There later -> case ys of
            SNatNil    -> case later of {}
            SNatCons{} -> fromSNat y ::: removeElem later ys
    

    SElem ,一个 并返回 蛇形 ,使用 RemoveElem

    data SElem (e :: Elem a (v :: Vect n k)) where
        SHere  :: forall x xs. SElem ('Here :: Elem x (x '::: xs))
        SThere :: forall x y xs (e :: Elem x xs). SElem e -> SElem ('There e :: Elem x (y '::: xs))
    
    type family RemoveElem (xs :: Vect ('S n) a) (e :: Elem x xs) :: Vect n a where
        RemoveElem (x '::: xs) 'Here = xs
        RemoveElem (x '::: xs) ('There later) = x '::: RemoveElem xs later
    
    sRemoveElem :: forall (xs :: Vect ('S n) Nat) (e :: Elem x xs)
        . SElem e
        -> SNatVect xs
        -> SNatVect (RemoveElem xs e)
    sRemoveElem prf (SNatCons y ys) = case prf of
        SHere        -> ys
        SThere later -> case ys of
            SNatNil    -> case later of {}
            SNatCons{} -> SNatCons y (sRemoveElem later ys)
    

    有趣的是,这两个版本都不需要将要删除的元素作为单独的参数传递,因为该信息包含在 / 塞勒姆 价值。这个 value removeElem_auto 变量可能有点混乱,因为它将只有向量作为显式参数,如果隐式 prf 参数没有与其他证明一起显式使用。

    1 回复  |  直到 8 年前
        1
  •  5
  •   HTNW    8 年前

    考虑 [1, 2, 1] RemoveElem 1 [1, 2, 1] 是 [2, 1] . 现在,电话 removeElem 1 (There $ There $ Here) ([1, 2, 1] :: SNatVect 3 [1, 2, 1]) :: SNatVect 2 [2, 1] ,应该编译。这是错误的。这个 Elem [1, 2] ,但类型签名表明它必须是 [2,1] .

    第一, SNatVect Nat 论据:

    data SNatVect :: forall n. Nat -> Vect n a -> Type where ...
    

    首先是 n ,第二个是未命名的 . 根据结构 ,他们总是平等的。它允许 蛇形 作为一个平等的证明来翻倍,但它可能不是这样的意图。你可能是说

    data SNatVect (n :: Nat) :: Vect n Nat -> Type where ...
    

    无法在源Haskell中使用 -> 语法。然而,当GHC打印这种类型时,有时会得到

    SNatVect :: forall (n :: Nat) -> Vect n Nat -> Type
    

    但这是多余的。你可以把 纳特 forall 论证,并从 Vect s型:

    data SNatVect (xs :: Vect n Nat) where
      SNatNil  :: SNatVect 'Nil
      SNatCons :: SNat x -> SNatVect xs -> SNatVect (x '::: xs)
    

    这给了

    SNatVect :: forall (n :: Nat). Vect n Nat -> Type
    

    removeElem :: forall (n :: Nat) (x :: Nat) (xs :: Vect (S n) Nat).
                  Elem x xs -> SNatVect xs -> Vect n Nat
    

    请注意 SNat 参数已不存在,返回类型如何是简单的 . 这个 参数使类型“太大”,所以当函数不起作用时,您会发现它有点起作用。这个 返回类型表示您跳过了步骤。大致上,每个函数都有三种形式:基本函数, f :: a -> b -> c ;类型级别1, type family F (x :: a) (y :: b) :: c ;和依赖的那个, f :: forall (x :: a) (y :: b). Sing x -> Sing y -> Sing (F x y) . 每种方法都是以“相同”的方式实现的,但是如果不实现前一种方法就试图实现一种方法,肯定会让人感到困惑。

    现在,你可以把这个抬起来一点:

    data SElem (e :: Elem x (xs :: Vect n k)) where
      SHere :: forall x xs. SElem ('Here :: Elem x (x '::: xs))
      SThere :: forall x y xs (e :: Elem x xs). SElem e -> SElem ('There e :: Elem x (y '::: xs))
    
    type family RemoveElem (xs :: Vect (S n) a) (e :: Elem x xs) :: Vect n a
    

    removeElem 和 RemoveElem . 参数的重新排序是因为 e xs ,因此需要相应地订购。另一种选择是: xs型 争论是从 福尔 'd-隐式给定到显式给定,然后 Sing xs

    最后,您可以编写此函数:

    sRemoveElem :: forall (xs :: Vect (S n) Nat) (e :: Elem x xs).
                   SElem e -> SNatVect xs -> SNatVect (RemoveElem xs e)
    
    推荐文章