Haskell单身人士:typelits包

时间:2017-10-08 18:38:34

标签: haskell dependent-type template-haskell singleton-type

我很难说服编译器我的类型是正确的。经常 使用NatZero构造函数的Succ非常简单(目标是为长度索引列表(replicate)编写Vect函数:)< / p>

replicate' :: SNat n -> a -> Vect n a
replicate' SZero _ = Nil
replicate' (SSucc n) a = a :> replicate' n a

但是常规Nat非常缓慢。

因此,有一个软件包可以在单例库中镜像GHC.TypeLits以获得更快的Nats。 但我不能让上面的例子与之合作:

sameNat :: forall a b. (KnownNat a, KnownNat b) => SNat a -> SNat b -> Maybe (a :~: b)
sameNat x y
  | natVal (Proxy :: Proxy a) == natVal (Proxy :: Proxy b) = Just (unsafeCoerce Refl)
  | otherwise            = Nothing

replicate'' :: (KnownNat n) => SNat n -> a -> Vect n a
replicate'' n a =
  case sameNat n (sing :: Sing 0) of
    Just Refl -> Nil
    Nothing   -> a ::> replicate'' (sPred n) a

这在最后一行没有成功:

Couldn't match type ‘n’
                     with ‘(n GHC.TypeNats.- 1) GHC.TypeNats.+ 1’

2 个答案:

答案 0 :(得分:2)

问题是,sameNat n (sing :: Sing 0)在n为零的情况下(当您在n ~ 0上进行模式匹配时)为您提供可用的Just Refl证明,但如果n 零它只给你Nothing。这并没有告诉你关于n的任何内容,所以只要类型检查器知道你可以在Nothing分支内调用完全相同的一组内容,而不需要调用sameNat首先{1}}(特别是,您无法使用sPred,因为这需要1 <= n)。

因此,我们需要对 提供n ~ 0 提供1 <= n证据的证据进行模式匹配。像这样:

data IsZero (n :: Nat)
  where Zero :: (0 ~ n) => IsZero n
        NonZero :: (1 <= n) => IsZero n
deriving instance Show (IsZero n)

然后我们可以这样写replicate''

isZero :: forall n. SNat n -> IsZero n
isZero n = _

replicate'' :: SNat n -> a -> Vect n a
replicate'' n x = case isZero n
                    of Zero -> Nil
                       NonZero -> x ::> replicate'' (sPred n) x

当然,这只是将问题转移到实施isZero功能,这个功能并没有给我们带来任何好处,但我会坚持使用它,因为它&#39方便将此作为您要使用Nat创建的任何其他归纳定义的基础。

所以,实施isZero。当然,我们可以用sameNat处理零情况,但这对非零情况没有帮助。单身人士套餐还提供了Data.Singletons.Decide,它为您提供了一种基于单身人士获得类型或不等等的证明的方法。所以我们可以这样做:

isZero :: forall n. SNat n -> IsZero n
isZero n = case n %~ (SNat @0)
             of Proved Refl -> Zero
                Disproved nonsense -> NonZero

可悲的是,这也不起作用! Proved案例很好(与sameNat相同,基本上是Just Refl。但是&#34;不平等的证据&#34;以nonsense绑定到类型(n :~: 0) -> Void的函数的形式出现,如果我们假设整数(没有恶作剧)那么这样的函数的存在&#34;证明&#34;我们无法构建n :~: 0值,这证明n绝对不是0。但这与证明1 <= n的证据相距甚远; 我们可以看到,如果n不是0,那么它必须至少为1,来自自然数的属性,但GHC并不知道这一点。

另一种方法是在Ord上使用单身人士SNat @1 :%<= n支持和模式匹配:

isZero :: forall n. SNat n -> IsZero n
isZero n = case (SNat @1) %:<= n
             of STrue -> NonZero
                SFalse -> Zero

但这也不起作用,因为STrueSFalse只是类型级TrueFalse的单例,与原始比较断开。我们无法从此获得0 ~ n 1 <= n的证明(同样地,通过与{{进行比较,无法使其发挥作用) 1}}或者)。这是类型检查器布尔盲,基本上。

最终我在代码中无法令人满意地解决这个问题。据我所知,我们错过了原语;我们需要能够以对应类型给出SNat @0<约束的方式比较单例,或者我们需要切换<=是零还是非零。

所以我被骗了:

Nat

由于isZero :: forall n. SNat n -> IsZero n isZero n = case n %~ (SNat @0) of Proved Refl -> Zero Disproved _ -> unsafeCoerce (NonZero @1) 仅包含NonZero为1或更多的证据,而不包含有关n的任何其他信息,因此您可以不安全地强制证明1为1或更高。< / p>

这是一个完整的工作示例:

n

