[英]How can I ‘convince’ GHC that I've excluded a certain case?
I have the following toy implementation of a non-empty list ( NEList
) datatype: 我有以下非空列表(
NEList
)数据类型的玩具实现:
-- A type to describe whether or not a list is empty.
data Emptiness :: Type where
Empty :: Emptiness
NotEmpty :: Emptiness
-- The list itself. Note the existential type in `Cons'.
data List :: Emptiness -> Type -> Type where
Nil :: List 'Empty a
Cons :: a -> List e a -> List 'NotEmpty a
type EList a = List 'Empty a
type NEList a = List 'NotEmpty a
For example it's very easy to define a 'safe head' function that only operates on non-empty lists: 例如,定义一个仅在非空列表上运行的“安全头”功能非常容易:
eHead :: NEList a -> a
eHead (Cons a _) = a
The last is similarly easy, but has a little complication: 最后一样容易,但有一点复杂性:
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _
b@(Cons _ _)) = eLast b
The reason the pattern needs to be like this is to convince GHC that the type of b
is indeed List 'NotEmpty
, instead of an unknown existential type. 模式需要这样的原因是为了说服GHC
b
的类型确实是List 'NotEmpty
,而不是未知的存在类型。 The following code fails for that reason: ( Couldn't match type 'e' with ''NotEmpty' ...
) 以下代码因此失败:(
Couldn't match type 'e' with ''NotEmpty' ...
)
eLast :: NEList a -> a
eLast (Cons a Nil) = a
eLast (Cons _
b) = eLast b
I'm fully aware why this is happening. 我完全清楚为什么会这样。 What I'd like to know is, can I avoid having to write
b@(Cons _ _)
every time? 我想知道的是,我是否可以避免每次都写
b@(Cons _ _)
? Is there some other way I can restrict the type, so that GHC knows that b
is referring strictly to something of type List 'NotEmpty
? 有没有其他方法可以限制类型,所以GHC知道
b
严格指代List 'NotEmpty
类型的东西?
One obvious way is to use unsafeCoerce
but this defeats the point of the exercise. 一个显而易见的方法是使用
unsafeCoerce
但这会破坏练习的重点。
For the sake of reproducibility, here is my preamble: 为了重现性,这是我的序言:
{-# OPTIONS_GHC -Wall -Werror #-} -- To prevent missed cases. {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} import Data.Kind
One thing you can do is pass around an alternative for empty lists: 您可以做的一件事是传递空列表的替代方案:
lastDef :: a -> List e a -> a
lastDef a Nil = a
lastDef _ (Cons a b) = lastDef a b
Then wrap it up once at the top level. 然后在顶层将其包裹一次。
last :: NEList a -> a
last (Cons a b) = lastDef a b
Extending this pattern to foldr
and foldr1
is left as an exercise for the reader. 将此模式扩展到
foldr
和foldr1
留给读者练习。
You've "defined" NEList
(I say that in a loose way) the same way base
defines NonEmpty
: as a single element tacked onto the head of a potentially empty list. 你已经“定义”
NEList
(我以松散的方式说) base
定义NonEmpty
方式相同:将一个元素添加到潜在空列表的头部。
data NonEmpty a = a :| [a]
Another presentation of NonEmpty
instead places that single element at the end. NonEmpty
另一个演示文稿将单个元素放在最后。
data NonEmpty a = Single a | Multiple a (NonEmpty a)
This presentation, no surprise, makes eLast
easy: 这个演示,毫不奇怪,使
eLast
变得简单:
eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs
Whenever you want multiple sets of constructors on the same type, look to pattern synonyms. 每当您想要同一类型的多组构造函数时,请查看模式同义词。 Instead of the base
NonEmpty
, we can also translate to your List
. 我们也可以转换为您的
List
,而不是基本的NonEmpty
。
pattern Single :: forall e a. () => e ~ 'NotEmpty => a -> List e a
pattern Single x = Cons x Nil
pattern Multiple :: forall e a. () => e ~ 'NotEmpty => a -> List 'NotEmpty a -> List e a
pattern Multiple x xs <- Cons x xs@(Cons _ _)
where Multiple x xs = Cons x xs
-- my dormant bidirectional pattern synonyms GHC proposal would allow just
-- pattern Multiple x (Cons y xs) = Cons x (Cons y xs)
-- but current pattern synonyms are a little stupid, so Multiple is ugly
{-# COMPLETE Nil, Single, Multiple :: List #-}
-- Single and Multiple are actually patterns on List e a, not List NotEmpty a
-- Therefore the COMPLETE must include Nil, or else we'd show that all
-- Lists are nonempty (\case { Single _ -> Refl; Multiple _ _ -> Refl })
-- They are, however, still complete on List NotEmpty a
-- GHC will "infer" this by "trying" the Nil constructor and deeming it impossible
Giving 给予
eLast :: NEList a -> a
eLast (Single x) = x
eLast (Multiple _ xs) = eLast xs
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.