我很难说服编译器我的类型是正确的。经常
使用Nat
和Zero
构造函数的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’
答案 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
但这也不起作用,因为STrue
和SFalse
只是类型级True
和False
的单例,与原始比较断开。我们无法从此获得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>始终使用iterate
和Int
。您必须信任的唯一代码(由编译器取消选中)是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)))