请注意,与使用{-# LANGUAGE DataKinds , GADTs , KindSignatures , ScopedTypeVariables , StandaloneDeriving , TypeApplications , TypeOperators #-} import GHC.TypeLits ( type (<=), type (-) ) import Data.Singletons.TypeLits ( Sing (SNat), SNat, Nat ) import Data.Singletons.Prelude.Enum ( sPred ) import Data.Singletons.Decide ( SDecide ((%~)) , Decision (Proved, Disproved) , (:~:) (Refl) ) import Unsafe.Coerce ( unsafeCoerce ) data IsZero (n :: Nat) where Zero :: (0 ~ n) => IsZero n NonZero :: (1 <= n) => IsZero n deriving instance Show (IsZero n) isZero :: forall n. SNat n -> IsZero n isZero n = case n %~ (SNat @0) of Proved Refl -> Zero Disproved _ -> unsafeCoerce (NonZero @1) data Vect (n :: Nat) a where Nil :: Vect 0 a (::>) :: a -> Vect (n - 1) a -> Vect n a deriving instance Show a => Show (Vect n a) replicate'' :: SNat n -> a -> Vect n a replicate'' n x = case isZero n of Zero -> Nil NonZero -> x ::> replicate'' (sPred n) x head'' :: (1 <= n) => Vect n a -> a head'' (x ::> _) = x main :: IO () main = putStrLn . (:[]) . head'' $ replicate'' (SNat @1000000000000000000000000000000000000000000000000000000) '\x1f60e' 的KA Buhr建议方法不同,此处复制代码实际上是使用类型检查器来验证它是否根据{{1}构造unsafeCoerce提供,而他们的建议要求你相信代码执行此操作(工作的实际内容由Vect n a依靠SNat n完成)并且只确保调用者< / em>始终使用iterateInt。您必须信任的唯一代码(由编译器取消选中)是SNat n确实暗示Vect n a,在Refuted _ :: Decision (n :~: 0)内(您可以重复使用以编写许多其他函数)需要打开1 <= n是否为零。

当您尝试使用isZero实现更多功能时,您会发现很多&#34;显而易见的&#34; GHC对SNat的属性不了解的事情非常痛苦。来自Vect包的Nat有很多有用的证据可供您使用(例如,如果您尝试实施Data.Constraint.Nat,您可能最终需要constraints所以,当你知道drop :: (k <= n) => SNat k -> Vect n a -> Vect (n - k) a然后leTrans时,你可以实际模式匹配以剥离另一个元素)。如果你想用你信任的代码实现你的操作并且不安全地将这些类型排成一行,那么避免这种无助就是K.A. Buhr的方法可以提供很大的帮助。

答案 1 :(得分:1)

据我所知,您采取的确切方法无法按照您的意愿行事。 sameNat在运行时进行评估,因此其决定&#34;因类型检查器无法使用,因此无法根据区分案例结构的两个分支执行任何类型推断。

您可能对我的回答感兴趣 How to deconstruct an SNat (singletons), 关于一个类似的问题,它提供了一个完全通过使用类型类来避免unsafeCoerce的实现。但是,正如@Ben在评论中指出的那样,由于使用了类型类,每当定义大小为n的向量时,编译器必须遵循n实例定义链(并且已编译的代码可以明确地包含n嵌套实例字典的结构),这使得这对于实际代码来说是不切实际的。例如,一百万个元素向量可能会导致编译器运行太长时间和/或使用太多内存才能被接受。

对于实际代码,我建议手动进行类型检查(即,验证所写的代码是否类型安全)和 用unsafeCoerce强制它:

replicate1 :: (KnownNat n) => SNat n -> a -> Vect n a
replicate1 n a =
  unsafeCoerce (iterate (unsafeCoerce . (a ::>)) Nil
                !! fromInteger (fromSing n))

显然,这个定义错过了这个特定定义的依赖类型点,但希望你可以构建一组可信(手动类型检查)原语,然后在它们之上构建非平凡的算法,可以从更严格的类型检查中受益。

请注意,在这种特殊情况下,您甚至不需要n参数,因此您可以写:

{-# LANGUAGE ScopedTypeVariables #-}

replicate2 :: forall a n . (KnownNat n) => a -> Vect n a
replicate2 a =
  unsafeCoerce (iterate (unsafeCoerce . (a ::>)) Nil
        !! fromInteger (fromSing (SNat :: SNat n)))

无论如何,一个完整的例子是:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE KindSignatures      #-}
{-# LANGUAGE TypeOperators       #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.Singletons
import Data.Singletons.Prelude
import Data.Singletons.TypeLits
import Unsafe.Coerce

infixr 5 ::>
data Vect (n :: Nat) a where
  Nil :: Vect 0 a
  (::>) :: a -> Vect (n :- 1) a -> Vect n a

instance (Show a) => Show (Vect n a) where
  showsPrec _ Nil = showString "Nil"
  showsPrec d (x ::> xs) = showParen (d > prec) $
showsPrec (prec+1) x . showString " ::> " . showsPrec prec xs
where prec=5

replicate1 :: (KnownNat n) => SNat n -> a -> Vect n a
replicate1 n a =
  unsafeCoerce (iterate (unsafeCoerce . (a ::>)) Nil
        !! fromInteger (fromSing n))

replicate2 :: forall a n . (KnownNat n) => a -> Vect n a
replicate2 a =
  unsafeCoerce (iterate (unsafeCoerce . (a ::>)) Nil
        !! fromInteger (fromSing (SNat :: SNat n)))

head' :: Vect (n :+ 1) a -> a
head' (x ::> _) = x

tail' :: ((n :+ 1) :- 1) ~ n => Vect (n :+ 1) a -> Vect n a
tail' (_ ::> v) = v

main = do print (replicate2 False   :: Vect 0 Bool)
          print (replicate2 "Three" :: Vect 3 String)
          print (head' (tail' (replicate2 "1M" :: Vect 1000000 String)))

          print (replicate1 (SNat :: SNat 0) False   :: Vect 0 Bool)
          print (replicate1 (SNat :: SNat 3) "Three" :: Vect 3 String)
          print (head' (tail' (replicate1 (SNat :: SNat 1000000) "1M" :: Vect 1000000 String)))